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 23:00:06 +00:00
// eslint-disable-next-line @typescript-eslint/no-misused-promises -- forEach callback is sync but DropdownComponent.addOption return type triggers the rule; Promise result intentionally ignored
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 23:00:06 +00:00
. setPlaceholder ( t ( 'gemini_key_placeholder' ) )
2026-03-15 12:25:50 +00:00
. 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 23:00:06 +00:00
. setPlaceholder ( t ( 'ocr_langs_placeholder' ) )
2026-03-15 12:25:50 +00:00
. 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
}
}