mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-15 09:14:08 +00:00
Mail DEFAULT_DRIVER changes from smtp to sendmail; DRIVER_ORDER is reshuffled so sendmail is the head of the list on fresh installs. This matches what most self-hosted installs already have working out of the box — SMTP requires provider credentials the typical user doesn't have set up yet. The mail config description is rewritten to drop the 'Laravel' framework reference and to explicitly tell unsure users to leave it on sendmail.
SiteApi::get() now catches GuzzleException (the broader interface) and returns null on network failure instead of bubbling the exception object — callers were treating a non-array return as 'marketplace unavailable' anyway, so null is the correct shape.
main.ts exposes the Vue runtime on window.__invoiceshelf_vue so module JS (compiled against the host's Vue install) can call createApp / defineComponent without re-bundling Vue. invoiceshelf.css adds Tailwind source globs for Modules/**/*.{js,ts,vue,blade.php} so module-contributed classes are picked up by the host CSS pipeline.
Installation wizard PreferencesView was already in the tree waiting for the API field rename (date_formats, time_zones, fiscal_years, languages) that landed in setting.service.ts; this commit catches both sides up together.
125 lines
3.4 KiB
TypeScript
125 lines
3.4 KiB
TypeScript
import { createApp } from 'vue'
|
|
import type { App } from 'vue'
|
|
import type { Router } from 'vue-router'
|
|
import App_ from './App.vue'
|
|
import router from './router'
|
|
import { createAppI18n, setI18nLanguage } from './plugins/i18n'
|
|
import type { AppI18n } from './plugins/i18n'
|
|
import { createAppPinia } from './plugins/pinia'
|
|
import { installTooltipDirective } from './plugins/tooltip'
|
|
import { defineGlobalComponents } from './global-components'
|
|
|
|
/**
|
|
* Callback signature for the `booting` hook.
|
|
* Receives the Vue app instance and the router so that modules /
|
|
* plugins can register additional routes, components, or providers.
|
|
*/
|
|
type BootCallback = (app: App, router: Router) => void
|
|
|
|
/**
|
|
* Bootstrap class for InvoiceShelf.
|
|
*
|
|
* External code (e.g. dynamically loaded modules) can call
|
|
* `window.InvoiceShelf.booting(callback)` to hook into the app
|
|
* before it mounts.
|
|
*
|
|
* Call `start()` to install all plugins, execute boot callbacks,
|
|
* and mount the application.
|
|
*/
|
|
export default class InvoiceShelf {
|
|
private bootingCallbacks: BootCallback[] = []
|
|
private messages: Record<string, Record<string, unknown>> = {}
|
|
private i18n: AppI18n | null = null
|
|
private app: App
|
|
|
|
constructor() {
|
|
this.app = createApp(App_)
|
|
}
|
|
|
|
/**
|
|
* Register a callback that will be invoked before the app mounts.
|
|
*/
|
|
booting(callback: BootCallback): void {
|
|
this.bootingCallbacks.push(callback)
|
|
}
|
|
|
|
/**
|
|
* Merge additional i18n message bundles (typically from modules).
|
|
*/
|
|
addMessages(moduleMessages: Record<string, Record<string, unknown>>): void {
|
|
for (const [locale, msgs] of Object.entries(moduleMessages)) {
|
|
this.messages[locale] = {
|
|
...this.messages[locale],
|
|
...msgs,
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dynamically load and activate a language.
|
|
*/
|
|
async loadLanguage(locale: string): Promise<void> {
|
|
if (this.i18n) {
|
|
await setI18nLanguage(this.i18n, locale)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute all registered boot callbacks, install plugins,
|
|
* and mount the app to `document.body`.
|
|
*/
|
|
async start(): Promise<void> {
|
|
// Execute boot callbacks so modules can register routes / components
|
|
this.executeCallbacks()
|
|
|
|
// Register global components
|
|
defineGlobalComponents(this.app)
|
|
|
|
// i18n
|
|
this.i18n = createAppI18n(this.messages)
|
|
|
|
// If the install wizard's Language step set a locale before the DB
|
|
// existed, honor it now so the rest of the wizard renders in the right
|
|
// language. Falls through to 'en' silently on any failure.
|
|
const installLanguage = this.readInstallLanguage()
|
|
if (installLanguage && installLanguage !== 'en') {
|
|
try {
|
|
await setI18nLanguage(this.i18n, installLanguage)
|
|
} catch {
|
|
// Locale file missing or load failed — fall back to en, no-op.
|
|
}
|
|
}
|
|
|
|
// Install plugins
|
|
this.app.use(router)
|
|
this.app.use(this.i18n)
|
|
this.app.use(createAppPinia())
|
|
|
|
// Directives
|
|
installTooltipDirective(this.app)
|
|
|
|
// Mount
|
|
this.app.mount('body')
|
|
}
|
|
|
|
// ---- private ----
|
|
|
|
private executeCallbacks(): void {
|
|
for (const callback of this.bootingCallbacks) {
|
|
callback(this.app, router)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read the install-wizard language choice from localStorage. Wrapped in
|
|
* try/catch because localStorage can throw in private-browsing edge cases.
|
|
*/
|
|
private readInstallLanguage(): string | null {
|
|
try {
|
|
return localStorage.getItem('install_language')
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
}
|