mirror of
https://github.com/gabriele-cusato/HandTranscriptMd.git
synced 2026-07-22 14:30:25 +00:00
aggiunte anche voci al menu con i 3 puntini
This commit is contained in:
parent
dd8db22ba2
commit
b1267debd5
3 changed files with 122 additions and 41 deletions
|
|
@ -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)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue