フロントエンド
サーバーサイドでレンダリングされる BXM ビュー、小さな Alpine.js コンポーネント、そして Vite でコンパイルされる SCSS/JS パイプライン。
このページ内
ããã³ããšã³ã
å šäœã®æ§æ
ããã³ããšã³ãã¯ãµãŒããŒãµã€ãã¬ã³ããªã³ã°ãš Alpine.js ã®ãã€ããªããã¢ããªã±ãŒã·ã§ã³ã§ã - SPA ã§ããã¯ã©ã€ã¢ã³ããµã€ãã«ãŒã¿ãŒã§ããããŸããã
app/layouts/ ã«ãã Admin.bxmãAuthSplit.bxm ãªã©ã HTML ãã¬ãŒã ãã¬ã³ããªã³ã°ããŸãã
app/views/ ã®ãã¥ãŒã¯ããã³ãã©ãŒã«ãã£ãŠãã§ã«è§£æ±ºæžã¿ã® rc/prc ããŒã¿ã䜿ã£ãŠã¬ã³ããªã³ã°ãããŸãã
å°ã㪠x-data ã³ã³ããŒãã³ããããã©ãŒã ãã¢ãŒãã«ãããã¯ãŒããã°ã«ãåŠçããŸã - ã³ã³ããŒãã³ãããšã®ãã«ãã¹ãããã¯äžèŠã§ãã
resources/assets/ ã® SCSS + JS ã public/includes/ ã«ã³ã³ãã€ã«ãããASSET_URL ãã¬ãã£ãã¯ã¹ã§é
ä¿¡ãããŸãã
Alpine.js ã¢ãŒããã¯ãã£
App.js (entry)
âââ Registers all Alpine stores + components
âââ Imports Bootstrap JS + Phosphor icons + Tippy.js
â
âââ Stores ($store.*)
â âââ theme.js â dark/light mode, syncs data-bs-theme + localStorage
â âââ sidebar.js â collapse/open, mobile overlay, localStorage persistence
â
âââ Components (x-data)
âââ auth/ â AuthForm, RegisterForm, ForgotPasswordForm, PasswordResetForm
âââ security/ â AuditLogForm, PermissionsForm, RolesForm, UserDetailForm, UsersForm
âââ profile/ â PasskeyOnboarding, PreferencesForm, ProfileForm
âââ settings/ â SettingsForm, SettingsRegistryForm
âââ ui/ â Drawer, GlobalProgress, GlobalToast, Logo, MessageBox, PasswordMeter, PasswordStrength, Switch
åã³ã³ããŒãã³ãã¯ãAlpine ã® x-data ãªããžã§ã¯ããè¿ãåç¬ã®ã¢ãžã¥ãŒã«ã§ãã
export default () => ( {
visible: true,
init() {
setTimeout( () => this.visible = false, 5000 );
}
} );
<div x-data="messageBox" x-show="visible" x-transition>
<!-- alert content -->
</div>
SCSS æ§é
app.scss
âââ _variables.scss Bootstrap variable overrides
âââ bootstrap Full Bootstrap 5.3 import
âââ _base.scss CSS custom properties (light/dark theme)
âââ components/ 9 component partials
âââ layouts/ Admin + Auth layout partials
âââ views/ Page-specific styles
Vite ã®èšå®
vite.config.mjs 㯠coldbox-vite-plugin ã® coldbox() ãã©ã°ã€ã³ã䜿çšããŠããŸãã
- ãšã³ããªãã€ã³ã:
resources/assets/scss/app.scssãšresources/assets/js/App.js refresh: appRefreshPathsâ ãã³ãã©ãŒ/ãã¥ãŒã®å€æŽæã«èªåãã«ãªããŒãpublicDirectory: "public/includes"â ãã«ãæžã¿ã¢ã»ããã®åºåå - æ°ãã Dart Sass åãã®
silenceDeprecationsãã©ã°(importãglobal-builtinãcolor-functionsãif-function)ãæã€ SCSS ããªããã»ããµ
npm run dev # Vite dev server with HMR
npm run build # Production build â public/includes/
npm run lint # ESLint check on resources/assets/js
npm run lint:fix # ESLint auto-fix
npm run lint:scss # Stylelint on resources/assets/scss
æ¬çªç°å¢ã§ã¯ãã³ã³ãã€ã«æžã¿ã¢ã»ããã® URL ã«ã¯ ASSET_URL ç°å¢å€æ°(.env.example ã§ã¯ããã©ã«ãã§ /includes)ããã¬ãã£ãã¯ã¹ãšããŠä»ããŸã - 詳现㯠èšå® ãåç
§ããŠãã ããã
ãµãŒããŒãµã€ãã¬ã³ããªã³ã°ã®ãã¥ãŒã³ã³ããŒãã³ã
ãããã® BXM ããŒã·ã£ã«ã¯ app/views/_components/ é
äžã«ãããColdBox ã® view() ãã«ããŒã§ã¬ã³ããªã³ã°ãããŸããæå³çã«ãã¬ãŒã³ããŒã·ã§ã³ã«ç¹åããŠãããargs æ§é äœã§å€ãæž¡ããããžãã¹ããžãã¯ã¯ãã³ãã©ãŒããµãŒãã¹ã«çœ®ããŠãã ããã
ã¢ããªã±ãŒã·ã§ã³ã·ã§ã«
| ããŒã·ã£ã« | ç®çãšå ¥å |
|---|---|
_components/app/includes | ããã¥ã¡ã³ãã¡ã¿ããŒã¿ãããŒã/ãµã€ãããŒã® FOUC 鲿¢ããã¹ããŒã¹ã¯ãªãããVite ã® CSS/JSããªãã·ã§ã³ã® titleã<head> å
ã§ 1 åã ãã€ã³ã¯ã«ãŒãããŸãã |
_components/app/sidebar | 管çããã²ãŒã·ã§ã³ãæš©éã«å¿ãã Users/Roles/Permissions/Audit Log ãªã³ã¯ãèšå®ãµãã¡ãã¥ãŒããµã€ãããŒããã¿ãŒãprc.authUser ãèªã¿åããŸããAdmin.bxm ããã€ã³ã¯ã«ãŒãããŸãã |
_components/app/sidebar-brand | ãµã€ãããŒã§äœ¿çšãããã¢ããªã±ãŒã·ã§ã³ã®ããŽ/ååãªã³ã¯ã |
_components/app/sidebar-footer | ãµã€ãããŒã§äœ¿çšããããèªèšŒæžã¿ãŠãŒã¶ãŒã®æŠèŠãšãããã£ãŒã«/ãµã€ã³ã¢ãŠãã¢ã¯ã·ã§ã³ã |
_components/app/topbar | ãµã€ãããŒãã°ã«ãããŒããã°ã«ããã³ãããªã¹ãããŠãŒã¶ãŒã¡ãã¥ãŒããµã€ã³ã¢ãŠãã¢ã¯ã·ã§ã³ãprc.authUser ãš prc.title ãèªã¿åããŸãã |
_components/app/topbar-breadcrumbs | ãããããŒå ã«ã¬ã³ããªã³ã°ãããããã·ã¥ããŒãã®ãã³ãããªã¹ããããæ·±ãããã²ãŒã·ã§ã³ã远å ããéã«æ¡åŒµããŠãã ããã |
_components/app/topbar-notifications | ã¢ããªã±ãŒã·ã§ã³éç¥çšã®ãããããŒéç¥ã¹ããã/ã³ã³ããŒãã³ãã |
_components/app/footer | èäœæš©è¡šç€ºãšããã¿ãŒãªã³ã¯ããªãã·ã§ã³ã® classesãprc.settings.cbCopyrightNotice ãèªã¿åããŸãã |
èªèšŒããŒã·ã£ã«
| ããŒã·ã£ã« | ç®çãšå ¥å |
|---|---|
_components/auth/footer | èªèšŒã¬ã€ã¢ãŠãã§äœ¿çšãããããã¿ãŒã |
_components/auth/passwordInput | 衚瀺åãæ¿ããšãã¹ã¯ãŒã匷床ã®è¡šç€ºãåãããåå©çšå¯èœãªãã¹ã¯ãŒããã£ãŒã«ãã |
UI ããŒã·ã£ã«
| ããŒã·ã£ã« | ç®çãšå ¥å |
|---|---|
_components/ui/modal | ãªãã·ã§ã³ã®ãã¹ãããããã¥ãŒãã¬ã³ããªã³ã°ããæ±çšã® Alpine ãã€ã¢ãã°ãå¿
é ã® id ã¯äžæã§ããå¿
èŠããããtitleãopenExpressionãcloseExpressionãcontentViewãcontentArgs ããµããŒãããŸãã |
_components/ui/drawer | èæ¯/Escape ã§ã®éããæäœããµããŒããããå³åŽã®ãã©ãŒã«ã¹ãã©ããä»ããã€ã¢ãã°ããªãã·ã§ã³ã§ contentView/contentArgs ãæå®ã§ããdrawer() ãåæåããŸãã |
_components/ui/confirm | éçãŸãã¯Alpine ãã€ã³ãã®ã¡ãã»ãŒãžã確èª/ãã£ã³ã»ã«åŒãã©ãã«ãã¢ã€ã³ã³ããã¿ã³ã¯ã©ã¹ãç¡å¹ååŒãæã€ç¢ºèªãã€ã¢ãã°ã |
_components/ui/messagebox | éããããšãã§ããæ
å ±/æå/èŠå/ãšã©ãŒã®ã¢ã©ãŒããéç㪠message/titleããŸãã¯åç㪠messageExpression/typeExpression/dismissAction ã«å ããautoDismiss ãš classes ããµããŒãããŸãã |
_components/ui/globalProgress | ã°ããŒãã«ãªã¢ã¯ã»ã·ãã«ããã°ã¬ã¹ããŒãã¬ã€ã¢ãŠãããšã« 1 åã ãã€ã³ã¯ã«ãŒããã$progress.start()`、`$progress.set()ã$progress.stop() ã§å¶åŸ¡ããŸãã |
_components/ui/globalToast | ã°ããŒãã«ãªããŒã¹ãã¹ã¿ãã¯ãã¬ã€ã¢ãŠãããšã« 1 åã ãã€ã³ã¯ã«ãŒãããdurationãpositionãmaxVisible ãåãä»ãã$toast() ããã®éç¥ãåãåããŸãã |
_components/ui/avatar | hasAvatar ã true ã®ãšãã«ãŠãŒã¶ãŒã®ã¢ãã¿ãŒç»åãã¬ã³ããªã³ã°ããããã§ãªããã° initials ã«ãã©ãŒã«ããã¯ããŸãããµã€ãããŒããããããŒãUsers äžèЧãUsers 詳现ããŒãžã§äœ¿ãããèªã¿åãå°çšã®è¡šç€ºã§ã â ã¢ãã¿ãŒãšãã©ã³ãã£ã³ã°ã㎠ãåç
§ããŠãã ããã |
_components/ui/logo | åå©çšå¯èœãªã¢ããªã±ãŒã·ã§ã³ããŽ/ãã©ã³ãã£ã³ã°ããŒã·ã£ã«ã |
_components/ui/passwordMeter | ãã¹ã¯ãŒããã£ãŒã«ãã®é£ã§äœ¿çšããããã¹ã¯ãŒãããªã·ãŒã¡ãŒã¿ãŒã |
_components/ui/progressbar | ããŒã«ã«ãªæ°å€ã®ããã®ã€ã³ã©ã€ã³ããã°ã¬ã¹ããŒããŒã·ã£ã«ã |
_components/ui/switch | çåœå€ã®èšå®ã®ããã®ã¢ã¯ã»ã·ãã«ãªã¹ã€ããã³ã³ãããŒã«ããŒã·ã£ã«ã |
Alpine ã³ã³ããŒãã³ããšã¹ãã¢
resources/assets/js/App.js ã¯ã以äžã®ååã Alpine ã«ã°ããŒãã«ã«ç»é²ããŸããBXM ãã¥ãŒå
ã§ã¯ x-data="name" ãŸã㯠x-data="name(...)" ãšããŠäœ¿çšããŠãã ããããã©ãŒã ã³ã³ããŒãã³ãã¯å¯Ÿå¿ãããã³ãã©ãŒã«ãŒããžãªã¢ãŒããªã¯ãšã¹ããè¡ãããã®ãã¥ãŒããäŸçµŠããã CSRF ããŒã¯ã³ã fetchWithCsrf() ãéããŠéä¿¡ãããããšãæ³å®ããŠããŸã(ãã¥ãŒããŒã·ã§ã³ãè¡ããªã¯ãšã¹ãã«ããã CSRF ãåç
§)ã
ã¢ããªã±ãŒã·ã§ã³ã·ã§ã«ãšèªèšŒ
| Alpine å | ãœãŒã¹ | åœ¹å² |
|---|---|---|
adminBody | components/app/AdminBody.js | 管çããŒãžã®ã·ã§ã«åäœãšã°ããŒãã«ã¬ã€ã¢ãŠãã€ãã³ãã |
sidebarBrand | components/app/SidebarBrand.js | ãµã€ãããŒã®ãã©ã³ããšã®ã€ã³ã¿ã©ã¯ã·ã§ã³ã |
footer | components/app/Footer.js | ããã¿ãŒã®ç¶æ ãšçŸåšã®å¹Žã®æåã |
authForm | components/auth/AuthForm.js | ãã°ã€ã³éä¿¡ãããªããŒã·ã§ã³ããªã¡ã³ããŒããŒããšã©ãŒã |
registerForm | components/auth/RegisterForm.js | ç»é²ããªããŒã·ã§ã³ãã¡ãŒã«ã¢ãã¬ã¹ã®å©çšå¯åŠç¢ºèªãéä¿¡ã |
forgotPasswordForm | components/auth/ForgotPasswordForm.js | ãã¹ã¯ãŒãå¿ããªã¯ãšã¹ãã®ç¶æ ãšãã£ãŒãããã¯ã |
passwordResetForm | components/auth/PasswordResetForm.js | ãã¹ã¯ãŒããªã»ããããŒã¯ã³ã®éä¿¡ãšããªããŒã·ã§ã³ã |
管çã»ãããã£ãŒã«ãã©ãŒã
| Alpine å | ãœãŒã¹ | åœ¹å² |
|---|---|---|
usersForm | components/security/UsersForm.js | ãŠãŒã¶ãŒäžèŠ§ãæ€çŽ¢ãããŒãžããŒã·ã§ã³ãæåŸ ãã¹ããŒã¿ã¹ã管çã¢ã¯ã·ã§ã³ã |
userDetailForm | components/security/UserDetailForm.js | ãŠãŒã¶ãŒã®ãããã£ãŒã«ãããŒã«ãæš©éãèšå®ãããŒã¯ã³ãèªèšŒã¢ã¯ã·ã§ã³ã |
rolesForm | components/security/RolesForm.js | ããŒã«ã® CRUDãããã³ãŠãŒã¶ãŒãšæš©éã®å²ãåœãŠ/è§£é€ã |
permissionsForm | components/security/PermissionsForm.js | æš©éã®äžèŠ§è¡šç€ºãš CRUD æäœã |
auditLogForm | components/security/AuditLogForm.js | ç£æ»ãã£ã«ã¿ãªã³ã°ãããŒãžããŒã·ã§ã³ã詳现ããã¯ãŒãCSV ãšã¯ã¹ããŒããããŒãžãã¯ãªã¢ã¢ã¯ã·ã§ã³ã |
settingsForm | components/settings/SettingsForm.js | ã³ã¢ãšãªãã¢ããªã±ãŒã·ã§ã³èšå®ã®ç·šéãšãã£ãã·ã¥é¢é£ã®ãã£ãŒãããã¯ã |
logoUploader | components/settings/LogoUploader.js | ãApp Logo Pathããã£ãŒã«ãã®ããã®ãã©ã³ãã£ã³ã°ããŽã®ã¢ããããŒã/åé€ãæ¢åã®æå URL å ¥åãšã©ã€ããã¬ãã¥ãŒãšäžŠåããŸã â ã¢ãã¿ãŒãšãã©ã³ãã£ã³ã°ã㎠ãåç §ã |
settingsRegistryForm | components/settings/SettingsRegistryForm.js | ã¬ãžã¹ããªã®æ€çŽ¢ãããŒãžããŒã·ã§ã³ãäœæ/æŽæ°ãæå¹/ç¡å¹åãåé€ã¢ã¯ã·ã§ã³ã |
profileForm | components/profile/ProfileForm.js | ãããã£ãŒã«ãã£ãŒã«ãããã¹ã¯ãŒãããªã·ãŒãAPI ããŒã¯ã³ç®¡çãã¡ãŒã«ã¢ãã¬ã¹å€æŽãªã¯ãšã¹ã/ãã£ã³ã»ã«ã®ãµããã©ãŒã ãã¢ãã¿ãŒã®ã¢ããããŒã/åé€ã |
preferencesForm | components/profile/PreferencesForm.js | ãŠãŒã¶ãŒèšå®ã®ä¿åã |
passkeyOnboarding | components/profile/PasskeyOnboarding.js | ãã¹ããŒç»é²ãšå¿ é ãã¹ããŒã®ãªã³ããŒãã£ã³ã°ã |
UI ã³ã³ããŒãã³ããšã°ããŒãã« API
| Alpine å | ãœãŒã¹ | åœ¹å² |
|---|---|---|
messageBox | components/ui/MessageBox.js | ã¢ã©ãŒãã®è¡šç€ºãšãªãã·ã§ã³ã®ã¿ã€ã ãè§£é€ã |
passwordMeter | components/ui/PasswordMeter.js | ãã¹ã¯ãŒãèŠä»¶ãšåŒ·åºŠã®è¡šç€ºã |
passwordStrength | components/ui/PasswordStrength.js | ãã¹ã¯ãŒã匷床ã®èšç®ãšã©ãã«ã |
switchComponent | components/ui/Switch.js | ãã°ã«ã®ç¶æ ãšå€æŽåŠçã |
drawer | components/ui/Drawer.js | ããã¯ãŒã®ã©ã€ããµã€ã¯ã«ãšãã©ãŒã«ã¹åäœã |
globalProgress | components/ui/GlobalProgress.js | ããã°ã¬ã¹ã€ãã³ããšçŸåšã®ããã°ã¬ã¹å€ã |
globalToast | components/ui/GlobalToast.js | ããŒã¹ããã¥ãŒãè§£é€ãã¿ã€ããããã³ã°ãã¹ã¿ãã¯äžéã |
ãœãŒã¹ã«ã¯ Header.jsãSidebar.jsãTopBarNotifications.jsãLogo.js ãå«ãŸããŠããŸãããããã®ãšã¯ã¹ããŒãã¯ããŒã«ã«ã€ã³ããŒããšããŠå©çšå¯èœã§ãããçŸåš App.js ã«ã¯ç»é²ãããŠããŸãããã°ããŒãã«ãª x-data ã³ã³ããŒãã³ããšããŠäœ¿çšããåã«ãAlpine.data() ã§ç»é²ããŠãã ããã
ã¹ãã¢ããŠãŒãã£ãªãã£ãããžãã¯ããããã£
| API | ãœãŒã¹ | çšé |
|---|---|---|
$store.theme | stores/theme.js | ã©ã€ã/ããŒã¯ã¢ãŒããdata-bs-themeãlocalStorage ãžã®æ°žç¶åã |
$store.sidebar | stores/sidebar.js | ãã¹ã¯ãããã®æãããã¿ãã¢ãã€ã«ã®ééãlocalStorage ãžã®æ°žç¶åã |
$formatDate`、`$formatDateTimeã$relativeDate | utils/dateFormat.js | ãã©ãŒã«ããã¯ä»ãã®äžè²«ããæ¥ä»è¡šç€ºã |
$countLabel | utils/countLabel.js | åæ°/è€æ°ã®ä»¶æ°ã©ãã«ã |
$sortClass`、`$sortIcon | utils/sort.js | ãœãŒãå¯èœãªããŒãã«ããããŒãšã€ã³ãžã±ãŒã¿ãŒã |
$passwordMeetsPolicy | utils/passwordPolicy.js | èšå®ããããã¹ã¯ãŒãèŠä»¶ãæºãããŠãããã確èªããŸãã |
$isEmail | App.js | 軜éãªã¡ãŒã«ã¢ãã¬ã¹åœ¢åŒãã§ãã¯ã |
$toast` / `$progress | components/ui/GlobalToast.jsãGlobalProgress.js | ã°ããŒãã«ãªéç¥ãšããã°ã¬ã¹ APIã |
$focus` / `$copy | App.js | Alpine ã®æŽæ°åŸã«åå«èŠçŽ ã«ãã©ãŒã«ã¹ããŸãããã©ãŠã¶ã®ã¯ãªããããŒã API çµç±ã§ããã¹ããã³ããŒããŸãã |
createRemoteListing() | utils/listing.js | å ±æã®ãªã¢ãŒããªã¹ãã£ã³ã°ç¶æ ãããŒãã£ã³ã°ãããŒãžããŒã·ã§ã³ããšã©ãŒãã³ããªã³ã°ã |
fetchWithCsrf()ãrefreshCsrfToken() | utils/csrf.js | ã³ã³ããŒãã³ãã® CSRF ããŒã¯ã³ã䜿ã£ãŠãã¥ãŒããŒã·ã§ã³ãªã¯ãšã¹ããéä¿¡ããå€ãããŒã¯ã³ããäžåºŠã ã埩æ§ããŸãã |
AlpinePlugins.js 㯠CollapseãFocusãMaskãPersist ãã€ã³ã¹ããŒã«ããŸããpasskeys.js ã¯ãã©ãŠã¶åŽã® WebAuthn 飿ºãæäŸããŸããæ°ããåå©çšå¯èœãªãã©ãŠã¶ API ã¯ãããã«ææžåããã°ããŒãã«ãªãã®ã§ããã° App.js ã«ç»é²/ã€ã³ããŒãã远å ããŠãã ããã
ãã¥ãŒããŒã·ã§ã³ãè¡ããªã¯ãšã¹ãã«ããã CSRF
é GET ãªã¯ãšã¹ããéä¿¡ãããã¹ãŠã®ã³ã³ããŒãã³ãã¢ã¯ã·ã§ã³ã¯ãfetch() ãçŽæ¥åŒã³åºãã®ã§ã¯ãªããfetchWithCsrf()(utils/csrf.js)ãéããŸããããã¯ããã¥ãŒããŒã·ã§ã³ãè¡ããªã¯ãšã¹ããæ§ç¯ãããå¯äžã®ã«ãã»ã«åãããå Žæã§ãããããŒã¯ã³åŸ©æ§ã®æå - ãããŠåŸã§è¿œå ããããããããªããã®(ãªã¯ãšã¹ã/ã¬ã¹ãã³ã¹ããã¯ãã°ããŒãã«ããããŒããã¬ã¡ããª)- ã¯ãç¶æ
ã倿Žãããã³ã«ããŸããŸééããã³ã³ããŒãã³ãããšã«å€æŽããã®ã§ã¯ãªããããã ãã倿Žããã°æžã¿ãŸãã
ãããããªã埩æ§ãå¿
èŠãªã®ãã ã³ã³ããŒãã³ãã® csrfToken ã¯ããã®ãã¥ãŒãã¬ã³ããªã³ã°ããããšãã«äžåºŠã ãåã蟌ãŸããŸããããŒãžãéãããŸãŸã®éã«ããµãŒããŒã¯ãããç¡å¹åã§ããŸããcbcsrf èªèº«ã®ããã¥ã¡ã³ããæããŠãã 2 ã€ã®æ¹æ³ã«ãã£ãŠã§ããcsrfField()(ãã¹ãŠã®é衚瀺 csrf å
¥åã®èåŸã«ããããã¯ã¹ã€ã³)ã¯ããªã¯ãšã¹ãããšã®ååäœ¿çšæã«ã»ãã·ã§ã³ã®ããŒã¯ã³ã匷å¶çã«ããŒããŒã·ã§ã³ããããããããã¬ã³ããªã³ã°ããããŒãž(Settingsããã¹ããŒå¿
é ããŒãžãèªèšŒããŒãž)ã¯ãéããŠããä»ã®ãã¹ãŠã®ã¿ãã«åº§ã£ãŠããããŒã¯ã³ãéãã«ç¡å¹åããŸãããŸããããŒã¯ã³ã¯ããŒãžãèªã¿èŸŒãŸããŠããã§ã¯ãªããäœæãããŠããäžå®æéã§æéåãã«ãªããããããŒã¯ã³ã®å¯¿åœã®çµç€ã«ã¬ã³ããªã³ã°ãããããŒãžã¯ãæ®ãæ°ç§ãããªãããŒã¯ã³ãæž¡ãããå¯èœæ§ããããŸãããããã«ãããã³ã³ããŒãã³ãã«åã蟌ãŸããããŒã¯ã³ã¯ããŠãŒã¶ãŒãå
¥åãçµããåã«å€ããªãããšããããŸãã
å¥çŽ:
export async function fetchWithCsrf( component, url, method, buildRequest ) { /* ... */ }
export async function refreshCsrfToken( component ) { /* ... */ }
component㯠Alpine ã³ã³ããŒãã³ãã®ã€ã³ã¹ã¿ã³ã¹ã§ã(thisãæž¡ããŸã)ãå¯å€ãªcsrfTokenããããã£ãå ¬éããŠããå¿ èŠããããŸã -fetchWithCsrf()ã¯ãããèªã¿åã£ãŠãªã¯ãšã¹ããæ§ç¯ããããŒã¯ã³ãå€ãå Žåã®åè©Šè¡æã«ã¯ãrefreshCsrfToken()çµç±ã§ã»ãã·ã§ã³ã®çŸåšã®ããŒã¯ã³ã§äžæžãããŸããbuildRequest( csrfToken )ã¯ãäžããããããŒã¯ã³ã«å¯Ÿããã¡ãœããåºæã®RequestInitãã£ãŒã«ã(headersãbodyãcredentialsãªã©)ãè¿ããŸããåè©Šè¡æã«ãå床åŒã³åºããããããäžåºŠã ãèšç®ããå€ãã¯ããŒãžã£ã§ä¿æããã®ã§ã¯ãªããæ¯åããã£ãæ°ããæ§ç¯ããå¿ èŠããããŸã - ããã«ãããåããã«ããŒãURLSearchParamsãJSON.stringify()ãFormDataã®ãããã®ããã£ãåãããã«æ±ããŸãã- 403 ãè¿ãããå Žåã
fetchWithCsrf()ã¯refreshCsrfToken()ãåŒã³åºããå®éã«æ°ããããŒã¯ã³ãååŸã§ããå Žåã¯ãbuildRequest()ãå床åŒã³åºããŠãªã¯ãšã¹ãã 1 åã ãåéããŸãã2 åç®ã® 403(äŸãã°å®éã®èªå¯å€±æããå®å šã«æéåãã«ãªã£ãã»ãã·ã§ã³)ã¯ãã®ãŸãŸè¿ãããŸã - ãã®å ŽåãåŒã³åºãå ã¯äŸç¶ãšããŠéåžžã®ãšã©ãŒãã³ããªã³ã°ãå¿ èŠãšããŸãã
const response = await fetchWithCsrf( this, "/permissions", "POST", ( csrf ) => ( {
headers : { "Content-Type": "application/x-www-form-urlencoded" },
body : new URLSearchParams( { permission: this.form.permission, csrf } ),
} ) );
const response = await fetchWithCsrf( this, form.action, "POST", ( csrf ) => {
const formData = new FormData( form );
formData.set( "csrf", csrf );
return { body: formData, credentials: "same-origin", headers: { Accept: "application/json" } };
} );
GET/HEAD ã®èªã¿åãã ãã fetchWithCsrf() ãã¹ãããã㊠fetch() ãçŽæ¥åŒã³åºããŸã - ããã㯠CSRF ããŒã¯ã³ãæããããããåå ã§ 403 ã«ãªãããšããããŸãããäžéšã® cbSecurity ã¢ãžã¥ãŒã«ã®ãšã³ããã€ã³ã(WebAuthn ãã¹ããŒã®ã»ã¬ã¢ããŒã«ãŒã)ãããã¬ãŒã³ãª fetch() ã§åŒã³åºãããŸããããã㯠WebAuthn ã»ã¬ã¢ããŒèªäœãéããŠèªèšŒãè¡ãããã®ã¢ããªã® CSRF ããŒã¯ã³ã¯äœ¿ããªãããããã®ãã«ããŒã®å¯Ÿè±¡å€ã§ããresources/assets/js/components/ å
ã®ãã以å€ã®ãã¹ãŠã®ãã¥ãŒããŒã·ã§ã³ã¯ fetchWithCsrf() ãéããŸãããµãŒããŒã®ç¶æ
ã倿Žãããªã¯ãšã¹ãã远å ããæ°ãããã©ãŒã ã³ã³ããŒãã³ãã§ãããããšäžè²«æ§ãä¿ã£ãŠãã ããã
ã¢ãã¿ãŒãšãã©ã³ãã£ã³ã°ããŽ
ãŠãŒã¶ãŒã®ã¢ãã¿ãŒãšã¢ããªã±ãŒã·ã§ã³ã®ãã©ã³ãã£ã³ã°ããŽã¯ãéå
¬éã® cbfs assets ãã£ã¹ã¯(èšå® ãåç
§)ã«ä¿åãããéçãã¡ã€ã«ãšããŠé
ä¿¡ãããã®ã§ã¯ãªã Assets.bx(ãã³ãã©ãŒãšã«ãŒãã£ã³ã° ãåç
§)ã«ãã£ãŠã¹ããªãŒãã³ã°é
ä¿¡ãããŸãã

- 衚瀺ã¯
_components/ui/avatarããŒã·ã£ã«ãéããŠè¡ãããŸããhasAvatarã true ã®ãšãã«<img src="/avatars/:userId/:size">ãã¬ã³ããªã³ã°ããããã§ãªããã°ã€ãã·ã£ã«ã®<span>ã«ãã©ãŒã«ããã¯ããŸãããµã€ãããŒããããããŒãUsers äžèЧããŒãã«(ãµãŒããŒåŽã§æåœ±ãããhasAvatarãã£ãŒã«ã)ã«çµã¿èŸŒãŸããŠãããUsers 詳现ããŒãžã§ã¯ã€ã³ã©ã€ã³ã§äœ¿ãããŸã(ãã®ããŒãžã®ã¢ãã¿ãŒã¯éçããŒã·ã£ã«ã§ã¯ãªã Alpine é§åã®ãµããªãŒã«ãŒãå ã«ãããããuser.hasAvatarã«å¿ããx-show/x-cloakã®åãæ¿ããè¡ããŸã)ã - çŸåšã®ãŠãŒã¶ãŒèªèº«ã®ã¢ãã¿ãŒã®ã¢ããããŒã/åé€ã¯ãããã£ãŒã«ããŒãžã«ããã
profileForm(ProfileForm.js)ãææããŸããé衚瀺ã®ãã¡ã€ã«å ¥åãéžæãããç»åã base64 ããŒã¿ URI ãšããŠèªã¿èŸŒã¿(readFileAsDataUrl())ããããPOST /profile/avatarã«éä¿¡ããŸããDELETE /profile/avatarã§åé€ããŸããã©ã¡ããversionã«ãŠã³ã¿ãŒãå¢å ããããã¡ã€ã«ãã¹èªäœã¯ã¢ããããŒãéã§å€ãããªããããã¹ããªãŒãã³ã°é ä¿¡ããã URL ã®ãã£ãã·ã¥ç¡å¹åçšã¯ãšãªãã©ã¡ãŒã¿ãšããŠäœ¿çšãããŸãã - ãã©ã³ãã£ã³ã°ããŽã¯ãSettings ããŒãžã§
logoUploaderã³ã³ããŒãã³ã(LogoUploader.js)ãä»ããPOST/DELETE /settings/logoã«å¯ŸããŠåæ§ã®ã¢ããããŒã/åé€ã®æ±ããåããŸããã¢ããããŒãæã«ã¯cbAppLogoèšå®ã®ããã¹ãå ¥åå€ãã¹ããªãŒãã³ã°ãã¹(/branding/logo/lg)ã«çœ®ãæããå逿ã«ã¯èšå®æžã¿ã®ããã©ã«ãå€ã埩å ããŸã â æåã® URL ããã¹ãå ¥åãšã©ã€ã<img>ãã¬ãã¥ãŒã¯ã代ããã«cbAppLogoãå€éš URL ã«åããã人ã®ããã«ããããŸã§ã©ããåäœãç¶ããŸãã - äž¡æ¹ã®ã¢ããããŒããšã³ããã€ã³ãã¯åã圢åŒãåãä»ããŸããç»åã¯ãµãŒããŒåŽã§
BaseSecureHandler.decodeDataUri()ã«ãã£ãŠãã³ãŒããããImageService(app/models/system/ImageService.bx)ã«ãã£ãŠsm/lgã® JPEG(ã¢ãã¿ãŒ)ãŸã㯠PNG(ããŽ)ããªã¢ã³ãã«ãªãµã€ãº/ã¯ããããããŸãã