2026-03-15 12:25:50 +00:00
|
|
|
/* =============================================
|
|
|
|
|
Settings — Configurazione del plugin
|
|
|
|
|
============================================= */
|
|
|
|
|
|
|
|
|
|
import { App, PluginSettingTab, Setting } from 'obsidian';
|
|
|
|
|
import type HandwritingPlugin from './main';
|
2026-03-24 17:28:50 +00:00
|
|
|
import { t, setLocale, availableLocales, localeNames } from './i18n';
|
2026-03-15 12:25:50 +00:00
|
|
|
|
2026-03-23 14:15:12 +00:00
|
|
|
// Modalità sfondo: chiaro, scuro o automatico (segue il tema di Obsidian)
|
|
|
|
|
export type BgMode = 'light' | 'dark' | 'auto';
|
2026-03-15 12:25:50 +00:00
|
|
|
|
|
|
|
|
export interface HandwritingSettings {
|
2026-03-22 17:23:41 +00:00
|
|
|
svgFolder: string; // cartella dove salvare i file SVG
|
|
|
|
|
canvasWidth: number; // larghezza interna del canvas (px)
|
|
|
|
|
canvasHeight: number; // altezza interna del canvas (px)
|
|
|
|
|
bgMode: BgMode; // modalità sfondo
|
|
|
|
|
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
|
2026-03-24 17:28:50 +00:00
|
|
|
uiLanguage: string; // lingua dell'interfaccia impostazioni ('auto' = segue sistema)
|
2026-03-15 12:25:50 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-23 14:15:12 +00:00
|
|
|
// Colori predefiniti per le modalità light e dark
|
|
|
|
|
export const BG_COLORS: Record<'light' | 'dark', string> = {
|
2026-03-15 12:25:50 +00:00
|
|
|
light: '#ffffff',
|
|
|
|
|
dark: '#1e1e1e',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Colore righe adattato allo sfondo
|
2026-03-23 14:15:12 +00:00
|
|
|
export const LINE_COLORS: Record<'light' | 'dark', string> = {
|
2026-03-15 12:25:50 +00:00
|
|
|
light: '#e0e0e0',
|
|
|
|
|
dark: '#3a3a3a',
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-23 14:15:12 +00:00
|
|
|
// Risolve 'auto' al tema effettivo leggendo la classe Obsidian sul body
|
|
|
|
|
function resolveAutoMode(): 'light' | 'dark' {
|
|
|
|
|
return document.body.classList.contains('theme-dark') ? 'dark' : 'light';
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 12:25:50 +00:00
|
|
|
// Ritorna il colore sfondo effettivo in base alle impostazioni
|
|
|
|
|
export function getEffectiveBgColor(settings: HandwritingSettings): string {
|
2026-03-23 14:15:12 +00:00
|
|
|
const mode = settings.bgMode === 'auto' ? resolveAutoMode() : settings.bgMode;
|
|
|
|
|
return BG_COLORS[mode];
|
2026-03-15 12:25:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ritorna il colore righe effettivo in base alle impostazioni
|
|
|
|
|
export function getEffectiveLineColor(settings: HandwritingSettings): string {
|
2026-03-23 14:15:12 +00:00
|
|
|
const mode = settings.bgMode === 'auto' ? resolveAutoMode() : settings.bgMode;
|
|
|
|
|
return LINE_COLORS[mode];
|
2026-03-15 12:25:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mappa colori chiari ↔ scuri per adattare i tratti al cambio tema.
|
|
|
|
|
// Quando l'utente cambia tema, i tratti con colori della palette opposta
|
|
|
|
|
// vengono rimappati ai corrispondenti colori leggibili.
|
2026-03-25 11:56:22 +00:00
|
|
|
// Esportati per essere usati da editor-view.ts senza ridefinirli.
|
|
|
|
|
export const LIGHT_COLORS = ['#000000', '#1e40af', '#dc2626', '#16a34a'];
|
|
|
|
|
export const DARK_COLORS = ['#ffffff', '#60a5fa', '#f87171', '#4ade80'];
|
|
|
|
|
|
|
|
|
|
// Risolve se il tema è scuro tenendo conto di 'auto' (legge la classe Obsidian sul body)
|
|
|
|
|
export function resolveIsDark(bgMode: string): boolean {
|
|
|
|
|
if (bgMode === 'auto') return document.body.classList.contains('theme-dark');
|
|
|
|
|
return bgMode === 'dark';
|
|
|
|
|
}
|
2026-03-15 12:25:50 +00:00
|
|
|
|
|
|
|
|
// Rimappa il colore di un tratto in base al tema corrente
|
|
|
|
|
export function remapStrokeColor(color: string, bgMode: BgMode): string {
|
2026-03-23 14:15:12 +00:00
|
|
|
// 'auto' viene risolto al tema Obsidian attuale al momento della chiamata
|
|
|
|
|
const mode = bgMode === 'auto' ? resolveAutoMode() : bgMode;
|
2026-03-15 12:25:50 +00:00
|
|
|
const c = color.toLowerCase();
|
2026-03-23 14:15:12 +00:00
|
|
|
if (mode === 'dark') {
|
2026-03-15 12:25:50 +00:00
|
|
|
// Se il tratto ha un colore "chiaro" (della palette light), mappalo al corrispondente dark
|
|
|
|
|
const idx = LIGHT_COLORS.indexOf(c);
|
|
|
|
|
if (idx >= 0) return DARK_COLORS[idx]!;
|
2026-03-23 14:15:12 +00:00
|
|
|
} else {
|
2026-03-15 12:25:50 +00:00
|
|
|
// Viceversa: colori dark → light
|
|
|
|
|
const idx = DARK_COLORS.indexOf(c);
|
|
|
|
|
if (idx >= 0) return LIGHT_COLORS[idx]!;
|
|
|
|
|
}
|
2026-03-23 14:15:12 +00:00
|
|
|
// Colori non in palette: lascia invariato
|
2026-03-15 12:25:50 +00:00
|
|
|
return color;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const DEFAULT_SETTINGS: HandwritingSettings = {
|
|
|
|
|
svgFolder: '_handwriting',
|
|
|
|
|
canvasWidth: 800,
|
|
|
|
|
canvasHeight: 300,
|
2026-03-23 14:15:12 +00:00
|
|
|
bgMode: 'auto', // default: segue automaticamente il tema di Obsidian
|
2026-03-15 12:25:50 +00:00
|
|
|
ocrLanguages: ['it', 'en'], // italiano e inglese di default
|
|
|
|
|
geminiApiKey: '',
|
|
|
|
|
debugMode: false,
|
2026-03-24 17:28:50 +00:00
|
|
|
uiLanguage: 'auto', // default: segue la lingua di sistema di Obsidian
|
2026-03-15 12:25:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export class HandwritingSettingTab extends PluginSettingTab {
|
|
|
|
|
plugin: HandwritingPlugin;
|
|
|
|
|
|
|
|
|
|
constructor(app: App, plugin: HandwritingPlugin) {
|
|
|
|
|
super(app, plugin);
|
|
|
|
|
this.plugin = plugin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
display(): void {
|
|
|
|
|
const { containerEl } = this;
|
|
|
|
|
containerEl.empty();
|
2026-03-25 11:56:22 +00:00
|
|
|
// Classe per scopare i CSS responsive delle impostazioni
|
|
|
|
|
containerEl.addClass('hwm_settings');
|
2026-03-15 12:25:50 +00:00
|
|
|
|
2026-03-27 23:54:31 +00:00
|
|
|
new Setting(containerEl).setName('Handwriting to Markdown').setHeading();
|
2026-03-15 12:25:50 +00:00
|
|
|
|
2026-03-24 17:28:50 +00:00
|
|
|
// Riga versione
|
2026-03-22 17:59:50 +00:00
|
|
|
containerEl.createEl('p', {
|
2026-03-24 17:28:50 +00:00
|
|
|
text: `v${this.plugin.manifest.version}`,
|
2026-03-22 17:59:50 +00:00
|
|
|
cls: 'setting-item-description',
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-24 17:28:50 +00:00
|
|
|
// --- 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
|
2026-03-28 18:03:17 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
2026-03-24 17:28:50 +00:00
|
|
|
availableLocales().forEach(code => drop.addOption(code, localeNames[code] ?? code));
|
|
|
|
|
drop.setValue(this.plugin.settings.uiLanguage);
|
2026-03-28 18:03:17 +00:00
|
|
|
drop.onChange((value) => { void (async () => {
|
2026-03-24 17:28:50 +00:00
|
|
|
this.plugin.settings.uiLanguage = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
// Aggiorna il dizionario attivo e ridisegna la pagina impostazioni
|
|
|
|
|
setLocale(value);
|
|
|
|
|
this.display();
|
2026-03-28 18:03:17 +00:00
|
|
|
})(); });
|
2026-03-24 17:28:50 +00:00
|
|
|
});
|
|
|
|
|
|
2026-03-15 12:25:50 +00:00
|
|
|
// --- Cartella SVG ---
|
|
|
|
|
new Setting(containerEl)
|
2026-03-24 17:28:50 +00:00
|
|
|
.setName(t('svg_folder_name'))
|
|
|
|
|
.setDesc(t('svg_folder_desc'))
|
2026-03-15 12:25:50 +00:00
|
|
|
.addText(text => text
|
|
|
|
|
.setPlaceholder('_handwriting')
|
|
|
|
|
.setValue(this.plugin.settings.svgFolder)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.svgFolder = value || '_handwriting';
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// --- Larghezza canvas ---
|
|
|
|
|
new Setting(containerEl)
|
2026-03-24 17:28:50 +00:00
|
|
|
.setName(t('canvas_width_name'))
|
|
|
|
|
.setDesc(t('canvas_width_desc'))
|
2026-03-15 12:25:50 +00:00
|
|
|
.addText(text => text
|
|
|
|
|
.setValue(String(this.plugin.settings.canvasWidth))
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
const n = parseInt(value);
|
|
|
|
|
if (!isNaN(n) && n > 100) {
|
|
|
|
|
this.plugin.settings.canvasWidth = n;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// --- Altezza canvas ---
|
|
|
|
|
new Setting(containerEl)
|
2026-03-24 17:28:50 +00:00
|
|
|
.setName(t('canvas_height_name'))
|
|
|
|
|
.setDesc(t('canvas_height_desc'))
|
2026-03-15 12:25:50 +00:00
|
|
|
.addText(text => text
|
|
|
|
|
.setValue(String(this.plugin.settings.canvasHeight))
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
const n = parseInt(value);
|
|
|
|
|
if (!isNaN(n) && n > 50) {
|
|
|
|
|
this.plugin.settings.canvasHeight = n;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// --- Sfondo canvas ---
|
|
|
|
|
new Setting(containerEl)
|
2026-03-24 17:28:50 +00:00
|
|
|
.setName(t('bg_mode_name'))
|
|
|
|
|
.setDesc(t('bg_mode_desc'))
|
2026-03-15 12:25:50 +00:00
|
|
|
.addDropdown(drop => drop
|
2026-03-24 17:28:50 +00:00
|
|
|
.addOption('auto', t('bg_mode_auto'))
|
|
|
|
|
.addOption('light', t('bg_mode_light'))
|
|
|
|
|
.addOption('dark', t('bg_mode_dark'))
|
2026-03-15 12:25:50 +00:00
|
|
|
.setValue(this.plugin.settings.bgMode)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.bgMode = value as BgMode;
|
|
|
|
|
await this.plugin.saveSettings();
|
2026-03-23 01:03:11 +00:00
|
|
|
// Notifica pannelli (dark class) e SVG attivi (remap colori)
|
|
|
|
|
this.plugin.notifyBgModeChange();
|
2026-03-15 12:25:50 +00:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// --- Chiave API Gemini ---
|
|
|
|
|
new Setting(containerEl)
|
2026-03-24 17:28:50 +00:00
|
|
|
.setName(t('gemini_key_name'))
|
|
|
|
|
.setDesc(t('gemini_key_desc'))
|
2026-03-15 12:25:50 +00:00
|
|
|
.addText(text => {
|
|
|
|
|
text
|
2026-03-28 18:03:17 +00:00
|
|
|
// eslint-disable-next-line obsidianmd/ui/sentence-case
|
2026-03-15 12:25:50 +00:00
|
|
|
.setPlaceholder('AIza...')
|
|
|
|
|
.setValue(this.plugin.settings.geminiApiKey)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.geminiApiKey = value.trim();
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
});
|
|
|
|
|
// Maschera il testo come password per nascondere la chiave
|
|
|
|
|
text.inputEl.type = 'password';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// --- Lingue OCR ---
|
|
|
|
|
new Setting(containerEl)
|
2026-03-24 17:28:50 +00:00
|
|
|
.setName(t('ocr_langs_name'))
|
|
|
|
|
.setDesc(t('ocr_langs_desc'))
|
2026-03-15 12:25:50 +00:00
|
|
|
.addText(text => text
|
|
|
|
|
// Mostra l'array come stringa "it, en"
|
|
|
|
|
.setValue(this.plugin.settings.ocrLanguages.join(', '))
|
2026-03-28 18:03:17 +00:00
|
|
|
// eslint-disable-next-line obsidianmd/ui/sentence-case
|
2026-03-15 12:25:50 +00:00
|
|
|
.setPlaceholder('it, en')
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
// Parsa la stringa in array, rimuovendo spazi e voci vuote
|
|
|
|
|
const langs = value
|
|
|
|
|
.split(',')
|
|
|
|
|
.map(l => l.trim())
|
|
|
|
|
.filter(l => l.length > 0);
|
|
|
|
|
this.plugin.settings.ocrLanguages = langs.length > 0 ? langs : ['it', 'en'];
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
}));
|
|
|
|
|
|
2026-03-24 17:28:50 +00:00
|
|
|
// --- Modalità debug (nascosta dall'UI, funzionalità mantenuta) ---
|
2026-03-23 21:21:57 +00:00
|
|
|
|
|
|
|
|
// --- 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', {
|
2026-03-24 17:28:50 +00:00
|
|
|
text: t('keywords_summary'),
|
2026-03-23 21:21:57 +00:00
|
|
|
cls: 'hwm_keyword-summary',
|
|
|
|
|
});
|
|
|
|
|
details.createEl('p', {
|
2026-03-24 17:28:50 +00:00
|
|
|
text: t('keywords_desc'),
|
2026-03-23 21:21:57 +00:00
|
|
|
cls: 'setting-item-description',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Tabella keyword: [nome, sintassi, output]
|
2026-03-24 17:28:50 +00:00
|
|
|
// 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"
|
2026-03-23 21:21:57 +00:00
|
|
|
const KEYWORDS: [string, string, string][] = [
|
2026-03-24 17:28:50 +00:00
|
|
|
['//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}\``],
|
2026-03-23 23:49:51 +00:00
|
|
|
['//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| ... |'],
|
2026-03-24 17:28:50 +00:00
|
|
|
['//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)`],
|
2026-03-23 23:49:51 +00:00
|
|
|
['//IMG', '//IMG alt, url', ''],
|
|
|
|
|
['//HR / //SEP', '//HR', '---'],
|
2026-03-24 17:28:50 +00:00
|
|
|
['//FN', `//FN ${FN}`, `[^1]: ${FN}`],
|
2026-03-23 23:49:51 +00:00
|
|
|
['//MATH', '//MATH formula', '$formula$'],
|
|
|
|
|
['//MATHBLOCK', '//MATHBLOCK', '$$\n...\n$$'],
|
2026-03-24 17:28:50 +00:00
|
|
|
['//TAG', `//TAG ${W}`, `#${W}`],
|
2026-03-23 23:49:51 +00:00
|
|
|
['//DATE', '//DATE', 'YYYY-MM-DD'],
|
|
|
|
|
['//TIME', '//TIME', 'HH:mm'],
|
|
|
|
|
['//DATETIME', '//DATETIME', 'YYYY-MM-DD HH:mm'],
|
2026-03-24 17:28:50 +00:00
|
|
|
['//INDENT', `//INDENT ${X}`, ` ${X}`],
|
2026-03-23 21:21:57 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const table = details.createEl('table', { cls: 'hwm_keyword-table' });
|
|
|
|
|
const thead = table.createEl('thead');
|
|
|
|
|
const hrow = thead.createEl('tr');
|
2026-03-24 17:28:50 +00:00
|
|
|
[t('keywords_col_keyword'), t('keywords_col_syntax'), t('keywords_col_output')].forEach(h => hrow.createEl('th', { text: h }));
|
2026-03-23 21:21:57 +00:00
|
|
|
const tbody = table.createEl('tbody');
|
|
|
|
|
for (const [name, syntax, output] of KEYWORDS) {
|
|
|
|
|
const row = tbody.createEl('tr');
|
|
|
|
|
row.createEl('td', { text: name, cls: 'hwm_kw-name' });
|
|
|
|
|
row.createEl('td').createEl('code', { text: syntax });
|
|
|
|
|
// Mostra il testo dell'output su più righe se contiene \n
|
|
|
|
|
const outTd = row.createEl('td');
|
|
|
|
|
output.split('\n').forEach((ln, idx) => {
|
|
|
|
|
if (idx > 0) outTd.createEl('br');
|
|
|
|
|
outTd.createEl('code', { text: ln });
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-03-15 12:25:50 +00:00
|
|
|
}
|
|
|
|
|
}
|