mirror of
https://github.com/gabriele-cusato/HandTranscriptMd.git
synced 2026-07-22 14:30:25 +00:00
aggiunte lingue, fixati bug, mancherebbe solo il bug su mobile del ridimensionamento e l'aggiunta nelle impostazioni per l'aggiunta delle keyword
This commit is contained in:
parent
4da0de3181
commit
0008e40bd2
18 changed files with 909 additions and 165 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "handwriting-to-markdown",
|
||||
"name": "Handwriting to Markdown",
|
||||
"name": "HandTranscriptMd",
|
||||
"version": "0.1.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Inline handwriting canvas with conversion to structured markdown",
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@
|
|||
handwriting Android.
|
||||
============================================= */
|
||||
|
||||
import { ItemView, WorkspaceLeaf, TFile, Notice, Platform, Modal, App } from 'obsidian';
|
||||
import { ItemView, WorkspaceLeaf, TFile, Notice, Platform, Modal, App, MarkdownView } from 'obsidian';
|
||||
import type HandwritingPlugin from './main';
|
||||
import { DrawingCanvas, Stroke } from './drawing-canvas';
|
||||
import { strokesToSvg, parseSvgStrokes } from './svg-utils';
|
||||
import { getEffectiveBgColor, getEffectiveLineColor, remapStrokeColor } from './settings';
|
||||
import { getRecognizer } from './recognizer';
|
||||
import { parseMarkdown } from './md-parser';
|
||||
import { t } from './i18n';
|
||||
|
||||
export const VIEW_TYPE_HANDWRITING = 'handwriting-editor';
|
||||
|
||||
|
|
@ -31,6 +32,7 @@ const ICONS: Record<string, string> = {
|
|||
'file-text': `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>`,
|
||||
'save': `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"/><path d="M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7"/><path d="M7 3v4a1 1 0 0 0 1 1h7"/></svg>`,
|
||||
'x': `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`,
|
||||
'file-x': `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><line x1="9.5" y1="12.5" x2="14.5" y2="17.5"/><line x1="14.5" y1="12.5" x2="9.5" y2="17.5"/></svg>`,
|
||||
'chevron-down':`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>`,
|
||||
'chevron-up': `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>`,
|
||||
'arrow-left': `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg>`,
|
||||
|
|
@ -98,34 +100,18 @@ export class DrawingEditorView extends ItemView {
|
|||
const lineColor = getEffectiveLineColor(this.plugin.settings);
|
||||
el.style.backgroundColor = bgColor;
|
||||
|
||||
// --- Top bar: ← a sinistra | toolbar a destra ---
|
||||
const topbar = el.createDiv({ cls: 'hwm_editor-topbar' });
|
||||
// --- Top bar: toolbar centrata ---
|
||||
const topbar = el.createDiv({ cls: 'hwm_editor-topbar hwm_editor-topbar--modal' });
|
||||
if (isDark) topbar.classList.add('hwm_editor-topbar--dark');
|
||||
|
||||
// Bottone ← (torna al documento)
|
||||
const backBtn = this.mkBtn(topbar, 'arrow-left', 'Torna al documento');
|
||||
backBtn.classList.add('hwm_back-btn');
|
||||
|
||||
// Toolbar (allineata a destra dentro il topbar)
|
||||
// Toolbar centrata nel topbar
|
||||
const toolbar = topbar.createDiv({ cls: 'hwm_toolbar hwm_editor-toolbar' });
|
||||
if (isDark) toolbar.classList.add('hwm_toolbar--dark');
|
||||
|
||||
// Toggle compatta (mobile)
|
||||
if (isMobile) {
|
||||
toolbar.classList.add('hwm_toolbar--compact');
|
||||
const toggleBtn = this.mkBtn(toolbar, 'chevron-down', 'Mostra tutti');
|
||||
toggleBtn.classList.add('hwm_toggle-btn');
|
||||
toggleBtn.addEventListener('click', () => {
|
||||
const compact = toolbar.classList.toggle('hwm_toolbar--compact');
|
||||
updateColorSizes(compact);
|
||||
toggleBtn.innerHTML = ICONS[compact ? 'chevron-down' : 'chevron-up'] ?? '';
|
||||
});
|
||||
}
|
||||
|
||||
// Penna / Gomma
|
||||
const penBtn = this.mkBtn(toolbar, 'pencil', 'Penna');
|
||||
const penBtn = this.mkBtn(toolbar, 'pencil', 'btn_pen');
|
||||
penBtn.classList.add('hwm_active', 'hwm_pen-btn');
|
||||
const eraserBtn = this.mkBtn(toolbar, 'eraser', 'Gomma');
|
||||
const eraserBtn = this.mkBtn(toolbar, 'eraser', 'btn_eraser');
|
||||
eraserBtn.classList.add('hwm_eraser-btn');
|
||||
toolbar.createDiv({ cls: 'hwm_separator' });
|
||||
|
||||
|
|
@ -152,16 +138,6 @@ export class DrawingEditorView extends ItemView {
|
|||
}
|
||||
toolbar.createDiv({ cls: 'hwm_separator' });
|
||||
|
||||
// Helper per dimensioni pallini in toolbar compatta
|
||||
const updateColorSizes = (compact: boolean) => {
|
||||
colorBtns.forEach(b => {
|
||||
const sz = (!compact || b.classList.contains('hwm_active')) ? '22px' : '0';
|
||||
b.style.setProperty('min-width', sz, 'important');
|
||||
b.style.setProperty('min-height', sz, 'important');
|
||||
});
|
||||
};
|
||||
if (isMobile) updateColorSizes(true);
|
||||
|
||||
// Listener bgMode: aggiorna toolbar, pallini colore e sfondo canvas al cambio tema.
|
||||
// Deve stare DOPO la dichiarazione di colorBtns per poterli aggiornare nel closure.
|
||||
const lightColors = ['#000000', '#1e40af', '#dc2626', '#16a34a'];
|
||||
|
|
@ -189,22 +165,30 @@ export class DrawingEditorView extends ItemView {
|
|||
this.plugin.bgModeListeners.add(this.bgModeListener);
|
||||
|
||||
// Undo / Redo / Clear
|
||||
const undoBtn = this.mkBtn(toolbar, 'rotate-ccw', 'Annulla');
|
||||
const undoBtn = this.mkBtn(toolbar, 'rotate-ccw', 'btn_undo');
|
||||
undoBtn.classList.add('hwm_undo-btn');
|
||||
const redoBtn = this.mkBtn(toolbar, 'rotate-cw', 'Ripristina');
|
||||
const redoBtn = this.mkBtn(toolbar, 'rotate-cw', 'btn_redo');
|
||||
redoBtn.classList.add('hwm_redo-btn');
|
||||
const clearBtn = this.mkBtn(toolbar, 'trash', 'Cancella tutto');
|
||||
const clearBtn = this.mkBtn(toolbar, 'trash', 'btn_clear');
|
||||
clearBtn.classList.add('hwm_clear-btn');
|
||||
toolbar.createDiv({ cls: 'hwm_separator' });
|
||||
|
||||
// Converti / Salva / Elimina
|
||||
const convertBtn = this.mkBtn(toolbar, 'file-text', 'Converti in Markdown');
|
||||
const convertBtn = this.mkBtn(toolbar, 'file-text', 'btn_convert');
|
||||
convertBtn.classList.add('hwm_convert-btn');
|
||||
const saveBtn = this.mkBtn(toolbar, 'save', 'Salva');
|
||||
const saveBtn = this.mkBtn(toolbar, 'save', 'btn_save');
|
||||
saveBtn.classList.add('hwm_save-btn');
|
||||
const deleteBtn = this.mkBtn(toolbar, 'x', 'Elimina riquadro');
|
||||
const deleteBtn = this.mkBtn(toolbar, 'file-x', 'btn_delete');
|
||||
deleteBtn.classList.add('hwm_delete-btn');
|
||||
|
||||
// Bottone chiudi (X): nel topbar, posizionata a destra via CSS absolute
|
||||
const closeBtn = this.mkBtn(topbar, 'x', 'btn_close');
|
||||
closeBtn.classList.add('hwm_close-btn');
|
||||
closeBtn.addEventListener('click', async () => {
|
||||
await this.saveSvg();
|
||||
this.leaf.detach();
|
||||
});
|
||||
|
||||
// --- Scroll container ---
|
||||
const scrollWrap = el.createDiv({ cls: 'hwm_editor-scroll' });
|
||||
const canvasWrap = scrollWrap.createDiv({ cls: 'hwm_canvas-wrap' });
|
||||
|
|
@ -280,10 +264,6 @@ export class DrawingEditorView extends ItemView {
|
|||
});
|
||||
|
||||
// Bottone ← → salva e chiudi la tab
|
||||
backBtn.addEventListener('click', async () => {
|
||||
await this.saveSvg();
|
||||
this.leaf.detach();
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- File I/O ---------- */
|
||||
|
|
@ -355,7 +335,7 @@ export class DrawingEditorView extends ItemView {
|
|||
/* ---------- Elimina ---------- */
|
||||
|
||||
private async doDelete() {
|
||||
if (!confirm('Eliminare questo riquadro handwriting e il file SVG associato?')) return;
|
||||
if (!confirm(t('confirm_delete'))) return;
|
||||
if (this.canvas) { this.canvas.destroy(); this.canvas = null; }
|
||||
// Rimuovi code block dal .md
|
||||
await this.removeCodeBlock();
|
||||
|
|
@ -363,7 +343,7 @@ export class DrawingEditorView extends ItemView {
|
|||
const svgFile = this.app.vault.getAbstractFileByPath(this.svgPath);
|
||||
if (svgFile instanceof TFile) await this.app.vault.delete(svgFile);
|
||||
this.leaf.detach();
|
||||
new Notice('Riquadro eliminato');
|
||||
new Notice(t('btn_delete'));
|
||||
}
|
||||
|
||||
/* ---------- Manipolazione file .md ---------- */
|
||||
|
|
@ -423,8 +403,10 @@ export class DrawingEditorView extends ItemView {
|
|||
|
||||
/* ---------- Helpers ---------- */
|
||||
|
||||
private mkBtn(parent: HTMLElement, icon: string, title: string): HTMLElement {
|
||||
const btn = parent.createEl('button', { cls: 'hwm_btn', attr: { title } });
|
||||
private mkBtn(parent: HTMLElement, icon: string, key: string): HTMLElement {
|
||||
const label = t(key as any);
|
||||
const btn = parent.createEl('button', { cls: 'hwm_btn', attr: { title: label } });
|
||||
btn.setAttribute('data-hwm-key', key);
|
||||
btn.innerHTML = ICONS[icon] ?? '';
|
||||
return btn;
|
||||
}
|
||||
|
|
@ -508,30 +490,17 @@ export class DrawingModal extends Modal {
|
|||
const lineColor = getEffectiveLineColor(this.plugin.settings);
|
||||
el.style.backgroundColor = bgColor;
|
||||
|
||||
// Top bar con toolbar (nessun backBtn: il Modal ha già la X nativa).
|
||||
// La classe --modal centra la toolbar orizzontalmente (nel DrawingEditorView
|
||||
// c'è il backBtn a sinistra e space-between; qui invece la toolbar è l'unico figlio).
|
||||
// Top bar con toolbar centrata. La X nativa di Obsidian è nascosta via CSS
|
||||
// (hwm_modal .modal-close-button); la chiusura avviene dal bottone X in toolbar.
|
||||
const topbar = el.createDiv({ cls: 'hwm_editor-topbar hwm_editor-topbar--modal' });
|
||||
if (isDark) topbar.classList.add('hwm_editor-topbar--dark');
|
||||
|
||||
const toolbar = topbar.createDiv({ cls: 'hwm_toolbar hwm_editor-toolbar' });
|
||||
if (isDark) toolbar.classList.add('hwm_toolbar--dark');
|
||||
|
||||
|
||||
if (isMobile) {
|
||||
toolbar.classList.add('hwm_toolbar--compact');
|
||||
const toggleBtn = this.mkBtn(toolbar, 'chevron-down', 'Mostra tutti');
|
||||
toggleBtn.classList.add('hwm_toggle-btn');
|
||||
toggleBtn.addEventListener('click', () => {
|
||||
const compact = toolbar.classList.toggle('hwm_toolbar--compact');
|
||||
updateColorSizes(compact);
|
||||
toggleBtn.innerHTML = ICONS[compact ? 'chevron-down' : 'chevron-up'] ?? '';
|
||||
});
|
||||
}
|
||||
|
||||
const penBtn = this.mkBtn(toolbar, 'pencil', 'Penna');
|
||||
const penBtn = this.mkBtn(toolbar, 'pencil', 'btn_pen');
|
||||
penBtn.classList.add('hwm_active', 'hwm_pen-btn');
|
||||
const eraserBtn = this.mkBtn(toolbar, 'eraser', 'Gomma');
|
||||
const eraserBtn = this.mkBtn(toolbar, 'eraser', 'btn_eraser');
|
||||
eraserBtn.classList.add('hwm_eraser-btn');
|
||||
toolbar.createDiv({ cls: 'hwm_separator' });
|
||||
|
||||
|
|
@ -552,15 +521,6 @@ export class DrawingModal extends Modal {
|
|||
}
|
||||
toolbar.createDiv({ cls: 'hwm_separator' });
|
||||
|
||||
const updateColorSizes = (compact: boolean) => {
|
||||
colorBtns.forEach(b => {
|
||||
const sz = (!compact || b.classList.contains('hwm_active')) ? '22px' : '0';
|
||||
b.style.setProperty('min-width', sz, 'important');
|
||||
b.style.setProperty('min-height', sz, 'important');
|
||||
});
|
||||
};
|
||||
if (isMobile) updateColorSizes(true);
|
||||
|
||||
// Listener bgMode: aggiorna toolbar, pallini colore e sfondo canvas al cambio tema.
|
||||
const lightColors = ['#000000', '#1e40af', '#dc2626', '#16a34a'];
|
||||
const darkColors = ['#ffffff', '#60a5fa', '#f87171', '#4ade80'];
|
||||
|
|
@ -586,21 +546,26 @@ export class DrawingModal extends Modal {
|
|||
};
|
||||
this.plugin.bgModeListeners.add(this.bgModeListener);
|
||||
|
||||
const undoBtn = this.mkBtn(toolbar, 'rotate-ccw', 'Annulla');
|
||||
const undoBtn = this.mkBtn(toolbar, 'rotate-ccw', 'btn_undo');
|
||||
undoBtn.classList.add('hwm_undo-btn');
|
||||
const redoBtn = this.mkBtn(toolbar, 'rotate-cw', 'Ripristina');
|
||||
const redoBtn = this.mkBtn(toolbar, 'rotate-cw', 'btn_redo');
|
||||
redoBtn.classList.add('hwm_redo-btn');
|
||||
const clearBtn = this.mkBtn(toolbar, 'trash', 'Cancella tutto');
|
||||
const clearBtn = this.mkBtn(toolbar, 'trash', 'btn_clear');
|
||||
clearBtn.classList.add('hwm_clear-btn');
|
||||
toolbar.createDiv({ cls: 'hwm_separator' });
|
||||
|
||||
const convertBtn = this.mkBtn(toolbar, 'file-text', 'Converti in Markdown');
|
||||
const convertBtn = this.mkBtn(toolbar, 'file-text', 'btn_convert');
|
||||
convertBtn.classList.add('hwm_convert-btn');
|
||||
const saveBtn = this.mkBtn(toolbar, 'save', 'Salva');
|
||||
const saveBtn = this.mkBtn(toolbar, 'save', 'btn_save');
|
||||
saveBtn.classList.add('hwm_save-btn');
|
||||
const deleteBtn = this.mkBtn(toolbar, 'x', 'Elimina riquadro');
|
||||
const deleteBtn = this.mkBtn(toolbar, 'file-x', 'btn_delete');
|
||||
deleteBtn.classList.add('hwm_delete-btn');
|
||||
|
||||
// Bottone chiudi (X): nel topbar, posizionata a destra via CSS absolute
|
||||
const closeBtn = this.mkBtn(topbar, 'x', 'btn_close');
|
||||
closeBtn.classList.add('hwm_close-btn');
|
||||
closeBtn.addEventListener('click', () => this.close());
|
||||
|
||||
const scrollWrap = el.createDiv({ cls: 'hwm_editor-scroll' });
|
||||
const canvasWrap = scrollWrap.createDiv({ cls: 'hwm_canvas-wrap' });
|
||||
|
||||
|
|
@ -694,14 +659,64 @@ export class DrawingModal extends Modal {
|
|||
} catch (e: unknown) { new Notice('Errore OCR: ' + (e instanceof Error ? e.message : String(e))); }
|
||||
}
|
||||
|
||||
// Overlay di conferma inline: nessun Modal annidato → nessun furto di focus
|
||||
private showDeleteConfirm(): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const overlay = this.contentEl.createDiv({ cls: 'hwm_confirm-overlay' });
|
||||
overlay.createEl('span', { text: t('confirm_delete'), cls: 'hwm_confirm-msg' });
|
||||
const okBtn = overlay.createEl('button', { text: t('confirm_ok'), cls: 'mod-warning' });
|
||||
const cancelBtn = overlay.createEl('button', { text: t('confirm_cancel') });
|
||||
okBtn.addEventListener('click', () => { overlay.remove(); resolve(true); });
|
||||
cancelBtn.addEventListener('click', () => { overlay.remove(); resolve(false); });
|
||||
okBtn.focus();
|
||||
});
|
||||
}
|
||||
|
||||
private async doDelete() {
|
||||
if (!confirm('Eliminare questo riquadro handwriting e il file SVG associato?')) return;
|
||||
if (!await this.showDeleteConfirm()) return;
|
||||
if (this.canvas) { this.canvas.destroy(); this.canvas = null; }
|
||||
|
||||
const srcPath = this.sourcePath;
|
||||
const ws = this.app.workspace;
|
||||
let focusDone = false;
|
||||
|
||||
// Funzione di focus: aspetta 300ms dopo che vault.modify ha sparato,
|
||||
// in modo da dare all'editor il tempo di completare il re-render del documento.
|
||||
const doFocus = () => {
|
||||
if (focusDone) return;
|
||||
focusDone = true;
|
||||
setTimeout(() => {
|
||||
let mdView = ws.getActiveViewOfType(MarkdownView);
|
||||
if (!mdView || mdView.file?.path !== srcPath) {
|
||||
const leaf = ws.getLeavesOfType('markdown')
|
||||
.find(l => (l.view as MarkdownView).file?.path === srcPath);
|
||||
if (leaf) ws.setActiveLeaf(leaf, { focus: true });
|
||||
mdView = ws.getActiveViewOfType(MarkdownView);
|
||||
}
|
||||
// Focus diretto sul contenteditable CM6
|
||||
const cm = mdView?.contentEl.querySelector('.cm-content') as HTMLElement;
|
||||
cm?.focus();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
// Registra il listener PRIMA di modificare il file, così non perdiamo l'evento.
|
||||
// vault.on('modify') scatta con certezza quando removeCodeBlock() scrive il file.
|
||||
const ref = this.app.vault.on('modify', (file) => {
|
||||
if (file.path === srcPath) {
|
||||
this.app.vault.offref(ref);
|
||||
doFocus();
|
||||
}
|
||||
});
|
||||
|
||||
await this.removeCodeBlock();
|
||||
const svgFile = this.app.vault.getAbstractFileByPath(this.svgPath);
|
||||
if (svgFile instanceof TFile) await this.app.vault.delete(svgFile);
|
||||
|
||||
// Fallback: se vault.modify non spara entro 3s (caso anomalo), forza comunque il focus
|
||||
setTimeout(() => { this.app.vault.offref(ref); doFocus(); }, 3000);
|
||||
|
||||
this.close();
|
||||
new Notice('Riquadro eliminato');
|
||||
new Notice(t('btn_delete'));
|
||||
}
|
||||
|
||||
private async archiveSvg() {
|
||||
|
|
@ -744,8 +759,10 @@ export class DrawingModal extends Modal {
|
|||
await this.replaceInMd('\n');
|
||||
}
|
||||
|
||||
private mkBtn(parent: HTMLElement, icon: string, title: string): HTMLElement {
|
||||
const btn = parent.createEl('button', { cls: 'hwm_btn', attr: { title } });
|
||||
private mkBtn(parent: HTMLElement, icon: string, key: string): HTMLElement {
|
||||
const label = t(key as any);
|
||||
const btn = parent.createEl('button', { cls: 'hwm_btn', attr: { title: label } });
|
||||
btn.setAttribute('data-hwm-key', key);
|
||||
btn.innerHTML = ICONS[icon] ?? '';
|
||||
return btn;
|
||||
}
|
||||
|
|
@ -763,3 +780,4 @@ export class DrawingModal extends Modal {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,12 @@ import {
|
|||
const ICONS: Record<string, string> = {
|
||||
'file-text': `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>`,
|
||||
'x': `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`,
|
||||
'file-x': `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><line x1="9.5" y1="12.5" x2="14.5" y2="17.5"/><line x1="14.5" y1="12.5" x2="9.5" y2="17.5"/></svg>`,
|
||||
'chevron-up': `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>`,
|
||||
'pencil': `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>`,
|
||||
};
|
||||
import type HandwritingPlugin from './main';
|
||||
import { t } from './i18n';
|
||||
import { Stroke } from './drawing-canvas';
|
||||
import { strokesToSvg, parseSvgStrokes, generateId } from './svg-utils';
|
||||
import { getEffectiveBgColor, getEffectiveLineColor, remapStrokeColor, BgMode } from './settings';
|
||||
|
|
@ -115,7 +117,7 @@ function setupMutationObserver(plugin: HandwritingPlugin) {
|
|||
|
||||
const filename = svgPath.split('/').pop() ?? '';
|
||||
const embedId = filename.replace('.svg', '');
|
||||
if (!embedId.startsWith('hw_')) return;
|
||||
if (!embedId.startsWith('hw_') && !embedId.startsWith('HTMD_')) return;
|
||||
|
||||
// Se Obsidian non ha ancora caricato l'immagine (classe image-embed
|
||||
// assente), riprova tra 150 ms — il caricamento è asincrono.
|
||||
|
|
@ -248,13 +250,13 @@ function showLegacyPreview(
|
|||
const btnBar = container.createDiv({ cls: 'hwm_inline-buttons' });
|
||||
if (isDark) btnBar.classList.add('hwm_inline-buttons--dark');
|
||||
|
||||
const deleteBtn = createBtn(btnBar, 'x', 'Elimina riquadro');
|
||||
const deleteBtn = createBtn(btnBar, 'file-x', 'Elimina riquadro');
|
||||
deleteBtn.classList.add('hwm_delete-btn');
|
||||
|
||||
const convertBtn = createBtn(btnBar, 'file-text', 'Converti in Markdown');
|
||||
convertBtn.classList.add('hwm_convert-btn');
|
||||
|
||||
const collapseBtn = createBtn(btnBar, 'chevron-up', 'Comprimi');
|
||||
const collapseBtn = createBtn(btnBar, 'chevron-up', 'btn_collapse');
|
||||
collapseBtn.classList.add('hwm_collapse-btn');
|
||||
|
||||
// --- Preview SVG via CSS background-image (nessun <img> dentro cm-content) ---
|
||||
|
|
@ -282,12 +284,14 @@ function showLegacyPreview(
|
|||
preview.classList.remove('hwm_collapsed');
|
||||
preview.style.maxHeight = '';
|
||||
collapseBtn.classList.remove('hwm_rotated');
|
||||
collapseBtn.title = 'Comprimi';
|
||||
collapseBtn.title = t('btn_collapse');
|
||||
collapseBtn.setAttribute('data-hwm-key', 'btn_collapse');
|
||||
} else {
|
||||
preview.classList.add('hwm_collapsed');
|
||||
preview.style.maxHeight = collapsedHeight + 'px';
|
||||
collapseBtn.classList.add('hwm_rotated');
|
||||
collapseBtn.title = 'Espandi';
|
||||
collapseBtn.title = t('btn_expand');
|
||||
collapseBtn.setAttribute('data-hwm-key', 'btn_expand');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -307,7 +311,7 @@ function showLegacyPreview(
|
|||
// Elimina
|
||||
deleteBtn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
if (!confirm('Eliminare questo riquadro handwriting e il file SVG associato?')) return;
|
||||
if (!await showInlineConfirm(container, t('confirm_delete'))) return;
|
||||
await removeLegacyEmbed(ctx, data, plugin);
|
||||
});
|
||||
}
|
||||
|
|
@ -622,7 +626,7 @@ function createPortalPanel(
|
|||
// --- Bottone matita ---
|
||||
// Desktop: apre DrawingModal (overlay fullscreen, senza aprire nuova tab)
|
||||
// Mobile: apre DrawingEditorView in una nuova tab
|
||||
const pencilBtn = createPanelBtn(panel, 'pencil', 'Apri editor disegno');
|
||||
const pencilBtn = createPanelBtn(panel, 'pencil', 'btn_open_editor');
|
||||
pencilBtn.addEventListener('click', async () => {
|
||||
if (Platform.isDesktop) {
|
||||
if (modalOpen) return;
|
||||
|
|
@ -659,21 +663,21 @@ function createPortalPanel(
|
|||
panel.appendChild(sep);
|
||||
|
||||
// --- Bottone converti in Markdown ---
|
||||
const convertBtn = createPanelBtn(panel, 'file-text', 'Converti in Markdown');
|
||||
const convertBtn = createPanelBtn(panel, 'file-text', 'btn_convert');
|
||||
|
||||
// --- Bottone comprimi/espandi ---
|
||||
// Usa height + overflow:hidden sul container (non max-height sull'<img>):
|
||||
// così l'immagine viene ritagliata verticalmente senza che la larghezza cambi.
|
||||
// Il pannello (position:absolute, top:6px) resta dentro l'area visibile
|
||||
// anche da compresso (collapsedHeight è sempre >> 6px + altezza pannello).
|
||||
const collapseBtn = createPanelBtn(panel, 'chevron-up', 'Comprimi');
|
||||
const collapseBtn = createPanelBtn(panel, 'chevron-up', 'btn_collapse');
|
||||
collapseBtn.classList.add('hwm_collapse-btn');
|
||||
|
||||
// --- Bottone elimina ---
|
||||
const deleteBtn = createPanelBtn(panel, 'x', 'Elimina riquadro');
|
||||
const deleteBtn = createPanelBtn(panel, 'file-x', 'btn_delete');
|
||||
deleteBtn.classList.add('hwm_delete-btn');
|
||||
deleteBtn.addEventListener('click', async () => {
|
||||
if (!confirm('Eliminare questo riquadro handwriting e il file SVG associato?')) return;
|
||||
if (!await showInlineConfirm(container, t('confirm_delete'))) return;
|
||||
await removeWikiEmbed(svgPath, sourcePath, plugin);
|
||||
});
|
||||
|
||||
|
|
@ -683,14 +687,16 @@ function createPortalPanel(
|
|||
container.style.height = '';
|
||||
container.style.overflow = '';
|
||||
collapseBtn.classList.remove('hwm_rotated');
|
||||
collapseBtn.title = 'Comprimi';
|
||||
collapseBtn.title = t('btn_collapse');
|
||||
collapseBtn.setAttribute('data-hwm-key', 'btn_collapse');
|
||||
};
|
||||
const doCollapse = () => {
|
||||
isExpanded = false;
|
||||
container.style.height = collapsedHeight + 'px';
|
||||
container.style.overflow = 'hidden';
|
||||
collapseBtn.classList.add('hwm_rotated');
|
||||
collapseBtn.title = 'Espandi';
|
||||
collapseBtn.title = t('btn_expand');
|
||||
collapseBtn.setAttribute('data-hwm-key', 'btn_expand');
|
||||
};
|
||||
// Carica SVG e chiama doConvertWiki (che lancia eccezione in caso di errore)
|
||||
const doConvertAction = async () => {
|
||||
|
|
@ -740,11 +746,27 @@ function createPortalPanel(
|
|||
plugin.register(() => plugin.bgModeListeners.delete(onBgMode));
|
||||
}
|
||||
|
||||
// Crea un bottone div nel pannello portale
|
||||
function createPanelBtn(parent: HTMLElement, icon: string, title: string): HTMLElement {
|
||||
// Overlay di conferma inline su un elemento position:relative.
|
||||
// Evita window.confirm() che in Electron ruba il focus dalla finestra.
|
||||
function showInlineConfirm(anchorEl: HTMLElement, msg: string): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const overlay = anchorEl.createDiv({ cls: 'hwm_confirm-overlay' });
|
||||
overlay.createEl('span', { text: msg, cls: 'hwm_confirm-msg' });
|
||||
const okBtn = overlay.createEl('button', { text: t('confirm_ok'), cls: 'mod-warning' });
|
||||
const cancelBtn = overlay.createEl('button', { text: t('confirm_cancel') });
|
||||
okBtn.addEventListener('click', () => { overlay.remove(); resolve(true); });
|
||||
cancelBtn.addEventListener('click', () => { overlay.remove(); resolve(false); });
|
||||
okBtn.focus();
|
||||
});
|
||||
}
|
||||
|
||||
// Crea un bottone div nel pannello portale.
|
||||
// key: chiave i18n — usata sia per il title che per data-hwm-key (aggiornamento live al cambio lingua)
|
||||
function createPanelBtn(parent: HTMLElement, icon: string, key: string): HTMLElement {
|
||||
const btn = document.createElement('div');
|
||||
btn.className = 'hwm_btn';
|
||||
btn.setAttribute('title', title);
|
||||
btn.setAttribute('title', t(key as any));
|
||||
btn.setAttribute('data-hwm-key', key);
|
||||
btn.setAttribute('role', 'button');
|
||||
btn.setAttribute('tabindex', '0');
|
||||
btn.innerHTML = ICONS[icon] ?? '';
|
||||
|
|
@ -808,8 +830,10 @@ function createLegacyPortalButton(
|
|||
|
||||
// Usa <div> invece di <button> per i bottoni dentro cm-content.
|
||||
// I <button> su Android Mobile possono interferire con l'handwriting.
|
||||
function createBtn(parent: HTMLElement, icon: string, title: string): HTMLElement {
|
||||
const btn = parent.createDiv({ cls: 'hwm_btn', attr: { title, role: 'button', tabindex: '0' } });
|
||||
// key: chiave i18n — usata sia per il title che per data-hwm-key (aggiornamento live al cambio lingua)
|
||||
function createBtn(parent: HTMLElement, icon: string, key: string): HTMLElement {
|
||||
const btn = parent.createDiv({ cls: 'hwm_btn', attr: { title: t(key as any), role: 'button', tabindex: '0' } });
|
||||
btn.setAttribute('data-hwm-key', key);
|
||||
btn.innerHTML = ICONS[icon] ?? '';
|
||||
return btn;
|
||||
}
|
||||
|
|
|
|||
66
HandTranscriptMd/src/i18n.ts
Normal file
66
HandTranscriptMd/src/i18n.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/* =============================================
|
||||
i18n — Localizzazione del plugin
|
||||
============================================= */
|
||||
|
||||
import it from './locales/it.json';
|
||||
import en from './locales/en.json';
|
||||
import de from './locales/de.json';
|
||||
import fr from './locales/fr.json';
|
||||
import es from './locales/es.json';
|
||||
import ru from './locales/ru.json';
|
||||
import ja from './locales/ja.json';
|
||||
import zhCn from './locales/zh-cn.json';
|
||||
import ptBr from './locales/pt-br.json';
|
||||
import pl from './locales/pl.json';
|
||||
|
||||
// Mappa codice lingua → dizionario traduzioni.
|
||||
// I codici corrispondono ai valori restituiti da moment.locale() in Obsidian.
|
||||
const locales: Record<string, typeof en> = {
|
||||
it, en, de, fr, es, ru, ja, pl,
|
||||
'zh-cn': zhCn,
|
||||
'pt-br': ptBr,
|
||||
};
|
||||
|
||||
// Nome nativo di ogni lingua — usato nel dropdown impostazioni
|
||||
export const localeNames: Record<string, string> = {
|
||||
it: 'Italiano',
|
||||
en: 'English',
|
||||
de: 'Deutsch',
|
||||
fr: 'Français',
|
||||
es: 'Español',
|
||||
ru: 'Русский',
|
||||
ja: '日本語',
|
||||
'zh-cn': '中文(简体)',
|
||||
'pt-br': 'Português (Brasil)',
|
||||
pl: 'Polski',
|
||||
};
|
||||
|
||||
// Dizionario attivo — aggiornato da setLocale()
|
||||
let dict: typeof en = en;
|
||||
|
||||
// Imposta la lingua attiva.
|
||||
// 'auto' rileva la lingua di Obsidian via moment.locale() con fallback a 'en'.
|
||||
// Dopo aver aggiornato il dizionario, aggiorna tutti i tooltip live nel DOM
|
||||
// che hanno l'attributo data-hwm-key (impostato da mkBtn/createBtn/createPanelBtn).
|
||||
export function setLocale(lang: string): void {
|
||||
const resolved = lang === 'auto'
|
||||
? ((window as any).moment?.locale?.() ?? 'en')
|
||||
: lang;
|
||||
dict = locales[resolved] ?? en;
|
||||
// Aggiorna i tooltip già presenti nel DOM
|
||||
document.querySelectorAll<HTMLElement>('[data-hwm-key]').forEach(el => {
|
||||
const key = el.getAttribute('data-hwm-key') as keyof typeof en;
|
||||
if (key) el.title = t(key);
|
||||
});
|
||||
}
|
||||
|
||||
// Funzione di traduzione: ritorna la stringa per la chiave data.
|
||||
// Se la chiave non esiste nel dizionario attivo usa il fallback inglese.
|
||||
export function t(key: keyof typeof en): string {
|
||||
return (dict as Record<string, string>)[key] ?? (en as Record<string, string>)[key] ?? key;
|
||||
}
|
||||
|
||||
// Ritorna i codici lingua disponibili (escluso 'auto')
|
||||
export function availableLocales(): string[] {
|
||||
return Object.keys(locales);
|
||||
}
|
||||
50
HandTranscriptMd/src/locales/de.json
Normal file
50
HandTranscriptMd/src/locales/de.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"kw_title": "Titel",
|
||||
"kw_text": "Text",
|
||||
"kw_footnote": "Fußnote",
|
||||
"kw_word": "Wort",
|
||||
"ui_language_name": "Oberflächensprache",
|
||||
"ui_language_desc": "Sprache der Einstellungsseite. \"Automatisch\" folgt der Obsidian-Systemsprache.",
|
||||
"ui_language_auto": "Automatisch (Systemsprache)",
|
||||
"svg_folder_name": "SVG-Ordner",
|
||||
"svg_folder_desc": "Ordner im Vault, in dem SVG-Zeichnungsdateien gespeichert werden",
|
||||
"canvas_width_name": "Canvas-Breite",
|
||||
"canvas_width_desc": "Horizontale Canvas-Auflösung in Pixeln",
|
||||
"canvas_height_name": "Canvas-Höhe",
|
||||
"canvas_height_desc": "Vertikale Canvas-Auflösung in Pixeln",
|
||||
"bg_mode_name": "Canvas-Hintergrund",
|
||||
"bg_mode_desc": "Hintergrundfarbe des Zeichenbereichs. \"Automatisch\" folgt dem Obsidian-Theme.",
|
||||
"bg_mode_auto": "Automatisch (folgt Obsidian-Theme)",
|
||||
"bg_mode_light": "Hell (weiß)",
|
||||
"bg_mode_dark": "Dunkel (dunkelgrau)",
|
||||
"gemini_key_name": "Gemini API-Schlüssel",
|
||||
"gemini_key_desc": "Erforderlich für die OCR-Handschrifterkennung. Erhalten Sie ihn von Google AI Studio (aistudio.google.com).",
|
||||
"ocr_langs_name": "OCR-Sprachen",
|
||||
"ocr_langs_desc": "Kommagetrennte BCP-47-Sprachcodes (z. B. \"it, en, fr\"). Wird vom KI-Modell für OCR verwendet.",
|
||||
"debug_mode_name": "Debug-Modus",
|
||||
"debug_mode_desc": "Echtzeit-Benachrichtigungen für IME/Touch-Ereignisse anzeigen (nützlich zur Diagnose von Android-Problemen).",
|
||||
"keywords_summary": "Vom OCR-Parser erkannte Schlüsselwörter",
|
||||
"keywords_desc": "Schreiben Sie diese Schlüsselwörter in Ihre Zeichnung, um die entsprechende Markdown-Struktur zu erzeugen. Alle sind Groß-/Kleinschreibung-unabhängig (//h1 = //H1). Der Inhalt folgt nach dem Schlüsselwort, getrennt durch ein Leerzeichen.",
|
||||
"keywords_col_keyword": "Schlüsselwort",
|
||||
"keywords_col_syntax": "Syntax",
|
||||
"keywords_col_output": "Ausgabe",
|
||||
"menu_expand_all": "Alle Zeichnungen aufklappen",
|
||||
"menu_collapse_all": "Alle Zeichnungen einklappen",
|
||||
"menu_convert_all": "Alle Zeichnungen in Text konvertieren",
|
||||
"error_conversion": "Konvertierungsfehler: ",
|
||||
"btn_pen": "Stift",
|
||||
"btn_eraser": "Radierer",
|
||||
"btn_undo": "Rückgängig",
|
||||
"btn_redo": "Wiederholen",
|
||||
"btn_clear": "Alles löschen",
|
||||
"btn_convert": "In Markdown konvertieren",
|
||||
"btn_save": "Speichern",
|
||||
"btn_delete": "Block löschen",
|
||||
"btn_close": "Schließen",
|
||||
"btn_collapse": "Einklappen",
|
||||
"btn_expand": "Aufklappen",
|
||||
"btn_open_editor": "Zeichnungseditor öffnen",
|
||||
"confirm_delete": "Diesen Handschrift-Block und die zugehörige SVG-Datei löschen?",
|
||||
"confirm_ok": "Löschen",
|
||||
"confirm_cancel": "Abbrechen"
|
||||
}
|
||||
50
HandTranscriptMd/src/locales/en.json
Normal file
50
HandTranscriptMd/src/locales/en.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"svg_folder_name": "SVG folder",
|
||||
"svg_folder_desc": "Vault folder where drawing SVG files are saved",
|
||||
"canvas_width_name": "Canvas width",
|
||||
"canvas_width_desc": "Horizontal canvas resolution in pixels",
|
||||
"canvas_height_name": "Canvas height",
|
||||
"canvas_height_desc": "Vertical canvas resolution in pixels",
|
||||
"bg_mode_name": "Canvas background",
|
||||
"bg_mode_desc": "Background color of the drawing area. \"Automatic\" follows the Obsidian theme.",
|
||||
"bg_mode_auto": "Automatic (follows Obsidian theme)",
|
||||
"bg_mode_light": "Light (white)",
|
||||
"bg_mode_dark": "Dark (dark gray)",
|
||||
"gemini_key_name": "Gemini API key",
|
||||
"gemini_key_desc": "Required for handwriting OCR recognition. Get it from Google AI Studio (aistudio.google.com).",
|
||||
"ocr_langs_name": "OCR languages",
|
||||
"ocr_langs_desc": "Comma-separated BCP-47 language codes (e.g. \"it, en, fr\"). Used by the AI model for OCR.",
|
||||
"debug_mode_name": "Debug mode",
|
||||
"debug_mode_desc": "Show real-time notifications for IME/touch events (useful for diagnosing issues on Android).",
|
||||
"keywords_summary": "Keywords recognized by the OCR parser",
|
||||
"keywords_desc": "Write these keywords in your drawing to generate the corresponding markdown structure. All are case-insensitive (//h1 = //H1). Content follows the keyword separated by a space.",
|
||||
"keywords_col_keyword": "Keyword",
|
||||
"keywords_col_syntax": "Syntax",
|
||||
"keywords_col_output": "Output",
|
||||
"kw_title": "Title",
|
||||
"kw_text": "text",
|
||||
"kw_footnote": "footnote",
|
||||
"kw_word": "word",
|
||||
"ui_language_name": "Interface language",
|
||||
"ui_language_desc": "Language used in the settings page. \"Automatic\" follows the Obsidian system language.",
|
||||
"ui_language_auto": "Automatic (system language)",
|
||||
"menu_expand_all": "Expand all drawings",
|
||||
"menu_collapse_all": "Collapse all drawings",
|
||||
"menu_convert_all": "Convert all drawings to text",
|
||||
"error_conversion": "Conversion error: ",
|
||||
"btn_pen": "Pen",
|
||||
"btn_eraser": "Eraser",
|
||||
"btn_undo": "Undo",
|
||||
"btn_redo": "Redo",
|
||||
"btn_clear": "Clear all",
|
||||
"btn_convert": "Convert to Markdown",
|
||||
"btn_save": "Save",
|
||||
"btn_delete": "Delete block",
|
||||
"btn_close": "Close",
|
||||
"btn_collapse": "Collapse",
|
||||
"btn_expand": "Expand",
|
||||
"btn_open_editor": "Open drawing editor",
|
||||
"confirm_delete": "Delete this handwriting block and the associated SVG file?",
|
||||
"confirm_ok": "Delete",
|
||||
"confirm_cancel": "Cancel"
|
||||
}
|
||||
50
HandTranscriptMd/src/locales/es.json
Normal file
50
HandTranscriptMd/src/locales/es.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"kw_title": "Título",
|
||||
"kw_text": "texto",
|
||||
"kw_footnote": "nota al pie",
|
||||
"kw_word": "palabra",
|
||||
"ui_language_name": "Idioma de la interfaz",
|
||||
"ui_language_desc": "Idioma usado en la página de ajustes. \"Automático\" sigue el idioma del sistema de Obsidian.",
|
||||
"ui_language_auto": "Automático (idioma del sistema)",
|
||||
"svg_folder_name": "Carpeta SVG",
|
||||
"svg_folder_desc": "Carpeta en el vault donde se guardan los archivos SVG de los dibujos",
|
||||
"canvas_width_name": "Ancho del canvas",
|
||||
"canvas_width_desc": "Resolución horizontal del canvas en píxeles",
|
||||
"canvas_height_name": "Alto del canvas",
|
||||
"canvas_height_desc": "Resolución vertical del canvas en píxeles",
|
||||
"bg_mode_name": "Fondo del canvas",
|
||||
"bg_mode_desc": "Color de fondo del área de dibujo. \"Automático\" sigue el tema de Obsidian.",
|
||||
"bg_mode_auto": "Automático (sigue el tema de Obsidian)",
|
||||
"bg_mode_light": "Claro (blanco)",
|
||||
"bg_mode_dark": "Oscuro (gris oscuro)",
|
||||
"gemini_key_name": "Clave API de Gemini",
|
||||
"gemini_key_desc": "Necesaria para el reconocimiento OCR de escritura a mano. Obténgala en Google AI Studio (aistudio.google.com).",
|
||||
"ocr_langs_name": "Idiomas OCR",
|
||||
"ocr_langs_desc": "Códigos de idioma BCP-47 separados por comas (p. ej. \"it, en, fr\"). Usados por el modelo de IA para el OCR.",
|
||||
"debug_mode_name": "Modo depuración",
|
||||
"debug_mode_desc": "Muestra notificaciones en tiempo real para eventos IME/táctil (útil para diagnosticar problemas en Android).",
|
||||
"keywords_summary": "Palabras clave reconocidas por el parser OCR",
|
||||
"keywords_desc": "Escribe estas palabras clave en el dibujo para generar la estructura markdown correspondiente. Todas son insensibles a mayúsculas/minúsculas (//h1 = //H1). El contenido sigue a la palabra clave separado por un espacio.",
|
||||
"keywords_col_keyword": "Palabra clave",
|
||||
"keywords_col_syntax": "Sintaxis",
|
||||
"keywords_col_output": "Salida",
|
||||
"menu_expand_all": "Expandir todos los dibujos",
|
||||
"menu_collapse_all": "Contraer todos los dibujos",
|
||||
"menu_convert_all": "Convertir todos los dibujos a texto",
|
||||
"error_conversion": "Error de conversión: ",
|
||||
"btn_pen": "Lápiz",
|
||||
"btn_eraser": "Borrador",
|
||||
"btn_undo": "Deshacer",
|
||||
"btn_redo": "Rehacer",
|
||||
"btn_clear": "Borrar todo",
|
||||
"btn_convert": "Convertir a Markdown",
|
||||
"btn_save": "Guardar",
|
||||
"btn_delete": "Eliminar bloque",
|
||||
"btn_close": "Cerrar",
|
||||
"btn_collapse": "Contraer",
|
||||
"btn_expand": "Expandir",
|
||||
"btn_open_editor": "Abrir editor de dibujo",
|
||||
"confirm_delete": "¿Eliminar este bloque de escritura a mano y el archivo SVG asociado?",
|
||||
"confirm_ok": "Eliminar",
|
||||
"confirm_cancel": "Cancelar"
|
||||
}
|
||||
50
HandTranscriptMd/src/locales/fr.json
Normal file
50
HandTranscriptMd/src/locales/fr.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"kw_title": "Titre",
|
||||
"kw_text": "texte",
|
||||
"kw_footnote": "note de bas de page",
|
||||
"kw_word": "mot",
|
||||
"ui_language_name": "Langue de l'interface",
|
||||
"ui_language_desc": "Langue utilisée dans la page des paramètres. \"Automatique\" suit la langue système d'Obsidian.",
|
||||
"ui_language_auto": "Automatique (langue du système)",
|
||||
"svg_folder_name": "Dossier SVG",
|
||||
"svg_folder_desc": "Dossier dans le vault où les fichiers SVG des dessins sont enregistrés",
|
||||
"canvas_width_name": "Largeur du canvas",
|
||||
"canvas_width_desc": "Résolution horizontale du canvas en pixels",
|
||||
"canvas_height_name": "Hauteur du canvas",
|
||||
"canvas_height_desc": "Résolution verticale du canvas en pixels",
|
||||
"bg_mode_name": "Fond du canvas",
|
||||
"bg_mode_desc": "Couleur de fond de la zone de dessin. \"Automatique\" suit le thème d'Obsidian.",
|
||||
"bg_mode_auto": "Automatique (suit le thème Obsidian)",
|
||||
"bg_mode_light": "Clair (blanc)",
|
||||
"bg_mode_dark": "Sombre (gris foncé)",
|
||||
"gemini_key_name": "Clé API Gemini",
|
||||
"gemini_key_desc": "Nécessaire pour la reconnaissance OCR de l'écriture manuscrite. Obtenez-la sur Google AI Studio (aistudio.google.com).",
|
||||
"ocr_langs_name": "Langues OCR",
|
||||
"ocr_langs_desc": "Codes de langue BCP-47 séparés par des virgules (ex. : \"it, en, fr\"). Utilisés par le modèle IA pour l'OCR.",
|
||||
"debug_mode_name": "Mode débogage",
|
||||
"debug_mode_desc": "Affiche des notifications en temps réel pour les événements IME/tactile (utile pour diagnostiquer les problèmes sur Android).",
|
||||
"keywords_summary": "Mots-clés reconnus par le parser OCR",
|
||||
"keywords_desc": "Écrivez ces mots-clés dans votre dessin pour générer la structure markdown correspondante. Tous sont insensibles à la casse (//h1 = //H1). Le contenu suit le mot-clé séparé par un espace.",
|
||||
"keywords_col_keyword": "Mot-clé",
|
||||
"keywords_col_syntax": "Syntaxe",
|
||||
"keywords_col_output": "Sortie",
|
||||
"menu_expand_all": "Développer tous les dessins",
|
||||
"menu_collapse_all": "Réduire tous les dessins",
|
||||
"menu_convert_all": "Convertir tous les dessins en texte",
|
||||
"error_conversion": "Erreur de conversion : ",
|
||||
"btn_pen": "Stylo",
|
||||
"btn_eraser": "Gomme",
|
||||
"btn_undo": "Annuler",
|
||||
"btn_redo": "Rétablir",
|
||||
"btn_clear": "Tout effacer",
|
||||
"btn_convert": "Convertir en Markdown",
|
||||
"btn_save": "Enregistrer",
|
||||
"btn_delete": "Supprimer le bloc",
|
||||
"btn_close": "Fermer",
|
||||
"btn_collapse": "Réduire",
|
||||
"btn_expand": "Développer",
|
||||
"btn_open_editor": "Ouvrir l'éditeur de dessin",
|
||||
"confirm_delete": "Supprimer ce bloc manuscrit et le fichier SVG associé ?",
|
||||
"confirm_ok": "Supprimer",
|
||||
"confirm_cancel": "Annuler"
|
||||
}
|
||||
50
HandTranscriptMd/src/locales/it.json
Normal file
50
HandTranscriptMd/src/locales/it.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"svg_folder_name": "Cartella SVG",
|
||||
"svg_folder_desc": "Cartella nel vault dove vengono salvati i file SVG dei disegni",
|
||||
"canvas_width_name": "Larghezza canvas",
|
||||
"canvas_width_desc": "Risoluzione orizzontale del canvas in pixel",
|
||||
"canvas_height_name": "Altezza canvas",
|
||||
"canvas_height_desc": "Risoluzione verticale del canvas in pixel",
|
||||
"bg_mode_name": "Sfondo canvas",
|
||||
"bg_mode_desc": "Colore di sfondo del riquadro di disegno. \"Automatico\" segue il tema di Obsidian.",
|
||||
"bg_mode_auto": "Automatico (segue tema Obsidian)",
|
||||
"bg_mode_light": "Chiaro (bianco)",
|
||||
"bg_mode_dark": "Scuro (grigio scuro)",
|
||||
"gemini_key_name": "Chiave API Gemini",
|
||||
"gemini_key_desc": "Necessaria per il riconoscimento OCR della scrittura a mano. Ottienila da Google AI Studio (aistudio.google.com).",
|
||||
"ocr_langs_name": "Lingue OCR",
|
||||
"ocr_langs_desc": "Codici lingua BCP-47 separati da virgola (es. \"it, en, fr\"). Usati dal modello AI per utilizzare l'OCR.",
|
||||
"debug_mode_name": "Modalità debug",
|
||||
"debug_mode_desc": "Mostra notifiche in tempo reale per eventi IME/touch (utile per diagnosticare problemi su Android).",
|
||||
"keywords_summary": "Keyword riconosciute dal parser OCR",
|
||||
"keywords_desc": "Scrivi queste keyword nel disegno per generare la struttura markdown corrispondente. Tutte sono case-insensitive (//h1 = //H1). Il contenuto segue dopo la keyword separato da uno spazio.",
|
||||
"keywords_col_keyword": "Keyword",
|
||||
"keywords_col_syntax": "Sintassi",
|
||||
"keywords_col_output": "Output",
|
||||
"kw_title": "Titolo",
|
||||
"kw_text": "testo",
|
||||
"kw_footnote": "nota a piè pagina",
|
||||
"kw_word": "parola",
|
||||
"ui_language_name": "Lingua interfaccia",
|
||||
"ui_language_desc": "Lingua usata nella pagina impostazioni. \"Automatico\" segue la lingua di sistema di Obsidian.",
|
||||
"ui_language_auto": "Automatico (lingua di sistema)",
|
||||
"menu_expand_all": "Espandi tutti i disegni",
|
||||
"menu_collapse_all": "Collassa tutti i disegni",
|
||||
"menu_convert_all": "Converti tutti i disegni in testo",
|
||||
"error_conversion": "Errore nella conversione: ",
|
||||
"btn_pen": "Penna",
|
||||
"btn_eraser": "Gomma",
|
||||
"btn_undo": "Annulla",
|
||||
"btn_redo": "Ripristina",
|
||||
"btn_clear": "Cancella tutto",
|
||||
"btn_convert": "Converti in Markdown",
|
||||
"btn_save": "Salva",
|
||||
"btn_delete": "Elimina riquadro",
|
||||
"btn_close": "Chiudi",
|
||||
"btn_collapse": "Comprimi",
|
||||
"btn_expand": "Espandi",
|
||||
"btn_open_editor": "Apri editor disegno",
|
||||
"confirm_delete": "Eliminare questo riquadro handwriting e il file SVG associato?",
|
||||
"confirm_ok": "Elimina",
|
||||
"confirm_cancel": "Annulla"
|
||||
}
|
||||
50
HandTranscriptMd/src/locales/ja.json
Normal file
50
HandTranscriptMd/src/locales/ja.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"kw_title": "タイトル",
|
||||
"kw_text": "テキスト",
|
||||
"kw_footnote": "脚注",
|
||||
"kw_word": "単語",
|
||||
"ui_language_name": "インターフェース言語",
|
||||
"ui_language_desc": "設定ページで使用する言語。「自動」はObsidianのシステム言語に従います。",
|
||||
"ui_language_auto": "自動(システム言語)",
|
||||
"svg_folder_name": "SVGフォルダ",
|
||||
"svg_folder_desc": "描画SVGファイルを保存するvaultのフォルダ",
|
||||
"canvas_width_name": "キャンバスの幅",
|
||||
"canvas_width_desc": "キャンバスの水平解像度(ピクセル)",
|
||||
"canvas_height_name": "キャンバスの高さ",
|
||||
"canvas_height_desc": "キャンバスの垂直解像度(ピクセル)",
|
||||
"bg_mode_name": "キャンバスの背景",
|
||||
"bg_mode_desc": "描画エリアの背景色。「自動」はObsidianのテーマに従います。",
|
||||
"bg_mode_auto": "自動(Obsidianテーマに従う)",
|
||||
"bg_mode_light": "ライト(白)",
|
||||
"bg_mode_dark": "ダーク(濃いグレー)",
|
||||
"gemini_key_name": "Gemini APIキー",
|
||||
"gemini_key_desc": "手書きOCR認識に必要です。Google AI Studio(aistudio.google.com)から取得してください。",
|
||||
"ocr_langs_name": "OCR言語",
|
||||
"ocr_langs_desc": "カンマ区切りのBCP-47言語コード(例:「it, en, fr」)。AIモデルのOCRに使用されます。",
|
||||
"debug_mode_name": "デバッグモード",
|
||||
"debug_mode_desc": "IME/タッチイベントのリアルタイム通知を表示します(Androidの問題診断に役立ちます)。",
|
||||
"keywords_summary": "OCRパーサーが認識するキーワード",
|
||||
"keywords_desc": "これらのキーワードを描画に書くと、対応するmarkdown構造が生成されます。すべて大文字小文字を区別しません(//h1 = //H1)。内容はキーワードの後にスペースで区切って続けます。",
|
||||
"keywords_col_keyword": "キーワード",
|
||||
"keywords_col_syntax": "構文",
|
||||
"keywords_col_output": "出力",
|
||||
"menu_expand_all": "すべての描画を展開",
|
||||
"menu_collapse_all": "すべての描画を折りたたむ",
|
||||
"menu_convert_all": "すべての描画をテキストに変換",
|
||||
"error_conversion": "変換エラー:",
|
||||
"btn_pen": "ペン",
|
||||
"btn_eraser": "消しゴム",
|
||||
"btn_undo": "元に戻す",
|
||||
"btn_redo": "やり直す",
|
||||
"btn_clear": "すべて消去",
|
||||
"btn_convert": "Markdownに変換",
|
||||
"btn_save": "保存",
|
||||
"btn_delete": "ブロックを削除",
|
||||
"btn_close": "閉じる",
|
||||
"btn_collapse": "折りたたむ",
|
||||
"btn_expand": "展開する",
|
||||
"btn_open_editor": "描画エディタを開く",
|
||||
"confirm_delete": "この手書きブロックと関連するSVGファイルを削除しますか?",
|
||||
"confirm_ok": "削除",
|
||||
"confirm_cancel": "キャンセル"
|
||||
}
|
||||
50
HandTranscriptMd/src/locales/pl.json
Normal file
50
HandTranscriptMd/src/locales/pl.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"kw_title": "Tytuł",
|
||||
"kw_text": "tekst",
|
||||
"kw_footnote": "przypis",
|
||||
"kw_word": "słowo",
|
||||
"ui_language_name": "Język interfejsu",
|
||||
"ui_language_desc": "Język używany na stronie ustawień. \"Automatyczny\" podąża za językiem systemu Obsidian.",
|
||||
"ui_language_auto": "Automatyczny (język systemu)",
|
||||
"svg_folder_name": "Folder SVG",
|
||||
"svg_folder_desc": "Folder w vaulcie, gdzie zapisywane są pliki SVG rysunków",
|
||||
"canvas_width_name": "Szerokość płótna",
|
||||
"canvas_width_desc": "Pozioma rozdzielczość płótna w pikselach",
|
||||
"canvas_height_name": "Wysokość płótna",
|
||||
"canvas_height_desc": "Pionowa rozdzielczość płótna w pikselach",
|
||||
"bg_mode_name": "Tło płótna",
|
||||
"bg_mode_desc": "Kolor tła obszaru rysowania. \"Automatyczny\" podąża za motywem Obsidian.",
|
||||
"bg_mode_auto": "Automatyczny (podąża za motywem Obsidian)",
|
||||
"bg_mode_light": "Jasny (biały)",
|
||||
"bg_mode_dark": "Ciemny (ciemnoszary)",
|
||||
"gemini_key_name": "Klucz API Gemini",
|
||||
"gemini_key_desc": "Wymagany do rozpoznawania OCR pisma odręcznego. Uzyskaj go w Google AI Studio (aistudio.google.com).",
|
||||
"ocr_langs_name": "Języki OCR",
|
||||
"ocr_langs_desc": "Kody języków BCP-47 oddzielone przecinkami (np. \"it, en, fr\"). Używane przez model AI do OCR.",
|
||||
"debug_mode_name": "Tryb debugowania",
|
||||
"debug_mode_desc": "Pokazuje powiadomienia w czasie rzeczywistym dla zdarzeń IME/dotyk (przydatne do diagnozowania problemów na Androidzie).",
|
||||
"keywords_summary": "Słowa kluczowe rozpoznawane przez parser OCR",
|
||||
"keywords_desc": "Wpisz te słowa kluczowe w rysunku, aby wygenerować odpowiednią strukturę markdown. Wszystkie są niewrażliwe na wielkość liter (//h1 = //H1). Treść następuje po słowie kluczowym oddzielona spacją.",
|
||||
"keywords_col_keyword": "Słowo kluczowe",
|
||||
"keywords_col_syntax": "Składnia",
|
||||
"keywords_col_output": "Wynik",
|
||||
"menu_expand_all": "Rozwiń wszystkie rysunki",
|
||||
"menu_collapse_all": "Zwiń wszystkie rysunki",
|
||||
"menu_convert_all": "Konwertuj wszystkie rysunki na tekst",
|
||||
"error_conversion": "Błąd konwersji: ",
|
||||
"btn_pen": "Pióro",
|
||||
"btn_eraser": "Gumka",
|
||||
"btn_undo": "Cofnij",
|
||||
"btn_redo": "Ponów",
|
||||
"btn_clear": "Wyczyść wszystko",
|
||||
"btn_convert": "Konwertuj do Markdown",
|
||||
"btn_save": "Zapisz",
|
||||
"btn_delete": "Usuń blok",
|
||||
"btn_close": "Zamknij",
|
||||
"btn_collapse": "Zwiń",
|
||||
"btn_expand": "Rozwiń",
|
||||
"btn_open_editor": "Otwórz edytor rysunku",
|
||||
"confirm_delete": "Usunąć ten blok pisma odręcznego i powiązany plik SVG?",
|
||||
"confirm_ok": "Usuń",
|
||||
"confirm_cancel": "Anuluj"
|
||||
}
|
||||
50
HandTranscriptMd/src/locales/pt-br.json
Normal file
50
HandTranscriptMd/src/locales/pt-br.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"kw_title": "Título",
|
||||
"kw_text": "texto",
|
||||
"kw_footnote": "nota de rodapé",
|
||||
"kw_word": "palavra",
|
||||
"ui_language_name": "Idioma da interface",
|
||||
"ui_language_desc": "Idioma usado na página de configurações. \"Automático\" segue o idioma do sistema do Obsidian.",
|
||||
"ui_language_auto": "Automático (idioma do sistema)",
|
||||
"svg_folder_name": "Pasta SVG",
|
||||
"svg_folder_desc": "Pasta no vault onde os arquivos SVG dos desenhos são salvos",
|
||||
"canvas_width_name": "Largura do canvas",
|
||||
"canvas_width_desc": "Resolução horizontal do canvas em pixels",
|
||||
"canvas_height_name": "Altura do canvas",
|
||||
"canvas_height_desc": "Resolução vertical do canvas em pixels",
|
||||
"bg_mode_name": "Fundo do canvas",
|
||||
"bg_mode_desc": "Cor de fundo da área de desenho. \"Automático\" segue o tema do Obsidian.",
|
||||
"bg_mode_auto": "Automático (segue o tema do Obsidian)",
|
||||
"bg_mode_light": "Claro (branco)",
|
||||
"bg_mode_dark": "Escuro (cinza escuro)",
|
||||
"gemini_key_name": "Chave de API do Gemini",
|
||||
"gemini_key_desc": "Necessária para o reconhecimento OCR de manuscrito. Obtenha no Google AI Studio (aistudio.google.com).",
|
||||
"ocr_langs_name": "Idiomas OCR",
|
||||
"ocr_langs_desc": "Códigos de idioma BCP-47 separados por vírgula (ex: \"it, en, fr\"). Usados pelo modelo de IA para OCR.",
|
||||
"debug_mode_name": "Modo debug",
|
||||
"debug_mode_desc": "Mostra notificações em tempo real para eventos IME/toque (útil para diagnosticar problemas no Android).",
|
||||
"keywords_summary": "Palavras-chave reconhecidas pelo parser OCR",
|
||||
"keywords_desc": "Escreva estas palavras-chave no desenho para gerar a estrutura markdown correspondente. Todas são insensíveis a maiúsculas/minúsculas (//h1 = //H1). O conteúdo segue após a palavra-chave separado por um espaço.",
|
||||
"keywords_col_keyword": "Palavra-chave",
|
||||
"keywords_col_syntax": "Sintaxe",
|
||||
"keywords_col_output": "Saída",
|
||||
"menu_expand_all": "Expandir todos os desenhos",
|
||||
"menu_collapse_all": "Recolher todos os desenhos",
|
||||
"menu_convert_all": "Converter todos os desenhos em texto",
|
||||
"error_conversion": "Erro na conversão: ",
|
||||
"btn_pen": "Caneta",
|
||||
"btn_eraser": "Borracha",
|
||||
"btn_undo": "Desfazer",
|
||||
"btn_redo": "Refazer",
|
||||
"btn_clear": "Limpar tudo",
|
||||
"btn_convert": "Converter para Markdown",
|
||||
"btn_save": "Salvar",
|
||||
"btn_delete": "Excluir bloco",
|
||||
"btn_close": "Fechar",
|
||||
"btn_collapse": "Recolher",
|
||||
"btn_expand": "Expandir",
|
||||
"btn_open_editor": "Abrir editor de desenho",
|
||||
"confirm_delete": "Excluir este bloco de manuscrito e o arquivo SVG associado?",
|
||||
"confirm_ok": "Excluir",
|
||||
"confirm_cancel": "Cancelar"
|
||||
}
|
||||
50
HandTranscriptMd/src/locales/ru.json
Normal file
50
HandTranscriptMd/src/locales/ru.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"kw_title": "Заголовок",
|
||||
"kw_text": "текст",
|
||||
"kw_footnote": "сноска",
|
||||
"kw_word": "слово",
|
||||
"ui_language_name": "Язык интерфейса",
|
||||
"ui_language_desc": "Язык страницы настроек. \"Авто\" следует системному языку Obsidian.",
|
||||
"ui_language_auto": "Авто (системный язык)",
|
||||
"svg_folder_name": "Папка SVG",
|
||||
"svg_folder_desc": "Папка в хранилище для сохранения SVG-файлов рисунков",
|
||||
"canvas_width_name": "Ширина холста",
|
||||
"canvas_width_desc": "Горизонтальное разрешение холста в пикселях",
|
||||
"canvas_height_name": "Высота холста",
|
||||
"canvas_height_desc": "Вертикальное разрешение холста в пикселях",
|
||||
"bg_mode_name": "Фон холста",
|
||||
"bg_mode_desc": "Цвет фона области рисования. \"Авто\" следует теме Obsidian.",
|
||||
"bg_mode_auto": "Авто (следует теме Obsidian)",
|
||||
"bg_mode_light": "Светлый (белый)",
|
||||
"bg_mode_dark": "Тёмный (тёмно-серый)",
|
||||
"gemini_key_name": "API-ключ Gemini",
|
||||
"gemini_key_desc": "Необходим для OCR-распознавания рукописного текста. Получите в Google AI Studio (aistudio.google.com).",
|
||||
"ocr_langs_name": "Языки OCR",
|
||||
"ocr_langs_desc": "Коды языков BCP-47, разделённые запятой (например \"it, en, fr\"). Используются моделью ИИ для OCR.",
|
||||
"debug_mode_name": "Режим отладки",
|
||||
"debug_mode_desc": "Показывать уведомления в реальном времени для событий IME/касания (полезно для диагностики проблем на Android).",
|
||||
"keywords_summary": "Ключевые слова, распознаваемые парсером OCR",
|
||||
"keywords_desc": "Пишите эти ключевые слова в рисунке для создания соответствующей структуры markdown. Регистр не важен (//h1 = //H1). Содержимое следует за ключевым словом через пробел.",
|
||||
"keywords_col_keyword": "Ключевое слово",
|
||||
"keywords_col_syntax": "Синтаксис",
|
||||
"keywords_col_output": "Вывод",
|
||||
"menu_expand_all": "Развернуть все рисунки",
|
||||
"menu_collapse_all": "Свернуть все рисунки",
|
||||
"menu_convert_all": "Конвертировать все рисунки в текст",
|
||||
"error_conversion": "Ошибка конвертации: ",
|
||||
"btn_pen": "Ручка",
|
||||
"btn_eraser": "Ластик",
|
||||
"btn_undo": "Отменить",
|
||||
"btn_redo": "Повторить",
|
||||
"btn_clear": "Очистить всё",
|
||||
"btn_convert": "Конвертировать в Markdown",
|
||||
"btn_save": "Сохранить",
|
||||
"btn_delete": "Удалить блок",
|
||||
"btn_close": "Закрыть",
|
||||
"btn_collapse": "Свернуть",
|
||||
"btn_expand": "Развернуть",
|
||||
"btn_open_editor": "Открыть редактор рисунков",
|
||||
"confirm_delete": "Удалить этот блок рукописного ввода и связанный файл SVG?",
|
||||
"confirm_ok": "Удалить",
|
||||
"confirm_cancel": "Отмена"
|
||||
}
|
||||
50
HandTranscriptMd/src/locales/zh-cn.json
Normal file
50
HandTranscriptMd/src/locales/zh-cn.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"kw_title": "标题",
|
||||
"kw_text": "文本",
|
||||
"kw_footnote": "脚注",
|
||||
"kw_word": "词语",
|
||||
"ui_language_name": "界面语言",
|
||||
"ui_language_desc": "设置页面使用的语言。\"自动\"跟随Obsidian系统语言。",
|
||||
"ui_language_auto": "自动(系统语言)",
|
||||
"svg_folder_name": "SVG文件夹",
|
||||
"svg_folder_desc": "保存绘图SVG文件的vault文件夹",
|
||||
"canvas_width_name": "画布宽度",
|
||||
"canvas_width_desc": "画布水平分辨率(像素)",
|
||||
"canvas_height_name": "画布高度",
|
||||
"canvas_height_desc": "画布垂直分辨率(像素)",
|
||||
"bg_mode_name": "画布背景",
|
||||
"bg_mode_desc": "绘图区域的背景颜色。\"自动\"跟随Obsidian主题。",
|
||||
"bg_mode_auto": "自动(跟随Obsidian主题)",
|
||||
"bg_mode_light": "浅色(白色)",
|
||||
"bg_mode_dark": "深色(深灰色)",
|
||||
"gemini_key_name": "Gemini API密钥",
|
||||
"gemini_key_desc": "手写OCR识别所需。从Google AI Studio (aistudio.google.com)获取。",
|
||||
"ocr_langs_name": "OCR语言",
|
||||
"ocr_langs_desc": "逗号分隔的BCP-47语言代码(例如\"it, en, fr\")。用于AI模型的OCR识别。",
|
||||
"debug_mode_name": "调试模式",
|
||||
"debug_mode_desc": "显示IME/触摸事件的实时通知(用于诊断Android问题)。",
|
||||
"keywords_summary": "OCR解析器识别的关键词",
|
||||
"keywords_desc": "在绘图中写入这些关键词以生成相应的markdown结构。不区分大小写(//h1 = //H1)。内容跟在关键词后面,用空格分隔。",
|
||||
"keywords_col_keyword": "关键词",
|
||||
"keywords_col_syntax": "语法",
|
||||
"keywords_col_output": "输出",
|
||||
"menu_expand_all": "展开所有绘图",
|
||||
"menu_collapse_all": "折叠所有绘图",
|
||||
"menu_convert_all": "将所有绘图转换为文本",
|
||||
"error_conversion": "转换错误:",
|
||||
"btn_pen": "画笔",
|
||||
"btn_eraser": "橡皮擦",
|
||||
"btn_undo": "撤销",
|
||||
"btn_redo": "重做",
|
||||
"btn_clear": "全部清除",
|
||||
"btn_convert": "转换为Markdown",
|
||||
"btn_save": "保存",
|
||||
"btn_delete": "删除块",
|
||||
"btn_close": "关闭",
|
||||
"btn_collapse": "折叠",
|
||||
"btn_expand": "展开",
|
||||
"btn_open_editor": "打开绘图编辑器",
|
||||
"confirm_delete": "删除此手写块及关联的SVG文件?",
|
||||
"confirm_ok": "删除",
|
||||
"confirm_cancel": "取消"
|
||||
}
|
||||
|
|
@ -7,7 +7,8 @@
|
|||
- Tab impostazioni
|
||||
============================================= */
|
||||
|
||||
import { Plugin, TFile, Notice } from 'obsidian';
|
||||
import { Plugin, TFile, TFolder, Notice, FuzzySuggestModal, FuzzyMatch, MarkdownView, Editor } from 'obsidian';
|
||||
import { t, setLocale } from './i18n';
|
||||
import { DEFAULT_SETTINGS, HandwritingSettings, HandwritingSettingTab } from './settings';
|
||||
import { registerEmbed, insertHandwritingBlock } from './embed';
|
||||
import { VIEW_TYPE_HANDWRITING, DrawingEditorView } from './editor-view';
|
||||
|
|
@ -47,6 +48,9 @@ export default class HandwritingPlugin extends Plugin {
|
|||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
// Applica la lingua interfaccia salvata (o la lingua di sistema se 'auto')
|
||||
setLocale(this.settings.uiLanguage);
|
||||
|
||||
// Rileva cambio tema Obsidian (aggiunta/rimozione classe 'theme-dark' sul body).
|
||||
// Se bgMode è 'auto', notifica tutti i listener per aggiornare colori e SVG.
|
||||
const themeObserver = new MutationObserver(() => {
|
||||
|
|
@ -65,11 +69,22 @@ export default class HandwritingPlugin extends Plugin {
|
|||
this.addCommand({
|
||||
id: 'insert-handwriting',
|
||||
name: 'Insert handwriting block',
|
||||
icon: 'pencil',
|
||||
editorCallback: async () => {
|
||||
await insertHandwritingBlock(this);
|
||||
}
|
||||
});
|
||||
|
||||
// Comando: inserisce un riferimento a un SVG esistente nella cartella handwriting
|
||||
this.addCommand({
|
||||
id: 'insert-svg-reference',
|
||||
name: 'Insert SVG reference',
|
||||
icon: 'file-plus',
|
||||
editorCallback: (editor: Editor) => {
|
||||
new SvgReferenceSuggest(this.app, this, editor).open();
|
||||
}
|
||||
});
|
||||
|
||||
// Icona nella ribbon (sidebar sinistra)
|
||||
this.addRibbonIcon('pencil', 'Insert handwriting', async () => {
|
||||
await insertHandwritingBlock(this);
|
||||
|
|
@ -86,7 +101,7 @@ export default class HandwritingPlugin extends Plugin {
|
|||
this.app.workspace.on('file-menu', (menu, file) => {
|
||||
if (!(file instanceof TFile) || file.extension !== 'md') return;
|
||||
menu.addItem(item => item
|
||||
.setTitle('Espandi tutti i disegni')
|
||||
.setTitle(t('menu_expand_all'))
|
||||
.setIcon('chevrons-down')
|
||||
.setSection('danger')
|
||||
.onClick(() => {
|
||||
|
|
@ -94,7 +109,7 @@ export default class HandwritingPlugin extends Plugin {
|
|||
})
|
||||
);
|
||||
menu.addItem(item => item
|
||||
.setTitle('Collassa tutti i disegni')
|
||||
.setTitle(t('menu_collapse_all'))
|
||||
.setIcon('chevrons-up')
|
||||
.setSection('danger')
|
||||
.onClick(() => {
|
||||
|
|
@ -102,7 +117,7 @@ export default class HandwritingPlugin extends Plugin {
|
|||
})
|
||||
);
|
||||
menu.addItem(item => item
|
||||
.setTitle('Converti tutti i disegni in testo')
|
||||
.setTitle(t('menu_convert_all'))
|
||||
.setIcon('file-text')
|
||||
.setSection('danger')
|
||||
.onClick(async () => {
|
||||
|
|
@ -112,7 +127,7 @@ export default class HandwritingPlugin extends Plugin {
|
|||
await actions.convert();
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
new Notice('Errore nella conversione: ' + (e instanceof Error ? e.message : String(e)));
|
||||
new Notice(t('error_conversion') + (e instanceof Error ? e.message : String(e)));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
@ -156,3 +171,48 @@ export default class HandwritingPlugin extends Plugin {
|
|||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
// Modal fuzzy-search per selezionare un SVG esistente nella cartella handwriting
|
||||
// e inserire il riferimento ![[path]] nel cursore dell'editor attivo.
|
||||
class SvgReferenceSuggest extends FuzzySuggestModal<TFile> {
|
||||
constructor(
|
||||
app: import('obsidian').App,
|
||||
private plugin: HandwritingPlugin,
|
||||
private editor: Editor
|
||||
) {
|
||||
super(app);
|
||||
this.setPlaceholder('Cerca SVG...');
|
||||
}
|
||||
|
||||
// Restituisce tutti gli SVG nella cartella impostata (esclusa _converted)
|
||||
getItems(): TFile[] {
|
||||
const folder = this.app.vault.getAbstractFileByPath(this.plugin.settings.svgFolder);
|
||||
if (!(folder instanceof TFolder)) return [];
|
||||
return folder.children.filter(
|
||||
(f): f is TFile =>
|
||||
f instanceof TFile &&
|
||||
f.extension === 'svg' &&
|
||||
!f.path.includes('/_converted/')
|
||||
);
|
||||
}
|
||||
|
||||
// Testo usato per il fuzzy-match (nome file)
|
||||
getItemText(file: TFile): string {
|
||||
return file.name;
|
||||
}
|
||||
|
||||
// Mostra thumbnail SVG + nome file invece del solo testo
|
||||
renderSuggestion(match: FuzzyMatch<TFile>, el: HTMLElement): void {
|
||||
const file = match.item;
|
||||
el.addClass('hwm_svg-suggest-item');
|
||||
// Thumbnail SVG tramite resource URL del vault
|
||||
const img = el.createEl('img', { cls: 'hwm_svg-thumb' });
|
||||
img.src = this.app.vault.getResourcePath(file);
|
||||
el.createEl('span', { text: file.name, cls: 'hwm_svg-suggest-name' });
|
||||
}
|
||||
|
||||
// Inserisce ![[path]] al cursore quando l'utente seleziona un file
|
||||
onChooseItem(file: TFile): void {
|
||||
this.editor.replaceSelection(`![[${file.path}]]`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import { App, PluginSettingTab, Setting } from 'obsidian';
|
||||
import type HandwritingPlugin from './main';
|
||||
import { t, setLocale, availableLocales, localeNames } from './i18n';
|
||||
|
||||
// Modalità sfondo: chiaro, scuro o automatico (segue il tema di Obsidian)
|
||||
export type BgMode = 'light' | 'dark' | 'auto';
|
||||
|
|
@ -16,6 +17,7 @@ export interface HandwritingSettings {
|
|||
ocrLanguages: string[]; // lingue per il riconoscimento OCR (codici BCP-47, es. 'it', 'en')
|
||||
geminiApiKey: string; // chiave API Google Gemini per l'OCR
|
||||
debugMode: boolean; // mostra Notice di debug per eventi IME/touch
|
||||
uiLanguage: string; // lingua dell'interfaccia impostazioni ('auto' = segue sistema)
|
||||
}
|
||||
|
||||
// Colori predefiniti per le modalità light e dark
|
||||
|
|
@ -79,6 +81,7 @@ export const DEFAULT_SETTINGS: HandwritingSettings = {
|
|||
ocrLanguages: ['it', 'en'], // italiano e inglese di default
|
||||
geminiApiKey: '',
|
||||
debugMode: false,
|
||||
uiLanguage: 'auto', // default: segue la lingua di sistema di Obsidian
|
||||
};
|
||||
|
||||
// Nome del branch corrente — aggiornare manualmente ad ogni cambio di branch
|
||||
|
|
@ -98,16 +101,35 @@ export class HandwritingSettingTab extends PluginSettingTab {
|
|||
|
||||
containerEl.createEl('h2', { text: 'Handwriting to Markdown' });
|
||||
|
||||
// Riga versione + branch
|
||||
// Riga versione
|
||||
containerEl.createEl('p', {
|
||||
text: `v${this.plugin.manifest.version} — branch: ${PLUGIN_BRANCH}`,
|
||||
text: `v${this.plugin.manifest.version}`,
|
||||
cls: 'setting-item-description',
|
||||
});
|
||||
|
||||
// --- Lingua interfaccia ---
|
||||
new Setting(containerEl)
|
||||
.setName(t('ui_language_name'))
|
||||
.setDesc(t('ui_language_desc'))
|
||||
.addDropdown(drop => {
|
||||
// Prima voce: automatico
|
||||
drop.addOption('auto', t('ui_language_auto'));
|
||||
// Una voce per ogni lingua disponibile nel plugin, con nome nativo
|
||||
availableLocales().forEach(code => drop.addOption(code, localeNames[code] ?? code));
|
||||
drop.setValue(this.plugin.settings.uiLanguage);
|
||||
drop.onChange(async (value) => {
|
||||
this.plugin.settings.uiLanguage = value;
|
||||
await this.plugin.saveSettings();
|
||||
// Aggiorna il dizionario attivo e ridisegna la pagina impostazioni
|
||||
setLocale(value);
|
||||
this.display();
|
||||
});
|
||||
});
|
||||
|
||||
// --- Cartella SVG ---
|
||||
new Setting(containerEl)
|
||||
.setName('Cartella SVG')
|
||||
.setDesc('Cartella nel vault dove vengono salvati i file SVG dei disegni')
|
||||
.setName(t('svg_folder_name'))
|
||||
.setDesc(t('svg_folder_desc'))
|
||||
.addText(text => text
|
||||
.setPlaceholder('_handwriting')
|
||||
.setValue(this.plugin.settings.svgFolder)
|
||||
|
|
@ -118,8 +140,8 @@ export class HandwritingSettingTab extends PluginSettingTab {
|
|||
|
||||
// --- Larghezza canvas ---
|
||||
new Setting(containerEl)
|
||||
.setName('Larghezza canvas')
|
||||
.setDesc('Risoluzione orizzontale del canvas in pixel')
|
||||
.setName(t('canvas_width_name'))
|
||||
.setDesc(t('canvas_width_desc'))
|
||||
.addText(text => text
|
||||
.setValue(String(this.plugin.settings.canvasWidth))
|
||||
.onChange(async (value) => {
|
||||
|
|
@ -132,8 +154,8 @@ export class HandwritingSettingTab extends PluginSettingTab {
|
|||
|
||||
// --- Altezza canvas ---
|
||||
new Setting(containerEl)
|
||||
.setName('Altezza canvas')
|
||||
.setDesc('Risoluzione verticale del canvas in pixel')
|
||||
.setName(t('canvas_height_name'))
|
||||
.setDesc(t('canvas_height_desc'))
|
||||
.addText(text => text
|
||||
.setValue(String(this.plugin.settings.canvasHeight))
|
||||
.onChange(async (value) => {
|
||||
|
|
@ -146,12 +168,12 @@ export class HandwritingSettingTab extends PluginSettingTab {
|
|||
|
||||
// --- Sfondo canvas ---
|
||||
new Setting(containerEl)
|
||||
.setName('Sfondo canvas')
|
||||
.setDesc('Colore di sfondo del riquadro di disegno. "Automatico" segue il tema di Obsidian.')
|
||||
.setName(t('bg_mode_name'))
|
||||
.setDesc(t('bg_mode_desc'))
|
||||
.addDropdown(drop => drop
|
||||
.addOption('auto', 'Automatico (segue tema Obsidian)')
|
||||
.addOption('light', 'Chiaro (bianco)')
|
||||
.addOption('dark', 'Scuro (grigio scuro)')
|
||||
.addOption('auto', t('bg_mode_auto'))
|
||||
.addOption('light', t('bg_mode_light'))
|
||||
.addOption('dark', t('bg_mode_dark'))
|
||||
.setValue(this.plugin.settings.bgMode)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.bgMode = value as BgMode;
|
||||
|
|
@ -162,8 +184,8 @@ export class HandwritingSettingTab extends PluginSettingTab {
|
|||
|
||||
// --- Chiave API Gemini ---
|
||||
new Setting(containerEl)
|
||||
.setName('Chiave API Gemini')
|
||||
.setDesc('Necessaria per il riconoscimento OCR della scrittura a mano. Ottienila da Google AI Studio (aistudio.google.com).')
|
||||
.setName(t('gemini_key_name'))
|
||||
.setDesc(t('gemini_key_desc'))
|
||||
.addText(text => {
|
||||
text
|
||||
.setPlaceholder('AIza...')
|
||||
|
|
@ -178,11 +200,8 @@ export class HandwritingSettingTab extends PluginSettingTab {
|
|||
|
||||
// --- Lingue OCR ---
|
||||
new Setting(containerEl)
|
||||
.setName('Lingue OCR')
|
||||
.setDesc(
|
||||
'Codici lingua BCP-47 separati da virgola (es. "it, en, fr"). ' +
|
||||
'Usati dal riconoscitore di scrittura su Android.'
|
||||
)
|
||||
.setName(t('ocr_langs_name'))
|
||||
.setDesc(t('ocr_langs_desc'))
|
||||
.addText(text => text
|
||||
// Mostra l'array come stringa "it, en"
|
||||
.setValue(this.plugin.settings.ocrLanguages.join(', '))
|
||||
|
|
@ -197,70 +216,66 @@ export class HandwritingSettingTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
// --- Modalità debug ---
|
||||
new Setting(containerEl)
|
||||
.setName('Modalità debug')
|
||||
.setDesc('Mostra notifiche in tempo reale per eventi IME/touch (utile per diagnosticare problemi su Android).')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.debugMode)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.debugMode = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
// --- Modalità debug (nascosta dall'UI, funzionalità mantenuta) ---
|
||||
|
||||
// --- Riferimento keyword OCR (sezione espandibile) ---
|
||||
// NOTA SVILUPPATORI: se aggiungi una keyword in md-parser.ts, aggiornala anche qui!
|
||||
const details = containerEl.createEl('details', { cls: 'hwm_keyword-ref' });
|
||||
details.createEl('summary', {
|
||||
text: 'Keyword riconosciute dal parser OCR',
|
||||
text: t('keywords_summary'),
|
||||
cls: 'hwm_keyword-summary',
|
||||
});
|
||||
details.createEl('p', {
|
||||
text: 'Scrivi queste keyword nel disegno per generare la struttura markdown corrispondente. Tutte sono case-insensitive (//h1 = //H1). Il contenuto segue dopo la keyword separato da uno spazio.',
|
||||
text: t('keywords_desc'),
|
||||
cls: 'setting-item-description',
|
||||
});
|
||||
|
||||
// Tabella keyword: [nome, sintassi, output]
|
||||
// Le parole segnaposto (titolo, testo, ecc.) vengono tradotte via t()
|
||||
const T = t('kw_title'); // es. "Titolo" / "Title"
|
||||
const X = t('kw_text'); // es. "testo" / "text"
|
||||
const FN = t('kw_footnote'); // es. "nota a piè pagina" / "footnote"
|
||||
const W = t('kw_word'); // es. "parola" / "word"
|
||||
const KEYWORDS: [string, string, string][] = [
|
||||
['//H1', '//H1 Titolo', '# Titolo'],
|
||||
['//H2', '//H2 Titolo', '## Titolo'],
|
||||
['//H3', '//H3 Titolo', '### Titolo'],
|
||||
['//H4', '//H4 Titolo', '#### Titolo'],
|
||||
['//B / //BOLD', '//B testo', '**testo**'],
|
||||
['//I', '//I testo', '*testo*'],
|
||||
['//BI', '//BI testo', '***testo***'],
|
||||
['//S / //STRIKE', '//S testo', '~~testo~~'],
|
||||
['//HL', '//HL testo', '==testo=='],
|
||||
['//CODE', '//CODE testo', '`testo`'],
|
||||
['//H1', `//H1 ${T}`, `# ${T}`],
|
||||
['//H2', `//H2 ${T}`, `## ${T}`],
|
||||
['//H3', `//H3 ${T}`, `### ${T}`],
|
||||
['//H4', `//H4 ${T}`, `#### ${T}`],
|
||||
['//B / //BOLD', `//B ${X}`, `**${X}**`],
|
||||
['//I', `//I ${X}`, `*${X}*`],
|
||||
['//BI', `//BI ${X}`, `***${X}***`],
|
||||
['//S / //STRIKE', `//S ${X}`, `~~${X}~~`],
|
||||
['//HL', `//HL ${X}`, `==${X}==`],
|
||||
['//CODE', `//CODE ${X}`, `\`${X}\``],
|
||||
['//CODEBLOCK', '//CODEBLOCK js', '```js\n...\n```'],
|
||||
['//LIST', '//LIST a, b, c', '- a\n- b\n- c'],
|
||||
['//NUMLIST', '//NUMLIST a, b, c', '1. a\n2. b\n3. c'],
|
||||
['//CHECK', '//CHECK a, b, c', '- [ ] a\n- [ ] b\n- [ ] c'],
|
||||
['//TABLE', '//TABLE Col1, Col2', '| Col1 | Col2 |\n|---|---|\n| ... |'],
|
||||
['//NOTE', '//NOTE testo', '> [!NOTE]\n> testo'],
|
||||
['//WARN', '//WARN testo', '> [!WARNING]\n> testo'],
|
||||
['//TIP', '//TIP testo', '> [!TIP]\n> testo'],
|
||||
['//INFO', '//INFO testo', '> [!INFO]\n> testo'],
|
||||
['//ERROR', '//ERROR testo', '> [!ERROR]\n> testo'],
|
||||
['//IMPORTANT', '//IMPORTANT testo', '> [!IMPORTANT]\n> testo'],
|
||||
['//QUOTE', '//QUOTE testo', '> testo'],
|
||||
['//LINK', '//LINK testo, url', '[testo](url)'],
|
||||
['//NOTE', `//NOTE ${X}`, `> [!NOTE]\n> ${X}`],
|
||||
['//WARN', `//WARN ${X}`, `> [!WARNING]\n> ${X}`],
|
||||
['//TIP', `//TIP ${X}`, `> [!TIP]\n> ${X}`],
|
||||
['//INFO', `//INFO ${X}`, `> [!INFO]\n> ${X}`],
|
||||
['//ERROR', `//ERROR ${X}`, `> [!ERROR]\n> ${X}`],
|
||||
['//IMPORTANT', `//IMPORTANT ${X}`, `> [!IMPORTANT]\n> ${X}`],
|
||||
['//QUOTE', `//QUOTE ${X}`, `> ${X}`],
|
||||
['//LINK', `//LINK ${X}, url`, `[${X}](url)`],
|
||||
['//IMG', '//IMG alt, url', ''],
|
||||
['//HR / //SEP', '//HR', '---'],
|
||||
['//FN', '//FN nota a piè pagina', '[^1]: nota a piè pagina'],
|
||||
['//FN', `//FN ${FN}`, `[^1]: ${FN}`],
|
||||
['//MATH', '//MATH formula', '$formula$'],
|
||||
['//MATHBLOCK', '//MATHBLOCK', '$$\n...\n$$'],
|
||||
['//TAG', '//TAG parola', '#parola'],
|
||||
['//TAG', `//TAG ${W}`, `#${W}`],
|
||||
['//DATE', '//DATE', 'YYYY-MM-DD'],
|
||||
['//TIME', '//TIME', 'HH:mm'],
|
||||
['//DATETIME', '//DATETIME', 'YYYY-MM-DD HH:mm'],
|
||||
['//INDENT', '//INDENT testo', ' testo'],
|
||||
['//INDENT', `//INDENT ${X}`, ` ${X}`],
|
||||
];
|
||||
|
||||
const table = details.createEl('table', { cls: 'hwm_keyword-table' });
|
||||
const thead = table.createEl('thead');
|
||||
const hrow = thead.createEl('tr');
|
||||
['Keyword', 'Sintassi', 'Output'].forEach(h => hrow.createEl('th', { text: h }));
|
||||
[t('keywords_col_keyword'), t('keywords_col_syntax'), t('keywords_col_output')].forEach(h => hrow.createEl('th', { text: h }));
|
||||
const tbody = table.createEl('tbody');
|
||||
for (const [name, syntax, output] of KEYWORDS) {
|
||||
const row = tbody.createEl('tr');
|
||||
|
|
|
|||
|
|
@ -7,11 +7,14 @@
|
|||
|
||||
import { Point, Stroke } from './drawing-canvas';
|
||||
|
||||
// Genera ID univoco per nuovi disegni (timestamp + random)
|
||||
// Genera ID univoco per nuovi disegni nel formato HTMD_YYYYMMDDHHMMSS_XXXX
|
||||
export function generateId(): string {
|
||||
const ts = Date.now().toString(36);
|
||||
const rnd = Math.random().toString(36).substring(2, 6);
|
||||
return `hw_${ts}_${rnd}`;
|
||||
const now = new Date();
|
||||
const p = (n: number) => String(n).padStart(2, '0');
|
||||
const date = `${now.getFullYear()}${p(now.getMonth() + 1)}${p(now.getDate())}`;
|
||||
const time = `${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}`;
|
||||
const rnd = Math.random().toString(36).substring(2, 6).toUpperCase();
|
||||
return `HTMD_${date}${time}_${rnd}`;
|
||||
}
|
||||
|
||||
// Converte un array di punti in un attributo SVG path "d"
|
||||
|
|
|
|||
|
|
@ -584,9 +584,67 @@
|
|||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Topbar del DrawingModal: centra la toolbar (non c'è backBtn come nell'EditorView) */
|
||||
/* Topbar centrata: usata sia nel DrawingModal (Windows) che in DrawingEditorView (Android).
|
||||
position: relative serve ad ancorare il bottone chiudi (position: absolute) */
|
||||
.hwm_editor-topbar--modal {
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Bottone X chiudi: ancorato al bordo destro del topbar, indipendente dal toolbar centrato */
|
||||
.hwm_close-btn {
|
||||
position: absolute !important;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
/* Nasconde la X nativa di Obsidian nel DrawingModal: la chiusura avviene dal bottone X in toolbar */
|
||||
.hwm_modal .modal-close-button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Overlay di conferma inline — no Modal annidato, nessun furto di focus */
|
||||
.hwm_confirm-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
z-index: 100; /* sopra il pannello portale (z-index: 10) */
|
||||
border-radius: inherit;
|
||||
pointer-events: auto !important; /* override pointer-events:none dello span padre */
|
||||
}
|
||||
.hwm_confirm-overlay button {
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
.hwm_confirm-msg {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* --- Suggest SVG reference: thumbnail + nome file --- */
|
||||
.hwm_svg-suggest-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.hwm_svg-thumb {
|
||||
width: 80px;
|
||||
height: 48px;
|
||||
object-fit: contain;
|
||||
border-radius: 4px;
|
||||
background: var(--background-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.hwm_svg-suggest-name {
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* --- Scroll container per il canvas nell'editor --- */
|
||||
|
|
|
|||
Loading…
Reference in a new issue