- Add multi-language support for registration and user menu components
- Refactor `TextEditor` to include book-closing functionality with updated toolbar buttons - Replace `generateHTML` with a lightweight custom TipTap-to-HTML renderer - Update and lock `esbuild` and related dependencies to latest versions
This commit is contained in:
@@ -162,6 +162,8 @@ function ScribeContent() {
|
|||||||
useEffect((): void => {
|
useEffect((): void => {
|
||||||
if (currentBook) {
|
if (currentBook) {
|
||||||
getLastChapter().then();
|
getLastChapter().then();
|
||||||
|
} else {
|
||||||
|
getBooks().then();
|
||||||
}
|
}
|
||||||
}, [currentBook]);
|
}, [currentBook]);
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import {useContext, useEffect, useRef, useState} from "react";
|
|||||||
import {SessionContext} from "@/context/SessionContext";
|
import {SessionContext} from "@/context/SessionContext";
|
||||||
import NoPicture from "@/components/NoPicture";
|
import NoPicture from "@/components/NoPicture";
|
||||||
import System from "@/lib/models/System";
|
import System from "@/lib/models/System";
|
||||||
|
import {useTranslations} from "next-intl";
|
||||||
|
|
||||||
export default function UserMenu() {
|
export default function UserMenu() {
|
||||||
const {session} = useContext(SessionContext);
|
const {session} = useContext(SessionContext);
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
const profileMenuRef: React.RefObject<HTMLDivElement | null> = useRef<HTMLDivElement>(null);
|
const profileMenuRef: React.RefObject<HTMLDivElement | null> = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -32,15 +34,8 @@ export default function UserMenu() {
|
|||||||
|
|
||||||
async function handleLogout(): Promise<void> {
|
async function handleLogout(): Promise<void> {
|
||||||
System.removeCookie("token");
|
System.removeCookie("token");
|
||||||
|
|
||||||
// Si dans Electron, utiliser IPC pour logout
|
|
||||||
if (window.electron) {
|
|
||||||
await window.electron.removeToken();
|
await window.electron.removeToken();
|
||||||
window.electron.logout();
|
window.electron.logout();
|
||||||
} else {
|
|
||||||
// Fallback web
|
|
||||||
document.location.href = "https://eritors.com/login";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -64,11 +59,11 @@ export default function UserMenu() {
|
|||||||
<a href="https://eritors.com/settings"
|
<a href="https://eritors.com/settings"
|
||||||
className="group flex items-center gap-3 px-4 py-2.5 text-text-primary hover:bg-secondary/50 transition-all hover:pl-5">
|
className="group flex items-center gap-3 px-4 py-2.5 text-text-primary hover:bg-secondary/50 transition-all hover:pl-5">
|
||||||
<span
|
<span
|
||||||
className="text-sm font-medium group-hover:text-primary transition-colors">Paramètres</span>
|
className="text-sm font-medium group-hover:text-primary transition-colors">{t('userMenu.settings')}</span>
|
||||||
</a>
|
</a>
|
||||||
<a onClick={handleLogout} href="#"
|
<a onClick={handleLogout} href="#"
|
||||||
className="group flex items-center gap-3 px-4 py-2.5 text-error hover:bg-error/10 transition-all hover:pl-5 rounded-b-xl">
|
className="group flex items-center gap-3 px-4 py-2.5 text-error hover:bg-error/10 transition-all hover:pl-5 rounded-b-xl">
|
||||||
<span className="text-sm font-medium">Déconnexion</span>
|
<span className="text-sm font-medium">{t('userMenu.logout')}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import {
|
|||||||
faListOl,
|
faListOl,
|
||||||
faListUl,
|
faListUl,
|
||||||
faParagraph,
|
faParagraph,
|
||||||
faUnderline
|
faUnderline,
|
||||||
|
faXmark
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import {EditorContext} from "@/context/EditorContext";
|
import {EditorContext} from "@/context/EditorContext";
|
||||||
import {ChapterContext} from '@/context/ChapterContext';
|
import {ChapterContext} from '@/context/ChapterContext';
|
||||||
@@ -135,8 +136,8 @@ export default function TextEditor() {
|
|||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const {lang} = useContext<LangContextProps>(LangContext)
|
const {lang} = useContext<LangContextProps>(LangContext)
|
||||||
const {editor} = useContext(EditorContext);
|
const {editor} = useContext(EditorContext);
|
||||||
const {chapter} = useContext(ChapterContext);
|
const {chapter, setChapter} = useContext(ChapterContext);
|
||||||
const {book} = useContext(BookContext);
|
const {book, setBook} = useContext(BookContext);
|
||||||
const {errorMessage, successMessage} = useContext(AlertContext);
|
const {errorMessage, successMessage} = useContext(AlertContext);
|
||||||
const {session} = useContext(SessionContext);
|
const {session} = useContext(SessionContext);
|
||||||
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
|
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
|
||||||
@@ -146,6 +147,7 @@ export default function TextEditor() {
|
|||||||
const [showGhostWriter, setShowGhostWriter] = useState<boolean>(false);
|
const [showGhostWriter, setShowGhostWriter] = useState<boolean>(false);
|
||||||
const [showUserSettings, setShowUserSettings] = useState<boolean>(false);
|
const [showUserSettings, setShowUserSettings] = useState<boolean>(false);
|
||||||
const [isSaving, setIsSaving] = useState<boolean>(false);
|
const [isSaving, setIsSaving] = useState<boolean>(false);
|
||||||
|
const [isClosing, setIsClosing] = useState<boolean>(false);
|
||||||
const [editorSettings, setEditorSettings] = useState<EditorDisplaySettings>(DEFAULT_EDITOR_SETTINGS);
|
const [editorSettings, setEditorSettings] = useState<EditorDisplaySettings>(DEFAULT_EDITOR_SETTINGS);
|
||||||
const [editorClasses, setEditorClasses] = useState<EditorClasses>({
|
const [editorClasses, setEditorClasses] = useState<EditorClasses>({
|
||||||
base: 'text-lg font-serif leading-normal',
|
base: 'text-lg font-serif leading-normal',
|
||||||
@@ -352,6 +354,14 @@ export default function TextEditor() {
|
|||||||
setShowGhostWriter(false);
|
setShowGhostWriter(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleCloseBook: () => Promise<void> = useCallback(async (): Promise<void> => {
|
||||||
|
setIsClosing(true);
|
||||||
|
await saveContent();
|
||||||
|
setBook && setBook(null);
|
||||||
|
setChapter && setChapter(undefined);
|
||||||
|
setIsClosing(false);
|
||||||
|
}, [saveContent, setBook, setChapter]);
|
||||||
|
|
||||||
useEffect((): void => {
|
useEffect((): void => {
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
|
|
||||||
@@ -454,25 +464,25 @@ export default function TextEditor() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-1 w-full h-full">
|
<div className="flex flex-col flex-1 w-full h-full">
|
||||||
<div
|
<div
|
||||||
className={`flex justify-between gap-3 border-b border-secondary/30 px-4 py-3 bg-gradient-to-b from-dark-background/80 to-dark-background/50 backdrop-blur-sm transition-opacity duration-300 shadow-md ${editorSettings.focusMode ? 'opacity-70 hover:opacity-100' : ''}`}>
|
className={`flex justify-between gap-2 lg:gap-3 border-b border-secondary/30 px-2 lg:px-4 py-2 lg:py-3 bg-gradient-to-b from-dark-background/80 to-dark-background/50 backdrop-blur-sm transition-opacity duration-300 shadow-md overflow-x-auto ${editorSettings.focusMode ? 'opacity-70 hover:opacity-100' : ''}`}>
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex gap-1 flex-shrink-0">
|
||||||
{toolbarButtons.map((button: ToolbarButton, index: number) => (
|
{toolbarButtons.map((button: ToolbarButton, index: number) => (
|
||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
onClick={button.action}
|
onClick={button.action}
|
||||||
className={`group flex items-center px-3 py-2 rounded-lg transition-all duration-200 ${button.isActive ? 'bg-primary text-text-primary shadow-md shadow-primary/30 scale-105' : 'text-muted hover:text-text-primary hover:bg-secondary/50 hover:shadow-sm hover:scale-105'}`}
|
className={`group flex items-center px-2 lg:px-3 py-1.5 lg:py-2 rounded-lg transition-all duration-200 flex-shrink-0 ${button.isActive ? 'bg-primary text-text-primary shadow-md shadow-primary/30 scale-105' : 'text-muted hover:text-text-primary hover:bg-secondary/50 hover:shadow-sm hover:scale-105'}`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={button.icon} className={'w-4 h-4 transition-transform duration-200 group-hover:scale-110'}/>
|
<FontAwesomeIcon icon={button.icon} className={'w-3.5 h-3.5 lg:w-4 lg:h-4 transition-transform duration-200 group-hover:scale-110'}/>
|
||||||
{
|
{
|
||||||
button.label &&
|
button.label &&
|
||||||
<span className="ml-2 text-sm font-medium">
|
<span className="ml-1 lg:ml-2 text-xs lg:text-sm font-medium">
|
||||||
{t(`textEditor.toolbar.${button.label}`)}
|
{t(`textEditor.toolbar.${button.label}`)}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
<CollapsableButton
|
<CollapsableButton
|
||||||
showCollapsable={showUserSettings}
|
showCollapsable={showUserSettings}
|
||||||
text={t("textEditor.preferences")}
|
text={t("textEditor.preferences")}
|
||||||
@@ -495,13 +505,30 @@ export default function TextEditor() {
|
|||||||
icon={faLayerGroup}
|
icon={faLayerGroup}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<SubmitButtonWLoading
|
<button
|
||||||
callBackAction={saveContent}
|
onClick={saveContent}
|
||||||
isLoading={isSaving}
|
disabled={isSaving}
|
||||||
text={t("textEditor.save")}
|
className={`group py-2.5 px-3 lg:px-5 rounded-lg font-semibold transition-all flex items-center justify-center gap-2 relative overflow-hidden ${
|
||||||
loadingText={t("textEditor.saving")}
|
isSaving
|
||||||
icon={faFloppyDisk}
|
? 'bg-secondary cursor-not-allowed opacity-75'
|
||||||
/>
|
: 'bg-secondary/80 hover:bg-secondary shadow-md hover:shadow-lg hover:shadow-primary/20 hover:scale-105 border border-secondary/50 hover:border-primary/30'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faFloppyDisk} className="w-4 h-4 transition-transform group-hover:scale-110 text-primary" />
|
||||||
|
<span className="hidden lg:inline text-sm text-primary">{isSaving ? t("textEditor.saving") : t("textEditor.save")}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleCloseBook}
|
||||||
|
disabled={isClosing}
|
||||||
|
className={`group py-2.5 px-3 lg:px-5 rounded-lg font-semibold transition-all flex items-center justify-center gap-2 relative overflow-hidden ${
|
||||||
|
isClosing
|
||||||
|
? 'bg-secondary/30 cursor-not-allowed opacity-75'
|
||||||
|
: 'bg-secondary/30 text-muted hover:text-text-primary hover:bg-secondary hover:shadow-sm hover:scale-105'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faXmark} className="w-4 h-4 transition-transform duration-200 group-hover:scale-110" />
|
||||||
|
<span className="hidden lg:inline text-sm">{t("textEditor.close")}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faLock, faWifi, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
|
import { faLock, faWifi, faEye, faEyeSlash, faSignOutAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import System from '@/lib/models/System';
|
||||||
|
|
||||||
interface OfflinePinVerifyProps {
|
interface OfflinePinVerifyProps {
|
||||||
onSuccess: (userId: string) => void;
|
onSuccess: (userId: string) => void;
|
||||||
@@ -59,6 +60,12 @@ export default function OfflinePinVerify({ onSuccess, onCancel }: OfflinePinVeri
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
System.removeCookie("token");
|
||||||
|
await window.electron.removeToken();
|
||||||
|
window.electron.logout();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background">
|
||||||
<div className="bg-tertiary rounded-2xl p-6 max-w-md w-full mx-4 shadow-2xl">
|
<div className="bg-tertiary rounded-2xl p-6 max-w-md w-full mx-4 shadow-2xl">
|
||||||
@@ -118,6 +125,14 @@ export default function OfflinePinVerify({ onSuccess, onCancel }: OfflinePinVeri
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
|
<button
|
||||||
|
onClick={handleLogout}
|
||||||
|
className="flex-1 px-4 py-2 text-error hover:bg-error/10 rounded-lg transition-all flex items-center justify-center gap-2"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faSignOutAlt} className="w-4 h-4" />
|
||||||
|
<span className="text-sm font-medium">{t('userMenu.logout')}</span>
|
||||||
|
</button>
|
||||||
{onCancel && (
|
{onCancel && (
|
||||||
<button
|
<button
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import StarterKit from '@tiptap/starter-kit'
|
|
||||||
import TextAlign from '@tiptap/extension-text-align'
|
|
||||||
import ChapterRepo, {
|
import ChapterRepo, {
|
||||||
ActChapterQuery,
|
ActChapterQuery,
|
||||||
ChapterQueryResult,
|
ChapterQueryResult,
|
||||||
@@ -11,7 +9,6 @@ import ChapterRepo, {
|
|||||||
} from "../repositories/chapter.repository.js";
|
} from "../repositories/chapter.repository.js";
|
||||||
import System from "../System.js";
|
import System from "../System.js";
|
||||||
import {getUserEncryptionKey} from "../keyManager.js";
|
import {getUserEncryptionKey} from "../keyManager.js";
|
||||||
import { generateHTML } from "@tiptap/react";
|
|
||||||
|
|
||||||
export interface ChapterContent {
|
export interface ChapterContent {
|
||||||
version: number;
|
version: number;
|
||||||
@@ -303,25 +300,92 @@ export default class Chapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static tipTapToHtml(tipTapContent: JSON): string {
|
static tipTapToHtml(tipTapContent: JSON): string {
|
||||||
const fixNode = (node: Record<string, unknown>): Record<string, unknown> => {
|
interface TipTapNode {
|
||||||
if (!node) return node;
|
type?: string;
|
||||||
|
text?: string;
|
||||||
if (node.type === 'text' && (!node.text || node.text === '')) {
|
content?: TipTapNode[];
|
||||||
node.text = '\u00A0';
|
attrs?: Record<string, unknown>;
|
||||||
|
marks?: Array<{ type: string; attrs?: Record<string, unknown> }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(node.content) && node.content.length) {
|
const escapeHtml = (text: string): string => {
|
||||||
node.content = node.content.map(fixNode);
|
return text
|
||||||
}
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
return node;
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
};
|
};
|
||||||
|
|
||||||
return generateHTML(fixNode(tipTapContent as unknown as Record<string, unknown>), [
|
const renderMarks = (text: string, marks?: Array<{ type: string; attrs?: Record<string, unknown> }>): string => {
|
||||||
StarterKit,
|
if (!marks || marks.length === 0) return escapeHtml(text);
|
||||||
TextAlign.configure({
|
|
||||||
types: ['heading', 'paragraph'],
|
let result = escapeHtml(text);
|
||||||
}),
|
marks.forEach((mark) => {
|
||||||
]);
|
switch (mark.type) {
|
||||||
|
case 'bold':
|
||||||
|
result = `<strong>${result}</strong>`;
|
||||||
|
break;
|
||||||
|
case 'italic':
|
||||||
|
result = `<em>${result}</em>`;
|
||||||
|
break;
|
||||||
|
case 'underline':
|
||||||
|
result = `<u>${result}</u>`;
|
||||||
|
break;
|
||||||
|
case 'strike':
|
||||||
|
result = `<s>${result}</s>`;
|
||||||
|
break;
|
||||||
|
case 'code':
|
||||||
|
result = `<code>${result}</code>`;
|
||||||
|
break;
|
||||||
|
case 'link':
|
||||||
|
const href = mark.attrs?.href || '#';
|
||||||
|
result = `<a href="${escapeHtml(String(href))}">${result}</a>`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderNode = (node: TipTapNode): string => {
|
||||||
|
if (!node) return '';
|
||||||
|
|
||||||
|
if (node.type === 'text') {
|
||||||
|
const textContent = node.text || '\u00A0';
|
||||||
|
return renderMarks(textContent, node.marks);
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = node.content?.map(renderNode).join('') || '';
|
||||||
|
const textAlign = node.attrs?.textAlign ? ` style="text-align: ${node.attrs.textAlign}"` : '';
|
||||||
|
|
||||||
|
switch (node.type) {
|
||||||
|
case 'doc':
|
||||||
|
return children;
|
||||||
|
case 'paragraph':
|
||||||
|
return `<p${textAlign}>${children || '\u00A0'}</p>`;
|
||||||
|
case 'heading':
|
||||||
|
const level = node.attrs?.level || 1;
|
||||||
|
return `<h${level}${textAlign}>${children}</h${level}>`;
|
||||||
|
case 'bulletList':
|
||||||
|
return `<ul>${children}</ul>`;
|
||||||
|
case 'orderedList':
|
||||||
|
return `<ol>${children}</ol>`;
|
||||||
|
case 'listItem':
|
||||||
|
return `<li>${children}</li>`;
|
||||||
|
case 'blockquote':
|
||||||
|
return `<blockquote>${children}</blockquote>`;
|
||||||
|
case 'codeBlock':
|
||||||
|
return `<pre><code>${children}</code></pre>`;
|
||||||
|
case 'hardBreak':
|
||||||
|
return '<br />';
|
||||||
|
case 'horizontalRule':
|
||||||
|
return '<hr />';
|
||||||
|
default:
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const contentNode = tipTapContent as unknown as TipTapNode;
|
||||||
|
return renderNode(contentNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {app, BrowserWindow, ipcMain, IpcMainInvokeEvent, nativeImage, protocol, safeStorage, shell} from 'electron';
|
import {app, BrowserWindow, ipcMain, IpcMainInvokeEvent, Menu, nativeImage, protocol, safeStorage, shell} from 'electron';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {fileURLToPath} from 'url';
|
import {fileURLToPath} from 'url';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
@@ -21,11 +21,11 @@ const __dirname = path.dirname(__filename);
|
|||||||
|
|
||||||
const isDev = !app.isPackaged;
|
const isDev = !app.isPackaged;
|
||||||
|
|
||||||
// Enregistrer le protocole app:// comme standard (avant app.whenReady)
|
// Enregistrer le protocole scribedesktop:// comme standard (avant app.whenReady)
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
protocol.registerSchemesAsPrivileged([
|
protocol.registerSchemesAsPrivileged([
|
||||||
{
|
{
|
||||||
scheme: 'app',
|
scheme: 'scribedesktop',
|
||||||
privileges: {
|
privileges: {
|
||||||
standard: true,
|
standard: true,
|
||||||
secure: true,
|
secure: true,
|
||||||
@@ -58,6 +58,7 @@ function createLoginWindow(): void {
|
|||||||
height: 900,
|
height: 900,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
...(process.platform !== 'darwin' && { icon: iconPath }),
|
...(process.platform !== 'darwin' && { icon: iconPath }),
|
||||||
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: preloadPath,
|
preload: preloadPath,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
@@ -73,7 +74,7 @@ function createLoginWindow(): void {
|
|||||||
loginWindow.loadURL(`http://localhost:${devPort}/login/login`);
|
loginWindow.loadURL(`http://localhost:${devPort}/login/login`);
|
||||||
loginWindow.webContents.openDevTools();
|
loginWindow.webContents.openDevTools();
|
||||||
} else {
|
} else {
|
||||||
loginWindow.loadURL('app://./login/login/index.html');
|
loginWindow.loadURL('scribedesktop://./login/login/index.html');
|
||||||
}
|
}
|
||||||
|
|
||||||
loginWindow.once('ready-to-show', () => {
|
loginWindow.once('ready-to-show', () => {
|
||||||
@@ -90,6 +91,7 @@ function createMainWindow(): void {
|
|||||||
width: 1200,
|
width: 1200,
|
||||||
height: 800,
|
height: 800,
|
||||||
...(process.platform !== 'darwin' && { icon: iconPath }),
|
...(process.platform !== 'darwin' && { icon: iconPath }),
|
||||||
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: preloadPath,
|
preload: preloadPath,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
@@ -104,7 +106,7 @@ function createMainWindow(): void {
|
|||||||
mainWindow.loadURL(`http://localhost:${devPort}`);
|
mainWindow.loadURL(`http://localhost:${devPort}`);
|
||||||
mainWindow.webContents.openDevTools();
|
mainWindow.webContents.openDevTools();
|
||||||
} else {
|
} else {
|
||||||
mainWindow.loadURL('app://./index.html');
|
mainWindow.loadURL('scribedesktop://./index.html');
|
||||||
}
|
}
|
||||||
|
|
||||||
mainWindow.once('ready-to-show', () => {
|
mainWindow.once('ready-to-show', () => {
|
||||||
@@ -345,11 +347,24 @@ ipcMain.handle('db-initialize', (_event, userId: string, encryptionKey: string)
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.whenReady().then(():void => {
|
app.whenReady().then(():void => {
|
||||||
|
// Menu minimal pour garder les raccourcis DevTools
|
||||||
|
const template: Electron.MenuItemConstructorOptions[] = [
|
||||||
|
{
|
||||||
|
label: 'View',
|
||||||
|
submenu: [
|
||||||
|
{ role: 'toggleDevTools' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const menu = Menu.buildFromTemplate(template);
|
||||||
|
Menu.setApplicationMenu(menu);
|
||||||
|
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
const outPath:string = path.join(process.resourcesPath, 'app.asar.unpacked/out');
|
const outPath:string = path.join(process.resourcesPath, 'app.asar.unpacked/out');
|
||||||
|
|
||||||
protocol.handle('app', async (request) => {
|
protocol.handle('scribedesktop', async (request) => {
|
||||||
let filePath:string = request.url.replace('app://', '').replace(/^\.\//, '');
|
let filePath:string = request.url.replace('scribedesktop://', '').replace(/^\.\//, '');
|
||||||
const fullPath:string = path.normalize(path.join(outPath, filePath));
|
const fullPath:string = path.normalize(path.join(outPath, filePath));
|
||||||
|
|
||||||
if (!fullPath.startsWith(outPath)) {
|
if (!fullPath.startsWith(outPath)) {
|
||||||
|
|||||||
@@ -44,6 +44,36 @@
|
|||||||
},
|
},
|
||||||
"backToLogin": "Back to login"
|
"backToLogin": "Back to login"
|
||||||
},
|
},
|
||||||
|
"registerStepOne": {
|
||||||
|
"fields": {
|
||||||
|
"firstName": {
|
||||||
|
"label": "First Name",
|
||||||
|
"placeholder": "Your first name"
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"label": "Last Name",
|
||||||
|
"placeholder": "Your last name"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"label": "Username",
|
||||||
|
"placeholder": "Choose a username",
|
||||||
|
"note": "Username must be at least 3 characters"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "Email Address",
|
||||||
|
"placeholder": "your.email@example.com"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"label": "Password",
|
||||||
|
"placeholder": "••••••••"
|
||||||
|
},
|
||||||
|
"repeatPassword": {
|
||||||
|
"label": "Confirm Password",
|
||||||
|
"placeholder": "••••••••"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"next": "Next"
|
||||||
|
},
|
||||||
"resetPassword": {
|
"resetPassword": {
|
||||||
"title": "Forgot password",
|
"title": "Forgot password",
|
||||||
"subtitle": "Reset your password in a few simple steps",
|
"subtitle": "Reset your password in a few simple steps",
|
||||||
@@ -429,6 +459,7 @@
|
|||||||
"draftCompanion": "Draft Companion",
|
"draftCompanion": "Draft Companion",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"saving": "Saving...",
|
"saving": "Saving...",
|
||||||
|
"close": "Close",
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"1": "1",
|
"1": "1",
|
||||||
"2": "2",
|
"2": "2",
|
||||||
@@ -901,6 +932,10 @@
|
|||||||
"close": "Close"
|
"close": "Close"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"userMenu": {
|
||||||
|
"settings": "Settings",
|
||||||
|
"logout": "Logout"
|
||||||
|
},
|
||||||
"offline": {
|
"offline": {
|
||||||
"mode": {
|
"mode": {
|
||||||
"title": "Offline Mode",
|
"title": "Offline Mode",
|
||||||
|
|||||||
@@ -44,6 +44,36 @@
|
|||||||
},
|
},
|
||||||
"backToLogin": "Retour à la connexion"
|
"backToLogin": "Retour à la connexion"
|
||||||
},
|
},
|
||||||
|
"registerStepOne": {
|
||||||
|
"fields": {
|
||||||
|
"firstName": {
|
||||||
|
"label": "Prénom",
|
||||||
|
"placeholder": "Votre prénom"
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"label": "Nom",
|
||||||
|
"placeholder": "Votre nom"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"label": "Nom d'utilisateur",
|
||||||
|
"placeholder": "Choisissez un nom d'utilisateur",
|
||||||
|
"note": "Le nom d'utilisateur doit contenir au moins 3 caractères"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "Adresse courriel",
|
||||||
|
"placeholder": "votre.courriel@exemple.com"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"label": "Mot de passe",
|
||||||
|
"placeholder": "••••••••"
|
||||||
|
},
|
||||||
|
"repeatPassword": {
|
||||||
|
"label": "Confirmer le mot de passe",
|
||||||
|
"placeholder": "••••••••"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"next": "Suivant"
|
||||||
|
},
|
||||||
"resetPassword": {
|
"resetPassword": {
|
||||||
"title": "Mot de passe oublié",
|
"title": "Mot de passe oublié",
|
||||||
"subtitle": "Réinitialisez votre mot de passe en quelques étapes simples",
|
"subtitle": "Réinitialisez votre mot de passe en quelques étapes simples",
|
||||||
@@ -429,6 +459,7 @@
|
|||||||
"draftCompanion": "Draft Companion",
|
"draftCompanion": "Draft Companion",
|
||||||
"save": "Enregistrer",
|
"save": "Enregistrer",
|
||||||
"saving": "Enregistrement en cours...",
|
"saving": "Enregistrement en cours...",
|
||||||
|
"close": "Fermer",
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"1": "1",
|
"1": "1",
|
||||||
"2": "2",
|
"2": "2",
|
||||||
@@ -902,6 +933,10 @@
|
|||||||
"close": "Fermer"
|
"close": "Fermer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"userMenu": {
|
||||||
|
"settings": "Paramètres",
|
||||||
|
"logout": "Déconnexion"
|
||||||
|
},
|
||||||
"offline": {
|
"offline": {
|
||||||
"mode": {
|
"mode": {
|
||||||
"title": "Mode Hors Ligne",
|
"title": "Mode Hors Ligne",
|
||||||
|
|||||||
966
package-lock.json
generated
966
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@
|
|||||||
"electron-builder": "^26.0.12",
|
"electron-builder": "^26.0.12",
|
||||||
"electron-rebuild": "^3.2.9",
|
"electron-rebuild": "^3.2.9",
|
||||||
"electronmon": "^2.0.4",
|
"electronmon": "^2.0.4",
|
||||||
|
"esbuild": "^0.27.2",
|
||||||
"tsx": "^4.20.6",
|
"tsx": "^4.20.6",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.2.2",
|
"vite": "^7.2.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user