aggiunte anche voci al menu con i 3 puntini

This commit is contained in:
gabriele-cusato 2026-03-23 18:41:59 +01:00
parent dd8db22ba2
commit b1267debd5
3 changed files with 122 additions and 41 deletions

View file

@ -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)"
]
}
}

View file

@ -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'<img>):
@ -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) {

View file

@ -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<string, {
expand: () => void;
collapse: () => void;
convert: () => Promise<void>;
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<void> }> = [];
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() {