src/main.ts
Rama main · réplica de lectura del repositorio original.
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<typeof createApp> | 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<void> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((window as any).mammoth) return Promise.resolve()
return new Promise<void>((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 }