import { createApp, defineComponent, h, VNode } from 'vue' import { createRouter, createWebHistory } from 'vue-router' import { createPinia } from 'pinia' // Import preset styling CSS import './assets/css/presets.css' import { t } from './i18n' // Load mammoth browser build as a script asset (works reliably with Umo Editor v8) // Vite will emit a URL for the browser build; we inject it before initializing the editor // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - vite url import import mammothUrl from 'mammoth/mammoth.browser.min.js?url' import App from './App.vue' import { useAuthStore } from './auth/index' import { useFileStore } from './store/files' import { createAuthGuard } from './auth/router-guard' // Routes import RootView from './views/RootView.vue' import Home from './views/Home.vue' import RunSheet from './views/RunSheet.vue' import MediaViewer from './views/MediaViewer.vue' import Sheets from './views/Sheets.vue' import Documents from './views/Documents.vue' import Forms from './views/Forms.vue' import FormBuilder from './views/FormBuilder.vue' import Bin from './views/Bin.vue' import OauthCallback from './views/OauthCallback.vue' import AuthenticatedLayout from './layouts/AuthenticatedLayout.vue' // Import utils as needed import FilePicker from './views/FilePicker.vue' import DocsEditor from './views/DocsEditor.vue' import Login from './views/Login.vue' import StoryEditor from './views/StoryEditor.vue' import Media from './views/Media.vue' import ImportView from './views/ImportView.vue' const routes = [ // Root route with authentication handling { path: '/', name: 'root', component: RootView, meta: { public: true } }, // Public routes { path: '/login', name: 'login', component: Login, meta: { public: true } }, { path: '/oauth/callback', name: 'oauthCallback', component: OauthCallback, meta: { public: true } }, { path: '/f/:slug/success', name: 'form-success', component: () => import('./views/FormPaymentSuccess.vue'), meta: { public: true }, }, { path: '/f/by-id/:id/success', name: 'form-success-by-id', component: () => import('./views/FormPaymentSuccess.vue'), meta: { public: true }, }, { path: '/f/by-id/:id', name: 'form-player-by-id', component: () => import('./views/FormPlayerHost.vue'), meta: { public: true, allowGuest: true }, }, { path: '/f/:slug(.*)', name: 'form-player', component: () => import('./views/FormPlayerHost.vue'), meta: { public: true }, }, // Public signing routes { path: '/signing/editor/:signingRequestId', name: 'signing-editor', component: () => import('./views/SigningEditor.vue'), meta: { hideLayout: true, public: true }, }, { path: '/signing/sign/:token', name: 'signing-player', component: () => import('./views/SigningPlayer.vue'), meta: { public: true }, }, { path: '/share/doc/:appFileId', name: 'docs-share', component: DocsEditor, meta: { hideLayout: true, public: true }, }, { path: '/share/sheet/:id', name: 'sheet-share', component: RunSheet, meta: { hideLayout: true, public: true, allowGuest: true }, }, { path: '/share/slide/:deckId', name: 'slides-share', component: () => import('./views/SlidesEditorV2.vue'), meta: { hideLayout: true, public: true }, }, // Authenticated routes with layout { path: '/', component: AuthenticatedLayout, children: [ { path: 'home', name: 'home', component: Home, meta: { allowGuest: true } }, { path: 'assistant', name: 'assistant', component: () => import('./views/AIWorkspace.vue') }, { path: 'docs', name: 'docs-view', component: Documents }, { path: 'docs/new', name: 'docs-new', component: DocsEditor, meta: { hideLayout: true } }, { path: 'docs/t/:template', name: 'docs-template', component: DocsEditor, meta: { hideLayout: true } }, { path: 'docs/:appFileId', name: 'docs-edit', component: DocsEditor, meta: { hideLayout: true, public: true } }, { path: 'sheets', name: 'sheets-view', component: Sheets, meta: { allowGuest: true } }, { path: 'sheets/new', name: 'sheets', component: RunSheet, meta: { hideLayout: true, allowGuest: true } }, { path: 'sheets/t/:template', name: 'sheet-template', component: RunSheet, meta: { hideLayout: true, allowGuest: true } }, { path: 'sheets/:id', name: 'sheet', component: RunSheet, meta: { hideLayout: true, public: true } }, { path: 'slides', name: 'slides', component: () => import('./views/Slides.vue') }, { path: 'slides/new', name: 'slides-new', component: () => import('./views/SlidesEditorV2.vue'), meta: { hideLayout: true } }, { path: 'slides/t/:template', name: 'slides-template', component: () => import('./views/SlidesEditorV2.vue'), meta: { hideLayout: true } }, { path: 'slides/:deckId', name: 'slides-edit', component: () => import('./views/SlidesEditorV2.vue'), meta: { hideLayout: true, public: true } }, { path: 'stories', name: 'stories', component: () => import('./views/Stories.vue') }, { path: 'stories/new', name: 'stories-new', component: StoryEditor, meta: { hideLayout: true } }, { path: 'stories/t/:template', name: 'stories-template', component: StoryEditor, meta: { hideLayout: true } }, { path: 'stories/:storyId', name: 'stories-edit', component: StoryEditor, meta: { hideLayout: true, public: true } }, { path: 'forms', name: 'forms', component: Forms }, { path: 'forms/t/:template', name: 'form-template', component: () => import('./views/FormTemplateHandler.vue'), meta: { hideLayout: true } }, { path: 'forms/:id/edit', name: 'form-edit', alias: ['forms/:id'], component: FormBuilder, meta: { hideLayout: true }, }, { path: 'forms/:id/settings', name: 'form-settings', component: FormBuilder, meta: { hideLayout: true } }, { path: 'forms/:id/responses', name: 'form-responses', component: () => import('./views/FormResponses.vue'), meta: { hideLayout: true } }, { path: 'media', name: 'media', component: Media }, { path: 'signing/documents', name: 'signing-documents', component: () => import('./views/SigningDocuments.vue') }, { path: 'bin', name: 'bin-view', component: Bin }, { path: 'picker', name: 'file-picker', component: FilePicker, meta: { hideLayout: true } }, { path: 'import/:id', name: 'import', component: ImportView, meta: { hideLayout: true } }, { path: 'files/:id', name: 'file', component: MediaViewer, meta: { hideLayout: true, public: true } }, ], }, ] const router = createRouter({ history: createWebHistory(), routes, }) // Store app instance for HMR cleanup let appInstance: ReturnType | null = null const app = createApp(App) const pinia = createPinia() // Add global $t for i18n in templates app.config.globalProperties.$t = t app.use(pinia) app.use(router) const authStore = useAuthStore(pinia) authStore.setRouter(router) authStore.setupAxiosInterceptor() authStore.hydrate() // Initialize file store sync handlers (online/offline listeners + periodic sync) const fileStore = useFileStore(pinia) void fileStore.initialize() // Router guard const authGuard = createAuthGuard(authStore) router.beforeEach(authGuard) // HMR cleanup if (import.meta.hot) { import.meta.hot.dispose(() => { if (appInstance) { appInstance.unmount() appInstance = null } }) } // Ensure mammoth is loaded globally before initializing the editor plugin function ensureMammoth(): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((window as any).mammoth) return Promise.resolve() return new Promise((resolve, reject) => { const s = document.createElement('script') s.src = mammothUrl as string s.async = true s.onload = () => resolve() s.onerror = (e) => reject(e) document.head.appendChild(s) }) } // Bootstrap app without top-level await async function bootstrap() { try { // Wait for mammoth to be available before installing the editor plugin await ensureMammoth() } catch (e) { console.error('Failed to initialize editor dependencies:', e) } // Custom component for style tags app.component('v-style', defineComponent({ render: function(): VNode { // Specify the return type as VNode return h('style', {}, this.$slots.default ? this.$slots.default() : []); // Check if $slots.default exists }, })) // Ensure clean mount - unmount any existing instance first const container = document.querySelector('#app') if (container && appInstance) { try { appInstance.unmount() } catch (e) { console.warn('Failed to unmount previous app instance:', e) } } // Mount and store the instance appInstance = app app.mount('#app') } void bootstrap(); export { router }