diff --git a/app/HomePage.tsx b/app/HomePage.tsx new file mode 100644 index 0000000..4b5db7d --- /dev/null +++ b/app/HomePage.tsx @@ -0,0 +1,5 @@ +import BookList from '@/components/book/BookList'; + +export default function HomePage() { + return ; +} diff --git a/app/book/BookLayout.tsx b/app/book/BookLayout.tsx new file mode 100644 index 0000000..0d9e48c --- /dev/null +++ b/app/book/BookLayout.tsx @@ -0,0 +1 @@ +export {default} from '@/app/book/[bookId]/layout'; diff --git a/app/book/BookPage.tsx b/app/book/BookPage.tsx new file mode 100644 index 0000000..630101c --- /dev/null +++ b/app/book/BookPage.tsx @@ -0,0 +1 @@ +export {default} from '@/app/book/[bookId]/page'; diff --git a/app/book/ChapterPage.tsx b/app/book/ChapterPage.tsx new file mode 100644 index 0000000..d3c88e3 --- /dev/null +++ b/app/book/ChapterPage.tsx @@ -0,0 +1 @@ +export {default} from '@/app/book/[bookId]/chapter/[chapterId]/page'; diff --git a/app/book/[bookId]/chapter/[chapterId]/page.tsx b/app/book/[bookId]/chapter/[chapterId]/page.tsx new file mode 100644 index 0000000..fa2a75b --- /dev/null +++ b/app/book/[bookId]/chapter/[chapterId]/page.tsx @@ -0,0 +1,77 @@ +'use client'; +import React, {useContext, useEffect, useRef} from 'react'; +import {useParams} from '@/lib/navigation'; +import {ChapterContext, ChapterContextProps} from '@/context/ChapterContext'; +import {SessionContext, SessionContextProps} from '@/context/SessionContext'; +import {LangContext, LangContextProps} from '@/context/LangContext'; +import {AlertContext, AlertContextProps} from '@/context/AlertContext'; +import {apiGet} from '@/lib/api/client'; +import {isDesktop} from '@/lib/configs'; +import * as tauri from '@/lib/tauri'; +import OfflineContext, {OfflineContextType} from '@/context/OfflineContext'; +import {BookContext, BookContextProps} from '@/context/BookContext'; +import {ChapterProps} from '@/lib/types/chapter'; +import {useTranslations} from '@/lib/i18n'; +import TextEditor from '@/components/editor/TextEditor'; + +export default function ChapterPage() { + const params: { bookId: string; chapterId: string } = useParams<{ bookId: string; chapterId: string }>(); + const {chapter, setChapter}: ChapterContextProps = useContext(ChapterContext); + const {session}: SessionContextProps = useContext(SessionContext); + const {lang}: LangContextProps = useContext(LangContext); + const {errorMessage}: AlertContextProps = useContext(AlertContext); + const {book}: BookContextProps = useContext(BookContext); + const {isCurrentlyOffline}: OfflineContextType = useContext(OfflineContext); + const t = useTranslations(); + const hasFetched = useRef(''); + + useEffect((): void => { + if (!session.accessToken || !params.chapterId) return; + if (chapter && chapter.chapterId === params.chapterId) { + hasFetched.current = params.chapterId; + return; + } + if (hasFetched.current === params.chapterId) return; + hasFetched.current = params.chapterId; + fetchChapter().then(); + }, [params.chapterId, session.accessToken, chapter]); + + async function fetchChapter(): Promise { + try { + const isFirstLoad: boolean = !chapter; + let response: ChapterProps | null; + if (isDesktop && (isCurrentlyOffline() || book?.localBook)) { + if (isFirstLoad) { + response = await tauri.getLastChapter(params.bookId); + } else { + response = await tauri.getWholeChapter(params.chapterId, chapter!.chapterContent.version, params.bookId); + } + } else { + const endpoint: string = isFirstLoad ? 'chapter/last-chapter' : 'chapter/whole'; + const queryParams: Record = isFirstLoad + ? {bookid: params.bookId} + : {bookid: params.bookId, id: params.chapterId, version: chapter!.chapterContent.version}; + response = await apiGet( + endpoint, session.accessToken, lang, queryParams + ); + } + if (!response) { + errorMessage(t('scribeChapterComponent.errorFetchChapter')); + return; + } + setChapter(response); + } catch (e: unknown) { + if (e instanceof Error) { + errorMessage(e.message); + } else { + errorMessage(t('scribeChapterComponent.errorFetchChapter')); + } + } + } + + if (!chapter || chapter.chapterId !== params.chapterId) { + return null; + } + + return ; +} diff --git a/app/book/[bookId]/layout.tsx b/app/book/[bookId]/layout.tsx new file mode 100644 index 0000000..300cb00 --- /dev/null +++ b/app/book/[bookId]/layout.tsx @@ -0,0 +1,86 @@ +'use client'; +import React, {ReactNode, useContext, useEffect} from 'react'; +import {useParams} from '@/lib/navigation'; +import {BookContext, BookContextProps} from '@/context/BookContext'; +import {SessionContext, SessionContextProps} from '@/context/SessionContext'; +import {LangContext, LangContextProps} from '@/context/LangContext'; +import {AlertContext, AlertContextProps} from '@/context/AlertContext'; +import {apiGet} from '@/lib/api/client'; +import {isDesktop} from '@/lib/configs'; +import * as tauri from '@/lib/tauri'; +import OfflineContext, {OfflineContextType} from '@/context/OfflineContext'; +import {BookProps} from '@/lib/types/book'; +import {useTranslations} from '@/lib/i18n'; +import {BooksSyncContext, BooksSyncContextProps} from '@/context/BooksSyncContext'; +import {SyncedBook} from '@/lib/types/synced-book'; + +export default function BookLayout({children}: { children: ReactNode }) { + const params: { bookId: string } = useParams<{ bookId: string }>(); + const {book, setBook}: BookContextProps = useContext(BookContext); + const {session}: SessionContextProps = useContext(SessionContext); + const {lang}: LangContextProps = useContext(LangContext); + const {errorMessage}: AlertContextProps = useContext(AlertContext); + const {isCurrentlyOffline}: OfflineContextType = useContext(OfflineContext); + const {localOnlyBooks}: BooksSyncContextProps = useContext(BooksSyncContext); + const t = useTranslations(); + + useEffect((): void => { + if (session.accessToken && params.bookId) { + if (!book || book.bookId !== params.bookId) { + fetchBook().then(); + } + } + }, [params.bookId, session.accessToken]); + + async function fetchBook(): Promise { + try { + let localBookOnly: boolean = false; + let bookResponse: BookProps | null = null; + + if (isCurrentlyOffline()) { + bookResponse = await tauri.getBookBasicInformation(params.bookId); + if (bookResponse) localBookOnly = true; + } else { + const isOfflineBook = localOnlyBooks.find((b: SyncedBook): boolean => b.id === params.bookId); + if (isOfflineBook) { + bookResponse = await tauri.getBookBasicInformation(params.bookId); + localBookOnly = true; + } + if (!bookResponse) { + bookResponse = await apiGet( + 'book/basic-information', session.accessToken, lang, {id: params.bookId} + ); + } + } + + if (!bookResponse) { + errorMessage(t('controllerBar.bookNotFound')); + return; + } + setBook({ + bookId: bookResponse.bookId, + type: bookResponse.type, + title: bookResponse.title, + subTitle: bookResponse.subTitle, + summary: bookResponse.summary, + publicationDate: bookResponse.publicationDate, + desiredWordCount: bookResponse.desiredWordCount, + totalWordCount: bookResponse.totalWordCount ?? 0, + quillsenseEnabled: bookResponse.quillsenseEnabled, + tools: bookResponse.tools, + seriesId: bookResponse.seriesId, + serie: bookResponse.serie, + coverImage: bookResponse.coverImage ? 'data:image/jpeg;base64,' + bookResponse.coverImage : '', + localBook: localBookOnly, + }); + } catch (e: unknown) { + if (e instanceof Error) { + errorMessage(e.message); + } else { + errorMessage(t('controllerBar.unknownBookError')); + } + } + } + + return <>{children}; +} diff --git a/app/book/[bookId]/page.tsx b/app/book/[bookId]/page.tsx new file mode 100644 index 0000000..8505a6a --- /dev/null +++ b/app/book/[bookId]/page.tsx @@ -0,0 +1,67 @@ +'use client'; +import React, {useContext, useEffect, useState} from 'react'; +import {useParams, useRouter} from '@/lib/navigation'; +import {SessionContext, SessionContextProps} from '@/context/SessionContext'; +import {LangContext, LangContextProps} from '@/context/LangContext'; +import {AlertContext, AlertContextProps} from '@/context/AlertContext'; +import {ChapterContext, ChapterContextProps} from '@/context/ChapterContext'; +import {apiGet} from '@/lib/api/client'; +import {isDesktop} from '@/lib/configs'; +import * as tauri from '@/lib/tauri'; +import OfflineContext, {OfflineContextType} from '@/context/OfflineContext'; +import {BookContext, BookContextProps} from '@/context/BookContext'; +import {ChapterProps} from '@/lib/types/chapter'; +import {useTranslations} from '@/lib/i18n'; +import NoBookHome from '@/components/editor/NoBookHome'; + +export default function BookPage() { + const params: { bookId: string } = useParams<{ bookId: string }>(); + const router = useRouter(); + const {session}: SessionContextProps = useContext(SessionContext); + const {lang}: LangContextProps = useContext(LangContext); + const {errorMessage}: AlertContextProps = useContext(AlertContext); + const {setChapter}: ChapterContextProps = useContext(ChapterContext); + const {book}: BookContextProps = useContext(BookContext); + const {isCurrentlyOffline}: OfflineContextType = useContext(OfflineContext); + const t = useTranslations(); + + const [isRedirecting, setIsRedirecting] = useState(true); + + useEffect((): void => { + if (session.accessToken && params.bookId) { + redirectToLastChapter().then(); + } + }, [session.accessToken, params.bookId]); + + async function redirectToLastChapter(): Promise { + try { + let response: ChapterProps | null; + if (isDesktop && (isCurrentlyOffline() || book?.localBook)) { + response = await tauri.getLastChapter(params.bookId); + } else { + response = await apiGet( + 'chapter/last-chapter', session.accessToken, lang, {bookid: params.bookId} + ); + } + if (response) { + setChapter(response); + router.replace(`/book/${params.bookId}/chapter/${response.chapterId}`); + return; + } + setIsRedirecting(false); + } catch (e: unknown) { + if (e instanceof Error) { + errorMessage(e.message); + } else { + errorMessage(t('homePage.errors.lastChapterError')); + } + setIsRedirecting(false); + } + } + + if (isRedirecting) { + return null; + } + + return ; +} diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000..b113666 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1 @@ +export {default} from './login/page'; diff --git a/app/main.tsx b/app/main.tsx new file mode 100644 index 0000000..390f38d --- /dev/null +++ b/app/main.tsx @@ -0,0 +1,137 @@ +import React, {useEffect, useState} from 'react'; +import ReactDOM from 'react-dom/client'; +import {BrowserRouter, Routes, Route, Outlet} from 'react-router-dom'; +import '@/app/globals.css'; +import '@/lib/i18n'; +import {listen} from '@tauri-apps/api/event'; +import * as tauri from '@/lib/tauri'; +import PulseLoader from '@/components/ui/PulseLoader'; +import {useTranslations} from '@/lib/i18n'; + +listen('auth-success', () => window.location.reload()); + +type MigrationState = 'pending' | 'error' | 'done'; + +function AppInitializer({children}: { children: React.ReactNode }) { + const t = useTranslations(); + const [state, setState] = useState('pending'); + const [retryCount, setRetryCount] = useState(0); + + useEffect(function (): void { + setState('pending'); + tauri.autoMigrateElectron().then(function (result): void { + if (result.migrated) { + window.location.reload(); + } else if (result.error) { + setState('error'); + } else { + setState('done'); + } + }).catch(function (): void { + setState('error'); + }); + }, [retryCount]); + + if (state === 'pending') { + return ( +
+ +
+ ); + } + + if (state === 'error') { + return ( +
+
+

{t('migration.autoErrorTitle')}

+

{t('migration.autoErrorText')}

+
+
+ +
+ +

{t('migration.autoErrorContinueWarning')}

+
+
+
+ ); + } + + return <>{children}; +} + +import ScribeShell from '@/components/layout/ScribeShell'; +import LoginWrapper from '@/app/login/LoginWrapper'; + +import HomePage from '@/app/HomePage'; +import BookPage from '@/app/book/BookPage'; +import ChapterPage from '@/app/book/ChapterPage'; +import BookLayout from '@/app/book/BookLayout'; + +import LoginPage from '@/app/login/login/page'; +import RegisterPage from '@/app/login/register/page'; +import ResetPasswordPage from '@/app/login/reset-password/page'; +import OfflineLoginPage from '@/app/login/offline/page'; + +function LoginShell() { + return ( + + + + ); +} + +function MainShell() { + return ( + + + + ); +} + +function BookShell() { + return ( + + + + ); +} + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + {/* Login routes — dedicated window, no ScribeShell */} + }> + }/> + }/> + }/> + }/> + }/> + + + {/* Main app routes — with ScribeShell */} + }> + }/> + }> + }/> + }/> + + + + + + +); diff --git a/components/layout/ScribeShell.tsx b/components/layout/ScribeShell.tsx index 4dbdaec..7f49aa4 100644 --- a/components/layout/ScribeShell.tsx +++ b/components/layout/ScribeShell.tsx @@ -38,7 +38,6 @@ import OfflineProvider from '@/context/OfflineProvider'; import OfflineContext, {OfflineContextType} from '@/context/OfflineContext'; import OfflinePinSetup from '@/components/offline/OfflinePinSetup'; import OfflinePinVerify from '@/components/offline/OfflinePinVerify'; -import MigrationModal from '@/components/migration/MigrationModal'; import {isDesktop} from '@/lib/configs'; import * as tauri from '@/lib/tauri'; import useSyncBooks from '@/hooks/useSyncBooks'; @@ -137,16 +136,6 @@ function ScribeContent({children}: { children: ReactNode }) { const [currentChapter, setCurrentChapter] = useState(undefined); const [currentBook, setCurrentBook] = useState(null); const [bookSettingId, setBookSettingId] = useState(''); - const [showMigrationPopup, setShowMigrationPopup] = useState(false); - - useEffect(function (): void { - if (!isDesktop) return; - const done: boolean = localStorage.getItem('electron_migration_done') === 'true'; - const dismissed: boolean = localStorage.getItem('electron_migration_dismissed') === 'true'; - if (!done && !dismissed) { - setShowMigrationPopup(true); - } - }, []); const [serverSyncedBooks, setServerSyncedBooks] = useState([]); const [localSyncedBooks, setLocalSyncedBooks] = useState([]); @@ -437,12 +426,6 @@ function ScribeContent({children}: { children: ReactNode }) { onCancel={(): void => {}} /> )} - {showMigrationPopup && ( - - )} diff --git a/components/layout/ScribeTopBar.tsx b/components/layout/ScribeTopBar.tsx index 9a0adad..91aee04 100644 --- a/components/layout/ScribeTopBar.tsx +++ b/components/layout/ScribeTopBar.tsx @@ -1,11 +1,14 @@ const logo = "/eritors-favicon-white.png"; import React, {useContext} from "react"; +import {Download, X} from "lucide-react"; import {BookContext, BookContextProps} from "@/context/BookContext"; import {useTranslations} from '@/lib/i18n'; +import {useAutoUpdate} from "@/hooks/useAutoUpdate"; export default function ScribeTopBar() { const {book}: BookContextProps = useContext(BookContext); const t = useTranslations(); + const update = useAutoUpdate(); return (
@@ -33,6 +36,21 @@ export default function ScribeTopBar() {
)}
+ {update.available && ( +
+ v{update.version} + + {!update.downloading && ( + + )} +
+ )}
) diff --git a/components/rightbar/ComposerRightBar.tsx b/components/rightbar/ComposerRightBar.tsx index 0b2fa03..f171a32 100644 --- a/components/rightbar/ComposerRightBar.tsx +++ b/components/rightbar/ComposerRightBar.tsx @@ -1,5 +1,5 @@ 'use client' -import {ArrowRightLeft, ExternalLink, Feather, Globe, Info, MapPin, MessageCircle, Users, Wand2, X} from 'lucide-react'; +import {ExternalLink, Feather, Globe, Info, MapPin, MessageCircle, Users, Wand2, X} from 'lucide-react'; import React, {lazy, Suspense, useContext, useEffect, useState} from "react"; import {BookContext, BookContextProps} from "@/context/BookContext"; import {PanelComponent} from "@/lib/types/editor"; @@ -12,8 +12,6 @@ import InsetPanel from "@/components/ui/InsetPanel"; import IconButton from "@/components/ui/IconButton"; import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; import {isDesktop} from '@/lib/configs'; -import MigrationModal from '@/components/migration/MigrationModal'; - // Lazy loaded Editor components const WorldEditor = lazy(function () { return import('@/components/book/settings/world/editor/WorldEditor'); @@ -54,11 +52,6 @@ export default function ComposerRightBar(): React.JSX.Element { const [panelHidden, setPanelHidden] = useState(false); const [currentPanel, setCurrentPanel] = useState(); const [showAbout, setShowAbout] = useState(false); - const [showMigration, setShowMigration] = useState(false); - - const migrationDone: boolean = localStorage.getItem('electron_migration_done') === 'true'; - const migrationDismissed: boolean = localStorage.getItem('electron_migration_dismissed') === 'true'; - const showMigrationButton: boolean = isDesktop && !migrationDone; function togglePanel(component: PanelComponent): void { if (panelHidden) { @@ -110,16 +103,6 @@ export default function ComposerRightBar(): React.JSX.Element { ]; const homeComponents: PanelComponent[] = [ - ...(showMigrationButton ? [{ - id: 0, - title: t("composerRightBar.homeComponents.migration.title"), - description: t("composerRightBar.homeComponents.migration.description"), - badge: 'IMPORT', - icon: ArrowRightLeft, - action: function (): void { - setShowMigration(true); - } - }] : []), { id: 1, title: t("composerRightBar.homeComponents.about.title"), @@ -241,10 +224,6 @@ export default function ComposerRightBar(): React.JSX.Element { {showAbout && } - {showMigration && } ); } diff --git a/lib/api/client.ts b/lib/api/client.ts index f85ed98..0d57454 100644 --- a/lib/api/client.ts +++ b/lib/api/client.ts @@ -25,7 +25,11 @@ async function handleResponse(response: Response): Promise { const body = await response.json().catch(() => ({message: response.statusText})); throw new ApiError(body.message || body || response.statusText, response.status); } - return response.json() as Promise; + const contentType = response.headers.get('content-type') ?? ''; + if (contentType.includes('application/json')) { + return response.json() as Promise; + } + return response.text() as unknown as Promise; } export async function apiGet(url: string, auth: string, lang: string = "fr", params: Record = {}): Promise { diff --git a/lib/locales/en.json b/lib/locales/en.json index b650194..b4cb773 100644 --- a/lib/locales/en.json +++ b/lib/locales/en.json @@ -55,7 +55,9 @@ "scribeTopBar": { "logoAlt": "Logo", "scribe": "Scribe", - "separator": " - " + "separator": " - ", + "update": "Update", + "updating": "Updating..." }, "scribeLeftBar": { "editorComponents": { @@ -217,7 +219,13 @@ "retry": "Retry", "fileNotFound": "The migration file was not found at this path.", "dbNotFound": "The database was not found next to the migration file.", - "importFailed": "Import failed. Please check that the files are valid." + "importFailed": "Import failed. Please check that the files are valid.", + "autoMigrating": "Migrating your data...", + "autoErrorTitle": "Automatic migration failed", + "autoErrorText": "Your data from the previous version could not be recovered.", + "autoErrorContinue": "Continue without migrating", + "autoErrorContinueWarning": "Warning: your local data will not be recovered.", + "autoErrorRetry": "Retry" }, "quillSense": { "needSubscription": "Please subscribe to QuillSense or bring your keys to access this feature.", diff --git a/lib/locales/fr.json b/lib/locales/fr.json index 047579d..9f4759f 100644 --- a/lib/locales/fr.json +++ b/lib/locales/fr.json @@ -55,7 +55,9 @@ "scribeTopBar": { "logoAlt": "Logo", "scribe": "Scribe", - "separator": " - " + "separator": " - ", + "update": "Mettre à jour", + "updating": "Mise à jour..." }, "scribeLeftBar": { "editorComponents": { @@ -217,7 +219,13 @@ "retry": "Réessayer", "fileNotFound": "Le fichier de migration est introuvable à ce chemin.", "dbNotFound": "La base de données n'a pas été trouvée à côté du fichier de migration.", - "importFailed": "L'importation a échoué. Vérifiez que les fichiers sont valides." + "importFailed": "L'importation a échoué. Vérifiez que les fichiers sont valides.", + "autoMigrating": "Migration des données en cours...", + "autoErrorTitle": "La migration automatique a échoué", + "autoErrorText": "Vos données de l'ancienne version n'ont pas pu être récupérées.", + "autoErrorContinue": "Continuer sans migrer", + "autoErrorContinueWarning": "Attention : vos données locales ne seront pas récupérées.", + "autoErrorRetry": "Réessayer" }, "quillSense": { "needSubscription": "Veuillez vous abonner à QuillSense ou Amenez vos clés pour accéder à cette fonctionnalité.", diff --git a/lib/tauri.ts b/lib/tauri.ts index 8e61543..122a22f 100644 --- a/lib/tauri.ts +++ b/lib/tauri.ts @@ -700,6 +700,20 @@ export async function importFromElectron(migrationFilePath: string): Promise('import_from_electron', {data: {migrationFilePath}}); } +export interface AutoMigrationResult { + migrated: boolean; + userId: string | null; + tokenMigrated: boolean; + keyMigrated: boolean; + pinMigrated: boolean; + dbMigrated: boolean; + error: string | null; +} + +export async function autoMigrateElectron(): Promise { + return invoke('auto_migrate_electron'); +} + // ─── Window Management ────────────────────────────────────── let loginWindowOpening = false; diff --git a/package-lock.json b/package-lock.json index 9165993..172892d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,9 @@ "@tailwindcss/postcss": "^4.1.17", "@tauri-apps/api": "^2.10.1", "@tauri-apps/plugin-http": "^2.5.8", + "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-shell": "^2.3.5", + "@tauri-apps/plugin-updater": "^2.10.1", "@tiptap/extension-color": "^3.10.7", "@tiptap/extension-gapcursor": "^3.10.7", "@tiptap/extension-highlight": "^3.10.7", @@ -1013,6 +1015,15 @@ "@tauri-apps/api": "^2.10.1" } }, + "node_modules/@tauri-apps/plugin-process": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-process/-/plugin-process-2.3.1.tgz", + "integrity": "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, "node_modules/@tauri-apps/plugin-shell": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.5.tgz", @@ -1022,6 +1033,15 @@ "@tauri-apps/api": "^2.10.1" } }, + "node_modules/@tauri-apps/plugin-updater": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.10.1.tgz", + "integrity": "sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, "node_modules/@tiptap/core": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.19.0.tgz", diff --git a/package.json b/package.json index 4344710..07cfd98 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "eritorsscribe", "productName": "ERitors Scribe", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "scripts": { "dev": "vite --port 4000", @@ -32,7 +32,9 @@ "@tailwindcss/postcss": "^4.1.17", "@tauri-apps/api": "^2.10.1", "@tauri-apps/plugin-http": "^2.5.8", + "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-shell": "^2.3.5", + "@tauri-apps/plugin-updater": "^2.10.1", "@tiptap/extension-color": "^3.10.7", "@tiptap/extension-gapcursor": "^3.10.7", "@tiptap/extension-highlight": "^3.10.7", diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 651555a..9478a8a 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -23,6 +23,8 @@ pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_http::init()) + .plugin(tauri_plugin_updater::Builder::new().build()) + .plugin(tauri_plugin_process::init()) .manage(db_manager) .manage(session) .invoke_handler(tauri::generate_handler![ @@ -172,7 +174,8 @@ pub fn run() { domains::tombstone::commands::get_tombstones_since, domains::tombstone::commands::apply_book_tombstones, domains::tombstone::commands::apply_series_tombstones, - // ─── Migration ────────────��─────────────────── + // ─── Migration ───────────────────────────────── + domains::migration::commands::auto_migrate_electron, domains::migration::commands::check_electron_migration, domains::migration::commands::import_from_electron, ])