fix: risolti warning e errori segnalati da Obsidian community review

- bumped minAppVersion a 1.7.2 (no-unsupported-api)
  - sostituito document con activeDocument (prefer-active-doc)
  - aggiunto prefisso window. a setTimeout/clearTimeout/requestAnimationFrame/cancelAnimationFrame
  - aggiornato eslint-plugin-obsidianmd a 0.3.0, aggiunto tseslint.configs.recommendedTypeChecked
  - rimossi globals obsoleti da eslint.config.mjs
  - fix prefer-const in editor-view.ts e md-parser.ts
  - bumped versione a 1.0.2
This commit is contained in:
gabriele-cusato 2026-05-17 18:03:44 +02:00
parent 33a5f4bed9
commit 67a8280267
13 changed files with 75 additions and 82 deletions

View file

@ -1,24 +1,19 @@
import { defineConfig } from "eslint/config";
import obsidianmd from "eslint-plugin-obsidianmd";
import tsparser from "@typescript-eslint/parser";
import tseslint from "typescript-eslint";
export default defineConfig([
...obsidianmd.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
{
files: ["**/*.ts"],
languageOptions: {
parser: tsparser,
parserOptions: {
project: "./tsconfig.json",
tsconfigRootDir: import.meta.dirname,
},
globals: {
window: "readonly",
document: "readonly",
setTimeout: "readonly",
clearTimeout: "readonly",
requestAnimationFrame: "readonly",
cancelAnimationFrame: "readonly",
performance: "readonly",
console: "readonly",
process: "readonly",

View file

@ -2,7 +2,7 @@
"id": "handwriting-to-markdown",
"name": "HandTranscriptMd",
"version": "1.0.2",
"minAppVersion": "0.15.0",
"minAppVersion": "1.7.2",
"description": "Inline handwriting canvas with OCR conversion to structured markdown. Requires a free Gemini API key.",
"author": "GabrieleC",
"authorUrl": "https://github.com/gabriele-cusato",

View file

@ -1,12 +1,12 @@
{
"name": "handwriting-to-markdown",
"version": "1.0.0",
"version": "1.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "handwriting-to-markdown",
"version": "1.0.0",
"version": "1.0.2",
"license": "MIT",
"dependencies": {
"obsidian": "latest"
@ -17,7 +17,7 @@
"@typescript-eslint/parser": "^8.57.2",
"esbuild": "0.25.5",
"eslint": "^9.39.4",
"eslint-plugin-obsidianmd": "^0.1.9",
"eslint-plugin-obsidianmd": "^0.3.0",
"globals": "^17.4.0",
"jiti": "^2.6.1",
"tslib": "2.4.0",
@ -612,7 +612,6 @@
"integrity": "sha512-rvR/EZtvUG3p9uqrSmcDJPYSH7atmWr0RnFWN6m917MAPx82+zQgPUmDu0whPFG6XTyM0vB/hR6c1Q63OaYtCQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@eslint/core": "^0.17.0",
"@eslint/plugin-kit": "^0.4.1",
@ -691,7 +690,6 @@
"integrity": "sha512-KWiFQpSAqEIyrTXko3hFNLeQvSK8zXlJQzhhxsyVn58WFRYXST99b3Nqnu+ttOtjds2Pl2grUHGpe2NzhPynuQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=18"
}
@ -785,9 +783,9 @@
}
},
"node_modules/@types/eslint": {
"version": "8.56.2",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz",
"integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==",
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -2263,24 +2261,42 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/eslint-plugin-no-unsanitized": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.1.5.tgz",
"integrity": "sha512-MSB4hXPVFQrI8weqzs6gzl7reP2k/qSjtCoL2vUMSDejIIq9YL1ZKvq5/ORBXab/PvfBBrWO2jWviYpL+4Ghfg==",
"dev": true,
"license": "MPL-2.0",
"peerDependencies": {
"eslint": "^9 || ^10"
}
},
"node_modules/eslint-plugin-obsidianmd": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/eslint-plugin-obsidianmd/-/eslint-plugin-obsidianmd-0.1.9.tgz",
"integrity": "sha512-/gyo5vky3Y7re4BtT/8MQbHU5Wes4o6VRqas3YmXE7aTCnMsdV0kfzV1GDXJN9Hrsc9UQPoeKUMiapKL0aGE4g==",
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-obsidianmd/-/eslint-plugin-obsidianmd-0.3.0.tgz",
"integrity": "sha512-QvGDI6B2nxJBrsZKGTg31da2A/fEJNlnwN+fRZkaoPIu1QL3fYXUdpP7ThyMdr/0iTYQxifb9lt2X9cpydQx1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint/config-helpers": "^0.4.2",
"@eslint/js": "^9.30.1",
"@eslint/json": "0.14.0",
"@microsoft/eslint-plugin-sdl": "^1.1.0",
"@types/eslint": "8.56.2",
"@types/eslint": "9.6.1",
"@types/node": "20.12.12",
"eslint": ">=9.0.0 <10.0.0",
"@typescript-eslint/types": "^8.33.1",
"@typescript-eslint/utils": "^8.33.1",
"eslint": ">=9.0.0",
"eslint-plugin-depend": "1.3.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-json-schema-validator": "5.1.0",
"eslint-plugin-no-unsanitized": "^4.1.5",
"eslint-plugin-security": "2.1.1",
"globals": "14.0.0",
"obsidian": "1.8.7",
"typescript": "5.4.5"
"obsidian": "1.12.3",
"semver": "^7.7.4",
"typescript": "5.4.5",
"typescript-eslint": "^8.35.1"
},
"bin": {
"eslint-plugin-obsidian": "dist/lib/index.js"
@ -2291,7 +2307,7 @@
"peerDependencies": {
"@eslint/js": "^9.30.1",
"@eslint/json": "0.14.0",
"eslint": ">=9.0.0 <10.0.0",
"eslint": ">=9.0.0",
"obsidian": "1.8.7",
"typescript-eslint": "^8.35.1"
}
@ -2319,21 +2335,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-plugin-obsidianmd/node_modules/obsidian": {
"version": "1.8.7",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.8.7.tgz",
"integrity": "sha512-h4bWwNFAGRXlMlMAzdEiIM2ppTGlrh7uGOJS6w4gClrsjc+ei/3YAtU2VdFUlCiPuTHpY4aBpFJJW75S1Tl/JA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/codemirror": "5.60.8",
"moment": "2.29.4"
},
"peerDependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"node_modules/eslint-plugin-obsidianmd/node_modules/typescript": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
@ -3826,9 +3827,9 @@
}
},
"node_modules/obsidian": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.10.3.tgz",
"integrity": "sha512-VP+ZSxNMG7y6Z+sU9WqLvJAskCfkFrTz2kFHWmmzis+C+4+ELjk/sazwcTHrHXNZlgCeo8YOlM6SOrAFCynNew==",
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz",
"integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==",
"license": "MIT",
"dependencies": {
"@types/codemirror": "5.60.8",

View file

@ -18,7 +18,7 @@
"@typescript-eslint/parser": "^8.57.2",
"esbuild": "0.25.5",
"eslint": "^9.39.4",
"eslint-plugin-obsidianmd": "^0.1.9",
"eslint-plugin-obsidianmd": "^0.3.0",
"globals": "^17.4.0",
"jiti": "^2.6.1",
"tslib": "2.4.0",

View file

@ -100,7 +100,7 @@ export class DrawingCanvas {
this.mobileMode = mobileMode;
this.debugFn = debugFn;
this.canvas = document.createElement('canvas');
this.canvas = activeDocument.createElement('canvas');
// Dimensione CSS: pixel logici → il browser mostra il canvas a questa dimensione
this.canvas.style.width = width + 'px';
this.canvas.style.height = height + 'px';
@ -268,7 +268,7 @@ export class DrawingCanvas {
destroy() {
if (this.animFrameId !== null) {
cancelAnimationFrame(this.animFrameId);
window.cancelAnimationFrame(this.animFrameId);
}
this.canvas.removeEventListener('pointerdown', this.boundDown);
this.canvas.removeEventListener('pointermove', this.boundMove);
@ -379,7 +379,7 @@ export class DrawingCanvas {
if (startLogicalH === targetLogicalH) return;
if (this.animFrameId !== null) {
cancelAnimationFrame(this.animFrameId);
window.cancelAnimationFrame(this.animFrameId);
this.animFrameId = null;
}
@ -406,13 +406,13 @@ export class DrawingCanvas {
this.resizeCb?.();
if (progress < 1) {
this.animFrameId = requestAnimationFrame(step);
this.animFrameId = window.requestAnimationFrame(step);
} else {
this.animFrameId = null;
}
};
this.animFrameId = requestAnimationFrame(step);
this.animFrameId = window.requestAnimationFrame(step);
}
/* --- Coordinate --- */

View file

@ -216,10 +216,7 @@ async function buildEditorUI(opts: {
opts.afterCanvas(canvas, scrollWrap, canvasWidth);
// Resize handle (visibile ma non interattivo)
// NOTA: handle è dichiarato dopo colorBtns ma catturato dal bgModeListener per closure:
// la closure legge il valore corrente di 'handle' quando viene invocata (non quando è definita).
let handle!: HTMLElement;
handle = scrollWrap.createDiv({ cls: 'hwm_resize-handle hwm_resize-handle--disabled' });
const handle = scrollWrap.createDiv({ cls: 'hwm_resize-handle hwm_resize-handle--disabled' });
handle.createEl('span', { text: '⋯' });
handle.classList.toggle('hwm_resize-handle--dark', isDark);
@ -331,7 +328,7 @@ export class DrawingEditorView extends ItemView {
this.canvas.destroy();
this.canvas = null;
}
if (this.saveTimer) clearTimeout(this.saveTimer);
if (this.saveTimer) window.clearTimeout(this.saveTimer);
// Deregistra il listener bgMode
if (this.bgModeListener) {
this.plugin.bgModeListeners.delete(this.bgModeListener);
@ -379,8 +376,8 @@ export class DrawingEditorView extends ItemView {
// Auto-save debounced (2s dopo l'ultimo cambiamento)
canvas.onChange(() => {
if (this.saveTimer) clearTimeout(this.saveTimer);
this.saveTimer = setTimeout(() => { void this.saveSvg(); }, 2000);
if (this.saveTimer) window.clearTimeout(this.saveTimer);
this.saveTimer = window.setTimeout(() => { void this.saveSvg(); }, 2000);
});
}
@ -485,7 +482,7 @@ export class DrawingModal extends Modal {
this.canvas.destroy();
this.canvas = null;
}
if (this.saveTimer) clearTimeout(this.saveTimer);
if (this.saveTimer) window.clearTimeout(this.saveTimer);
// Deregistra il listener bgMode
if (this.bgModeListener) {
this.plugin.bgModeListeners.delete(this.bgModeListener);
@ -510,7 +507,7 @@ export class DrawingModal extends Modal {
// Espande il canvas a tutta la larghezza del modal eliminando le bande laterali.
// requestAnimationFrame garantisce che il layout del modal sia pronto prima di misurarlo.
afterCanvas: (cv, scrollWrap, canvasWidth) => {
requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
const displayW = scrollWrap.clientWidth;
if (displayW > canvasWidth) cv.setDisplayWidth(displayW);
});
@ -525,8 +522,8 @@ export class DrawingModal extends Modal {
// Auto-save debounced (2s dopo l'ultimo cambiamento)
canvas.onChange(() => {
if (this.saveTimer) clearTimeout(this.saveTimer);
this.saveTimer = setTimeout(() => { void this.saveSvg(); }, 2000);
if (this.saveTimer) window.clearTimeout(this.saveTimer);
this.saveTimer = window.setTimeout(() => { void this.saveSvg(); }, 2000);
});
}
@ -591,7 +588,7 @@ export class DrawingModal extends Modal {
const doFocus = () => {
if (focusDone) return;
focusDone = true;
setTimeout(() => {
window.setTimeout(() => {
let mdView = ws.getActiveViewOfType(MarkdownView);
if (!mdView || mdView.file?.path !== srcPath) {
const leaf = ws.getLeavesOfType('markdown')
@ -618,7 +615,7 @@ export class DrawingModal extends Modal {
if (svgFile instanceof TFile) await this.app.fileManager.trashFile(svgFile);
// Fallback: se vault.modify non spara entro 3s (caso anomalo), forza comunque il focus
setTimeout(() => { this.app.vault.offref(ref); doFocus(); }, 3000);
window.setTimeout(() => { this.app.vault.offref(ref); doFocus(); }, 3000);
this.close();
new Notice(t('notice_deleted'));

View file

@ -116,7 +116,7 @@ function setupMutationObserver(plugin: HandwritingPlugin) {
// Se Obsidian non ha ancora caricato l'immagine (classe image-embed
// assente), riprova tra 150 ms — il caricamento è asincrono.
if (!span.classList.contains('image-embed')) {
setTimeout(() => tryDecorate(span), 150);
window.setTimeout(() => tryDecorate(span), 150);
return;
}
@ -153,7 +153,7 @@ function setupMutationObserver(plugin: HandwritingPlugin) {
// Disable transition temporarily so the height sync is instant (no visual flash).
wrapper.classList.add('hwm_no-transition');
wrapper.style.setProperty('height', newH + 'px');
requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
wrapper.style.removeProperty('height');
wrapper.classList.remove('hwm_no-transition');
});
@ -200,7 +200,7 @@ function setupMutationObserver(plugin: HandwritingPlugin) {
}
});
observer.observe(document.body, { childList: true, subtree: true });
observer.observe(activeDocument.body, { childList: true, subtree: true });
// Disconnette l'observer quando il plugin viene disabilitato
plugin.register(() => observer.disconnect());
}
@ -615,7 +615,7 @@ function createPortalPanel(
// position: relative sullo span è gestita dalla regola CSS
// .internal-embed[data-hwm-decorated="1"] — non serve inline style.
const panel = document.createElement('div');
const panel = activeDocument.createElement('div');
panel.className = 'hwm_portal-panel';
if (isDark) panel.classList.add('hwm_portal-panel--dark');
container.appendChild(panel);
@ -660,7 +660,7 @@ function createPortalPanel(
})(); });
// Separatore visivo
const sep = document.createElement('div');
const sep = activeDocument.createElement('div');
sep.className = 'hwm_separator';
panel.appendChild(sep);
@ -694,7 +694,7 @@ function createPortalPanel(
if (!img || !img.parentElement) return null;
// Crea il wrapper e sposta l'img dentro: nessuna classe/altezza iniziale.
// La transizione CSS (height 0.3s ease) è già definita in styles.css su .hwm_clip-wrapper.
wrapper = document.createElement('div');
wrapper = activeDocument.createElement('div');
wrapper.className = 'hwm_clip-wrapper';
img.parentElement.insertBefore(wrapper, img);
wrapper.appendChild(img);
@ -717,7 +717,7 @@ function createPortalPanel(
wrapper.classList.remove('hwm_overflow-hidden');
};
wrapper.addEventListener('transitionend', cleanup, { once: true });
setTimeout(cleanup, 400); // 300ms transizione + 100ms buffer
window.setTimeout(cleanup, 400); // 300ms transizione + 100ms buffer
}
container.classList.remove('hwm_is-collapsed');
collapseBtn.classList.remove('hwm_rotated');
@ -768,9 +768,9 @@ function createPortalPanel(
const showConvertOverlay = (): HTMLElement => {
// Nasconde il pannello durante OCR: evita click sui bottoni
panel.classList.add('hwm_hidden');
const overlay = document.createElement('div');
const overlay = activeDocument.createElement('div');
overlay.className = 'hwm_convert-overlay';
const spinner = document.createElement('div');
const spinner = activeDocument.createElement('div');
spinner.className = 'hwm_spinner';
overlay.appendChild(spinner);
container.appendChild(overlay);
@ -861,7 +861,7 @@ function showInlineConfirm(anchorEl: HTMLElement, msg: string): Promise<boolean>
// Crea un bottone div nel pannello portale.
// key: chiave i18n — usata sia per il title che per data-hwm-key (aggiornamento live al cambio lingua)
function createPanelBtn(parent: HTMLElement, icon: string, key: I18nKey): HTMLElement {
const btn = document.createElement('div');
const btn = activeDocument.createElement('div');
btn.className = 'hwm_btn';
btn.setAttribute('title', t(key));
btn.setAttribute('data-hwm-key', key);
@ -886,12 +886,12 @@ function createLegacyPortalButton(
svgPath: string,
sourcePath: string
) {
const btn = document.createElement('button');
const btn = activeDocument.createElement('button');
btn.className = 'hwm_portal-btn';
// setIcon: inserisce l'SVG Lucide in modo sicuro (no innerHTML)
setIcon(btn, 'pencil');
btn.title = t('btn_open_editor');
document.body.appendChild(btn);
activeDocument.body.appendChild(btn);
// Apre la tab editor al click
btn.addEventListener('click', () => { void (async () => {
@ -927,9 +927,9 @@ function createLegacyPortalButton(
.some(l => (l.view as DrawingEditorView).getEmbedId() === embedId);
const inViewport = rect.width > 0 && rect.top < window.innerHeight && rect.bottom > 0;
btn.classList.toggle('hwm_hidden', !(inViewport && !editorOpen));
requestAnimationFrame(update);
window.requestAnimationFrame(update);
};
requestAnimationFrame(update);
window.requestAnimationFrame(update);
}
// Usa <div> invece di <button> per i bottoni dentro cm-content.

View file

@ -48,7 +48,7 @@ export function setLocale(lang: string): void {
: lang;
dict = locales[resolved] ?? en;
// Aggiorna i tooltip già presenti nel DOM
document.querySelectorAll<HTMLElement>('[data-hwm-key]').forEach(el => {
activeDocument.querySelectorAll<HTMLElement>('[data-hwm-key]').forEach(el => {
const key = el.getAttribute('data-hwm-key') as keyof typeof en;
if (key) el.title = t(key);
});

View file

@ -56,7 +56,7 @@ export default class HandwritingPlugin extends Plugin {
const themeObserver = new MutationObserver(() => {
if (this.settings.bgMode === 'auto') this.notifyBgModeChange();
});
themeObserver.observe(document.body, { attributeFilter: ['class'] });
themeObserver.observe(activeDocument.body, { attributeFilter: ['class'] });
this.register(() => themeObserver.disconnect());
// Registra la vista editor (tab dedicata per il disegno)

View file

@ -360,7 +360,7 @@ export function expandKeywords(text: string, fnStart = 1): string {
const rows: string[][] = [];
i = startI;
while (i < lines.length) {
let rowLine = lines[i].trim();
const rowLine = lines[i].trim();
// Chiusura esplicita //TABLE
if (/^\/\/TABLE/i.test(rowLine)) { i++; break; }
// Qualsiasi altra keyword chiude implicitamente senza consumarla

View file

@ -34,7 +34,7 @@ export const LINE_COLORS: Record<'light' | 'dark', string> = {
// Risolve 'auto' al tema effettivo leggendo la classe Obsidian sul body
function resolveAutoMode(): 'light' | 'dark' {
return document.body.classList.contains('theme-dark') ? 'dark' : 'light';
return activeDocument.body.classList.contains('theme-dark') ? 'dark' : 'light';
}
// Ritorna il colore sfondo effettivo in base alle impostazioni
@ -58,7 +58,7 @@ 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');
if (bgMode === 'auto') return activeDocument.body.classList.contains('theme-dark');
return bgMode === 'dark';
}

View file

@ -135,7 +135,7 @@ function unescapeXml(s: string): string {
// Usato dalla pipeline OCR (embed.ts e editor-view.ts) prima di inviare a Gemini.
export function svgToBase64Png(svgElement: SVGElement): Promise<string> {
return new Promise((resolve, reject) => {
const cvs = document.createElement('canvas');
const cvs = activeDocument.createElement('canvas');
const ctx = cvs.getContext('2d')!;
const img = new Image();
const blob = new Blob(

View file

@ -2,7 +2,7 @@
"id": "handwriting-to-markdown",
"name": "HandTranscriptMd",
"version": "1.0.2",
"minAppVersion": "0.15.0",
"minAppVersion": "1.7.2",
"description": "Inline handwriting canvas with OCR conversion to structured markdown. Requires a free Gemini API key.",
"author": "GabrieleC",
"authorUrl": "https://github.com/gabriele-cusato",