From b1267debd522ed8a4ae28c7cd2ad7b5466cf7618 Mon Sep 17 00:00:00 2001 From: gabriele-cusato Date: Mon, 23 Mar 2026 18:41:59 +0100 Subject: [PATCH] aggiunte anche voci al menu con i 3 puntini --- .claude/settings.local.json | 4 +- HandTranscriptMd/src/embed.ts | 87 +++++++++++++++++++---------------- HandTranscriptMd/src/main.ts | 72 ++++++++++++++++++++++++++++- 3 files changed, 122 insertions(+), 41 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 7246c59..fbed6ee 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -7,7 +7,9 @@ "WebFetch(domain:css-tricks.com)", "WebFetch(domain:chromium.googlesource.com)", "WebFetch(domain:github.com)", - "WebFetch(domain:developer.chrome.com)" + "WebFetch(domain:developer.chrome.com)", + "Bash(\"C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe\" --login -c \"cd 'C:/Projects/pluginObsidian/handWrittenMarkdownConverter/HandTranscriptMd' && bash deploy.sh && bash cloudDeploy.sh\" 2>&1)", + "Bash(\"C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe\" --login -c \"cd 'C:/Projects/pluginObsidian/handWrittenMarkdownConverter/HandTranscriptMd' && bash deploy.sh\" 2>&1)" ] } } diff --git a/HandTranscriptMd/src/embed.ts b/HandTranscriptMd/src/embed.ts index a615a6d..ade9374 100644 --- a/HandTranscriptMd/src/embed.ts +++ b/HandTranscriptMd/src/embed.ts @@ -339,23 +339,19 @@ async function doConvertWiki( sourcePath: string, plugin: HandwritingPlugin ) { - try { - new Notice('Riconoscimento in corso…'); - const parser = new DOMParser(); - const svgEl = parser.parseFromString(svgContent, 'image/svg+xml') - .documentElement as unknown as SVGElement; - const base64 = await svgToBase64Png(svgEl); - const recognizer = getRecognizer(plugin.settings.geminiApiKey, plugin.settings.ocrLanguages); - const rawText = await recognizer.recognize(base64); - if (!rawText.trim()) { new Notice('Nessun testo riconosciuto'); return; } - - const markdown = parseMarkdown(rawText); - await archiveSvgByPath(svgPath, plugin); - await replaceWikiEmbedWithMarkdown(svgPath, markdown, sourcePath, plugin); - new Notice('Conversione completata!'); - } catch (e: unknown) { - new Notice('Errore OCR: ' + (e instanceof Error ? e.message : String(e))); - } + // Lancia eccezione in caso di errore (il chiamante decide se mostrare Notice o propagare) + new Notice('Riconoscimento in corso…'); + const parser = new DOMParser(); + const svgEl = parser.parseFromString(svgContent, 'image/svg+xml') + .documentElement as unknown as SVGElement; + const base64 = await svgToBase64Png(svgEl); + const recognizer = getRecognizer(plugin.settings.geminiApiKey, plugin.settings.ocrLanguages); + const rawText = await recognizer.recognize(base64); + if (!rawText.trim()) throw new Error('Nessun testo riconosciuto'); + const markdown = parseMarkdown(rawText); + await archiveSvgByPath(svgPath, plugin); + await replaceWikiEmbedWithMarkdown(svgPath, markdown, sourcePath, plugin); + new Notice('Conversione completata!'); } /* ============================================= @@ -660,14 +656,6 @@ function createPortalPanel( // --- Bottone converti in Markdown --- const convertBtn = createPanelBtn(panel, 'file-text', 'Converti in Markdown'); - convertBtn.addEventListener('click', async () => { - const { strokes, svgContent } = await loadSvgData(svgPath, plugin); - if (!svgContent || strokes.length === 0) { - new Notice('Nessun tratto da convertire'); - return; - } - await doConvertWiki(svgContent, svgPath, sourcePath, plugin); - }); // --- Bottone comprimi/espandi --- // Usa height + overflow:hidden sul container (non max-height sull'): @@ -676,20 +664,6 @@ function createPortalPanel( // anche da compresso (collapsedHeight è sempre >> 6px + altezza pannello). const collapseBtn = createPanelBtn(panel, 'chevron-up', 'Comprimi'); collapseBtn.classList.add('hwm_collapse-btn'); - collapseBtn.addEventListener('click', () => { - isExpanded = !isExpanded; - if (isExpanded) { - container.style.height = ''; - container.style.overflow = ''; - collapseBtn.classList.remove('hwm_rotated'); - collapseBtn.title = 'Comprimi'; - } else { - container.style.height = collapsedHeight + 'px'; - container.style.overflow = 'hidden'; - collapseBtn.classList.add('hwm_rotated'); - collapseBtn.title = 'Espandi'; - } - }); // --- Bottone elimina --- const deleteBtn = createPanelBtn(panel, 'x', 'Elimina riquadro'); @@ -699,6 +673,41 @@ function createPortalPanel( await removeWikiEmbed(svgPath, sourcePath, plugin); }); + // --- Funzioni condivise: usate dai bottoni e dal menu globale (⋮ Obsidian) --- + const doExpand = () => { + isExpanded = true; + container.style.height = ''; + container.style.overflow = ''; + collapseBtn.classList.remove('hwm_rotated'); + collapseBtn.title = 'Comprimi'; + }; + const doCollapse = () => { + isExpanded = false; + container.style.height = collapsedHeight + 'px'; + container.style.overflow = 'hidden'; + collapseBtn.classList.add('hwm_rotated'); + collapseBtn.title = 'Espandi'; + }; + // Carica SVG e chiama doConvertWiki (che lancia eccezione in caso di errore) + const doConvertAction = async () => { + const { strokes, svgContent } = await loadSvgData(svgPath, plugin); + if (!svgContent || strokes.length === 0) throw new Error('Nessun tratto da convertire'); + await doConvertWiki(svgContent, svgPath, sourcePath, plugin); + }; + + collapseBtn.addEventListener('click', () => { + if (isExpanded) doCollapse(); else doExpand(); + }); + convertBtn.addEventListener('click', async () => { + // Bottone singolo: mostra Notice in caso di errore senza propagare + try { await doConvertAction(); } + catch (e: unknown) { new Notice('Errore OCR: ' + (e instanceof Error ? e.message : String(e))); } + }); + + // Registra le azioni nel plugin per il menu "⋮ Espandi/Collassa/Converti tutti" + plugin.embedActions.set(embedId, { expand: doExpand, collapse: doCollapse, convert: doConvertAction, container, sourcePath }); + plugin.register(() => plugin.embedActions.delete(embedId)); + // Layout-change: su Mobile nasconde il pannello quando la tab editor è aperta const onLayoutChange = () => { if (!container.isConnected) { diff --git a/HandTranscriptMd/src/main.ts b/HandTranscriptMd/src/main.ts index 051be73..865cec5 100644 --- a/HandTranscriptMd/src/main.ts +++ b/HandTranscriptMd/src/main.ts @@ -7,7 +7,7 @@ - Tab impostazioni ============================================= */ -import { Plugin } from 'obsidian'; +import { Plugin, TFile, Notice } from 'obsidian'; import { DEFAULT_SETTINGS, HandwritingSettings, HandwritingSettingTab } from './settings'; import { registerEmbed, insertHandwritingBlock } from './embed'; import { VIEW_TYPE_HANDWRITING, DrawingEditorView } from './editor-view'; @@ -24,6 +24,15 @@ export default class HandwritingPlugin extends Plugin { // Callback notificate quando l'utente cambia bgMode nelle impostazioni public bgModeListeners = new Set<(bgMode: string) => void>(); + // Mappa embedId → azioni (expand/collapse/convert): usata dal menu "⋮" di Obsidian + public embedActions = new Map void; + collapse: () => void; + convert: () => Promise; + container: HTMLElement; + sourcePath: string; + }>(); + // Invocato dall'editor tab dopo ogni salvataggio per aggiornare la preview inline refreshPreview(id: string, svgContent: string) { this.previewCallbacks.get(id)?.(svgContent); @@ -68,6 +77,67 @@ export default class HandwritingPlugin extends Plugin { // Tab impostazioni this.addSettingTab(new HandwritingSettingTab(this.app, this)); + + // Voci nel menu "⋮" (tre puntini) di Obsidian per operazioni su tutti i disegni. + // Vengono aggiunte con setSection('danger') e poi spostate prima di "Elimina file" + // tramite (menu as any).items — l'unico modo per posizionarle nell'ultima sezione + // sopra Delete senza usare API private più instabili. + this.registerEvent( + 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') + .setIcon('chevrons-down') + .setSection('danger') + .onClick(() => { + this.getActiveEmbeds(file.path).forEach(a => a.expand()); + }) + ); + menu.addItem(item => item + .setTitle('Collassa tutti i disegni') + .setIcon('chevrons-up') + .setSection('danger') + .onClick(() => { + this.getActiveEmbeds(file.path).forEach(a => a.collapse()); + }) + ); + menu.addItem(item => item + .setTitle('Converti tutti i disegni in testo') + .setIcon('file-text') + .setSection('danger') + .onClick(async () => { + try { + // Sequenziale: si ferma al primo errore + for (const actions of this.getActiveEmbeds(file.path)) { + await actions.convert(); + } + } catch (e: unknown) { + new Notice('Errore nella conversione: ' + (e instanceof Error ? e.message : String(e))); + } + }) + ); + // Sposta le 3 voci appena aggiunte prima del primo item 'danger' esistente + // (cioè prima di "Elimina file"), in modo che compaiano sopra di esso. + const items: Array<{ section: string }> = (menu as any).items; + const added = items.splice(items.length - 3, 3); + const firstDangerIdx = items.findIndex(i => i.section === 'danger'); + items.splice(firstDangerIdx >= 0 ? firstDangerIdx : items.length, 0, ...added); + }) + ); + } + + // Restituisce gli embed attivi (container nel DOM) appartenenti al file indicato. + // Rimuove dalla mappa gli embed il cui container non è più nel DOM. + private getActiveEmbeds(filePath: string) { + const result: Array<{ expand: () => void; collapse: () => void; convert: () => Promise }> = []; + for (const [id, actions] of this.embedActions) { + if (!actions.container.isConnected) { + this.embedActions.delete(id); + continue; + } + if (actions.sourcePath === filePath) result.push(actions); + } + return result; } async loadSettings() {