/** * alpine-components.ts — Alpine.js CSP-compatible component registrations. * * All components use the Alpine CSP build (`@alpinejs/csp`), which replaces * the expression evaluator with registered function references. Inline * JavaScript expressions in Alpine directives are not allowed; all directives * must reference properties or methods by name. * * Registered during `alpine:init` so they are available before Alpine * initialises the DOM. */ type AlpineRoot = HTMLElement & { dataset: DOMStringMap }; document.addEventListener('alpine:init', () => { const Alpine = window.Alpine; if (!Alpine) return; // ── tabSwitcher — CLI / MCP / REST tab panel (issue_detail.html) ────────── // // Usage: x-data="tabSwitcher" // Buttons: :class="cliClass" @click="setCliTab" // Panes: x-show="showCli" Alpine.data('tabSwitcher', () => ({ tab: 'cli' as string, get showCli() { return (this as { tab: string }).tab === 'cli'; }, get showMcp() { return (this as { tab: string }).tab === 'mcp'; }, get showRest() { return (this as { tab: string }).tab === 'rest'; }, get cliClass() { return { 'is-active': (this as { tab: string }).tab === 'cli' }; }, get mcpClass() { return { 'is-active': (this as { tab: string }).tab === 'mcp' }; }, get restClass() { return { 'is-active': (this as { tab: string }).tab === 'rest' }; }, setCliTab() { (this as { tab: string }).tab = 'cli'; }, setMcpTab() { (this as { tab: string }).tab = 'mcp'; }, setRestTab() { (this as { tab: string }).tab = 'rest'; }, })); // ── newRepoForm — initialisation toggle (new_repo.html) ────────────────── // // Topics are managed entirely by TypeScript (initTagInput in new-repo.ts). // Only the "add initial commit / show branch name" toggle lives here. // // Usage: x-data="newRepoForm" // Checkbox: x-model="showBranch" // Row: x-show="showBranch" Alpine.data('newRepoForm', () => ({ showBranch: true as boolean, })); // ── settingsPage — settings navigation + confirmation modals ───────────── // // The active section is seeded from the root element's data-initial-section // attribute so Jinja2 can set it server-side without an inline expression. // // Usage: x-data="settingsPage" data-initial-section="{{ active_section }}" // Nav links: :class="generalNavClass" @click.prevent="setSection" data-section="general" // Panels: x-show="showGeneral" // Modals: x-show="archiveOpen" @click="openArchive" @click="closeArchive" Alpine.data('settingsPage', () => ({ section: 'general' as string, archiveOpen: false as boolean, transferOpen: false as boolean, deleteOpen: false as boolean, init() { const root = (this as unknown as { $el: AlpineRoot }).$el; (this as { section: string }).section = root.dataset['initialSection'] ?? 'general'; }, get showGeneral() { return (this as { section: string }).section === 'general'; }, get showCollaboration() { return (this as { section: string }).section === 'collaboration'; }, get showMerge() { return (this as { section: string }).section === 'merge'; }, get showDanger() { return (this as { section: string }).section === 'danger'; }, get generalNavClass() { return { active: (this as { section: string }).section === 'general' }; }, get collaborationNavClass() { return { active: (this as { section: string }).section === 'collaboration' }; }, get mergeNavClass() { return { active: (this as { section: string }).section === 'merge' }; }, get dangerNavClass() { return { active: (this as { section: string }).section === 'danger' }; }, setSection(e: Event) { const el = e.currentTarget as HTMLElement; (this as { section: string }).section = el.dataset['section'] ?? 'general'; }, openArchive() { (this as { archiveOpen: boolean }).archiveOpen = true; }, closeArchive() { (this as { archiveOpen: boolean }).archiveOpen = false; }, openTransfer() { (this as { transferOpen: boolean }).transferOpen = true; }, closeTransfer() { (this as { transferOpen: boolean }).transferOpen = false; }, openDelete() { (this as { deleteOpen: boolean }).deleteOpen = true; }, closeDelete() { (this as { deleteOpen: boolean }).deleteOpen = false; }, })); });