- 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:
@@ -1,5 +1,3 @@
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import TextAlign from '@tiptap/extension-text-align'
|
||||
import ChapterRepo, {
|
||||
ActChapterQuery,
|
||||
ChapterQueryResult,
|
||||
@@ -11,7 +9,6 @@ import ChapterRepo, {
|
||||
} from "../repositories/chapter.repository.js";
|
||||
import System from "../System.js";
|
||||
import {getUserEncryptionKey} from "../keyManager.js";
|
||||
import { generateHTML } from "@tiptap/react";
|
||||
|
||||
export interface ChapterContent {
|
||||
version: number;
|
||||
@@ -303,25 +300,92 @@ export default class Chapter {
|
||||
}
|
||||
|
||||
static tipTapToHtml(tipTapContent: JSON): string {
|
||||
const fixNode = (node: Record<string, unknown>): Record<string, unknown> => {
|
||||
if (!node) return node;
|
||||
interface TipTapNode {
|
||||
type?: string;
|
||||
text?: string;
|
||||
content?: TipTapNode[];
|
||||
attrs?: Record<string, unknown>;
|
||||
marks?: Array<{ type: string; attrs?: Record<string, unknown> }>;
|
||||
}
|
||||
|
||||
if (node.type === 'text' && (!node.text || node.text === '')) {
|
||||
node.text = '\u00A0';
|
||||
}
|
||||
|
||||
if (Array.isArray(node.content) && node.content.length) {
|
||||
node.content = node.content.map(fixNode);
|
||||
}
|
||||
|
||||
return node;
|
||||
const escapeHtml = (text: string): string => {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
};
|
||||
|
||||
return generateHTML(fixNode(tipTapContent as unknown as Record<string, unknown>), [
|
||||
StarterKit,
|
||||
TextAlign.configure({
|
||||
types: ['heading', 'paragraph'],
|
||||
}),
|
||||
]);
|
||||
const renderMarks = (text: string, marks?: Array<{ type: string; attrs?: Record<string, unknown> }>): string => {
|
||||
if (!marks || marks.length === 0) return escapeHtml(text);
|
||||
|
||||
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 {fileURLToPath} from 'url';
|
||||
import * as fs from 'fs';
|
||||
@@ -21,11 +21,11 @@ const __dirname = path.dirname(__filename);
|
||||
|
||||
const isDev = !app.isPackaged;
|
||||
|
||||
// Enregistrer le protocole app:// comme standard (avant app.whenReady)
|
||||
// Enregistrer le protocole scribedesktop:// comme standard (avant app.whenReady)
|
||||
if (!isDev) {
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
scheme: 'app',
|
||||
scheme: 'scribedesktop',
|
||||
privileges: {
|
||||
standard: true,
|
||||
secure: true,
|
||||
@@ -58,6 +58,7 @@ function createLoginWindow(): void {
|
||||
height: 900,
|
||||
resizable: false,
|
||||
...(process.platform !== 'darwin' && { icon: iconPath }),
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
preload: preloadPath,
|
||||
contextIsolation: true,
|
||||
@@ -73,7 +74,7 @@ function createLoginWindow(): void {
|
||||
loginWindow.loadURL(`http://localhost:${devPort}/login/login`);
|
||||
loginWindow.webContents.openDevTools();
|
||||
} else {
|
||||
loginWindow.loadURL('app://./login/login/index.html');
|
||||
loginWindow.loadURL('scribedesktop://./login/login/index.html');
|
||||
}
|
||||
|
||||
loginWindow.once('ready-to-show', () => {
|
||||
@@ -90,6 +91,7 @@ function createMainWindow(): void {
|
||||
width: 1200,
|
||||
height: 800,
|
||||
...(process.platform !== 'darwin' && { icon: iconPath }),
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
preload: preloadPath,
|
||||
contextIsolation: true,
|
||||
@@ -104,7 +106,7 @@ function createMainWindow(): void {
|
||||
mainWindow.loadURL(`http://localhost:${devPort}`);
|
||||
mainWindow.webContents.openDevTools();
|
||||
} else {
|
||||
mainWindow.loadURL('app://./index.html');
|
||||
mainWindow.loadURL('scribedesktop://./index.html');
|
||||
}
|
||||
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
@@ -345,11 +347,24 @@ ipcMain.handle('db-initialize', (_event, userId: string, encryptionKey: string)
|
||||
});
|
||||
|
||||
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) {
|
||||
const outPath:string = path.join(process.resourcesPath, 'app.asar.unpacked/out');
|
||||
|
||||
protocol.handle('app', async (request) => {
|
||||
let filePath:string = request.url.replace('app://', '').replace(/^\.\//, '');
|
||||
protocol.handle('scribedesktop', async (request) => {
|
||||
let filePath:string = request.url.replace('scribedesktop://', '').replace(/^\.\//, '');
|
||||
const fullPath:string = path.normalize(path.join(outPath, filePath));
|
||||
|
||||
if (!fullPath.startsWith(outPath)) {
|
||||
|
||||
Reference in New Issue
Block a user