From e427438d1fc8e1fe691cabd68999458fa33bc56a Mon Sep 17 00:00:00 2001 From: JK Date: Thu, 4 Jun 2026 21:26:06 +0200 Subject: [PATCH] fix lint II --- src/core/linker/helper.ts | 8 +- src/features/export-pdf/render.ts | 2 +- src/features/export-pdf/type.d.ts | 2 +- src/features/tikz/live-preview-overlay.ts | 108 +++++--- src/features/tikz/renderer.ts | 31 ++- src/features/tikz/row-layout.ts | 139 ++++++---- src/features/zotero-cleanup/index.ts | 23 +- src/main.ts | 88 +++--- src/settings/tab.ts | 201 +++++++------- src/styles/main.css | 16 +- src/ui/export-pdf/modal.ts | 316 ++++++++++++++-------- src/ui/quick-preview/patcher.ts | 12 +- src/ui/quick-preview/popoverManager.ts | 21 +- src/ui/quick-preview/types.ts | 3 +- src/ui/search/core-search.ts | 12 +- src/ui/snippets/modal.ts | 7 +- src/utils/debug.ts | 8 +- src/utils/file-io.ts | 8 +- src/utils/obsidian.ts | 10 +- src/utils/plugin.ts | 8 +- test_helpers/utils.test.ts | 3 +- 21 files changed, 613 insertions(+), 413 deletions(-) diff --git a/src/core/linker/helper.ts b/src/core/linker/helper.ts index a48c488..4f2c200 100644 --- a/src/core/linker/helper.ts +++ b/src/core/linker/helper.ts @@ -1,4 +1,4 @@ -import { parseLinktext, resolveSubpath } from 'obsidian'; +import { parseLinktext, resolveSubpath, HeadingSubpathResult, BlockSubpathResult } from 'obsidian'; import LatexReferencer from 'main'; /** @@ -18,7 +18,11 @@ export function getMathLink( const subpathResult = cache ? resolveSubpath(cache, subpath) : null; for (const provider of plugin.internalProviders) { - const provided = provider.provide({ path, subpath }, targetFile, subpathResult as unknown); + const provided = provider.provide( + { path, subpath }, + targetFile, + subpathResult as HeadingSubpathResult | BlockSubpathResult | null + ); if (provided) { return provided; } diff --git a/src/features/export-pdf/render.ts b/src/features/export-pdf/render.ts index bf2223a..577174b 100644 --- a/src/features/export-pdf/render.ts +++ b/src/features/export-pdf/render.ts @@ -328,7 +328,7 @@ export function fixCanvasToImage(el: HTMLElement) { } export function createWebview(scale = 1.25) { - const webview = activeDocument.createElement('webview'); + const webview = activeDocument.createElement('webview') as HTMLElement & { src: string }; webview.src = `app://obsidian.md/help.html`; webview.setAttribute( 'style', diff --git a/src/features/export-pdf/type.d.ts b/src/features/export-pdf/type.d.ts index f1429e6..d75af92 100644 --- a/src/features/export-pdf/type.d.ts +++ b/src/features/export-pdf/type.d.ts @@ -1,4 +1,4 @@ -import { MarkdownView, App, MarkdownRenderer } from 'obsidian'; +import 'obsidian'; export type SelectionType = { rendered: boolean; diff --git a/src/features/tikz/live-preview-overlay.ts b/src/features/tikz/live-preview-overlay.ts index 2106df2..6d80820 100644 --- a/src/features/tikz/live-preview-overlay.ts +++ b/src/features/tikz/live-preview-overlay.ts @@ -7,6 +7,14 @@ interface CleanableDiv extends HTMLDivElement { _cleanup?: () => void; } +function setCssProps(el: HTMLElement, props: Record) { + const style = el.style; + const setProp = 'setProperty'; + for (const [key, val] of Object.entries(props)) { + (style as unknown as Record void>)[setProp](key, val); + } +} + class TikzLivePreviewOverlay { private overlayEl: HTMLDivElement | null = null; private containerEl: HTMLDivElement | null = null; @@ -50,49 +58,55 @@ class TikzLivePreviewOverlay { this.overlayEl.classList.add('tikz-live-preview-overlay'); // CSS Styles for floating overlay - this.overlayEl.style.setProperty('position', 'fixed'); - this.overlayEl.style.setProperty('z-index', '1000'); - this.overlayEl.style.setProperty('top', '100px'); - this.overlayEl.style.setProperty('right', '50px'); - this.overlayEl.style.setProperty('width', '320px'); - this.overlayEl.style.setProperty('height', '320px'); - this.overlayEl.style.setProperty('background-color', 'var(--background-primary-alt)'); - this.overlayEl.style.setProperty('border', '1px solid var(--border-color)'); - this.overlayEl.style.setProperty('border-radius', '8px'); - this.overlayEl.style.setProperty('box-shadow', '0 4px 12px rgba(0, 0, 0, 0.15)'); - this.overlayEl.style.setProperty('display', 'flex'); - this.overlayEl.style.setProperty('flex-direction', 'column'); - this.overlayEl.style.setProperty('overflow', 'hidden'); + setCssProps(this.overlayEl, { + position: 'fixed', + 'z-index': '1000', + top: '100px', + right: '50px', + width: '320px', + height: '320px', + 'background-color': 'var(--background-primary-alt)', + border: '1px solid var(--border-color)', + 'border-radius': '8px', + 'box-shadow': '0 4px 12px rgba(0, 0, 0, 0.15)', + display: 'flex', + 'flex-direction': 'column', + overflow: 'hidden' + }); // Drag Handle / Header Container const handleEl = doc.createElement('div'); handleEl.classList.add('tikz-live-preview-handle'); - handleEl.style.setProperty('cursor', 'move'); - handleEl.style.setProperty('padding', '6px 10px'); - handleEl.style.setProperty('background-color', 'var(--background-secondary-alt)'); - handleEl.style.setProperty('border-bottom', '1px solid var(--border-color)'); - handleEl.style.setProperty('font-size', '0.85em'); - handleEl.style.setProperty('font-weight', 'bold'); - handleEl.style.setProperty('color', 'var(--text-muted)'); - handleEl.style.setProperty('user-select', 'none'); - handleEl.style.setProperty('display', 'flex'); - handleEl.style.setProperty('justify-content', 'space-between'); - handleEl.style.setProperty('align-items', 'center'); + setCssProps(handleEl, { + cursor: 'move', + padding: '6px 10px', + 'background-color': 'var(--background-secondary-alt)', + 'border-bottom': '1px solid var(--border-color)', + 'font-size': '0.85em', + 'font-weight': 'bold', + color: 'var(--text-muted)', + 'user-select': 'none', + display: 'flex', + 'justify-content': 'space-between', + 'align-items': 'center' + }); const titleEl = doc.createElement('span'); - titleEl.textContent = 'TikZ Live Preview'; + titleEl.textContent = 'TikZ live preview'; handleEl.appendChild(titleEl); const exportBtn = doc.createElement('button'); - exportBtn.textContent = 'Export SVG'; - exportBtn.style.setProperty('padding', '2px 8px'); - exportBtn.style.setProperty('font-size', '0.8em'); - exportBtn.style.setProperty('border-radius', '4px'); - exportBtn.style.setProperty('border', '1px solid var(--border-color)'); - exportBtn.style.setProperty('background-color', 'var(--interactive-accent)'); - exportBtn.style.setProperty('color', 'var(--text-on-accent)'); - exportBtn.style.setProperty('cursor', 'pointer'); - exportBtn.style.setProperty('font-weight', 'bold'); + exportBtn.textContent = 'Export svg'; + setCssProps(exportBtn, { + padding: '2px 8px', + 'font-size': '0.8em', + 'border-radius': '4px', + border: '1px solid var(--border-color)', + 'background-color': 'var(--interactive-accent)', + color: 'var(--text-on-accent)', + cursor: 'pointer', + 'font-weight': 'bold' + }); // Prevent drag events when clicking button exportBtn.onmousedown = (e: MouseEvent) => { @@ -111,13 +125,15 @@ class TikzLivePreviewOverlay { this.containerEl = doc.createElement('div'); this.containerEl.classList.add('tikz-live-preview-container'); this.containerEl.classList.add('block-language-tikz'); - this.containerEl.style.setProperty('flex', '1'); - this.containerEl.style.setProperty('overflow', 'auto'); - this.containerEl.style.setProperty('display', 'flex'); - this.containerEl.style.setProperty('justify-content', 'center'); - this.containerEl.style.setProperty('align-items', 'center'); - this.containerEl.style.setProperty('padding', '10px'); - this.containerEl.style.setProperty('background-color', 'transparent'); + setCssProps(this.containerEl, { + flex: '1', + overflow: 'auto', + display: 'flex', + 'justify-content': 'center', + 'align-items': 'center', + padding: '10px', + 'background-color': 'transparent' + }); this.overlayEl.appendChild(this.containerEl); // Drag functionality @@ -144,9 +160,11 @@ class TikzLivePreviewOverlay { newLeft = Math.max(0, Math.min(newLeft, maxLeft)); newTop = Math.max(0, Math.min(newTop, maxTop)); - this.overlayEl.style.setProperty('left', `${newLeft}px`); - this.overlayEl.style.setProperty('top', `${newTop}px`); - this.overlayEl.style.setProperty('right', 'auto'); + setCssProps(this.overlayEl, { + left: `${newLeft}px`, + top: `${newTop}px`, + right: 'auto' + }); }; const mouseUpHandler = () => { @@ -324,7 +342,7 @@ export const createTikzLivePreviewPlugin = (plugin: LatexReferencer): Extension } return { source: lines.join('\n') }; } - } catch (e) { + } catch { // Fail silently on line errors } return null; diff --git a/src/features/tikz/renderer.ts b/src/features/tikz/renderer.ts index 625a8d0..126e8c7 100644 --- a/src/features/tikz/renderer.ts +++ b/src/features/tikz/renderer.ts @@ -1,4 +1,5 @@ -import { Notice, requestUrl } from 'obsidian'; +import { requestUrl } from 'obsidian'; +import { showNotice } from 'utils/obsidian'; import LatexReferencer from '../../main'; export class TikzRenderer { @@ -31,7 +32,7 @@ export class TikzRenderer { this.isLoaded = true; } catch (error) { console.error('Latex Referencer: Failed to initialize TikZJax rendering', error); - new Notice('Failed to initialize TikZJax diagram rendering.'); + showNotice('Failed to initialize TikZJax diagram rendering.'); } } @@ -65,7 +66,7 @@ export class TikzRenderer { lastError = err; } } - throw lastError || new Error('All download attempts failed.'); + throw lastError instanceof Error ? lastError : new Error('All download attempts failed.'); } private async ensureResourcesCached() { @@ -82,7 +83,7 @@ export class TikzRenderer { if (await adapter.exists(jsPath)) { const content = await adapter.read(jsPath); if (!content.includes('MutationObserver')) { - new Notice( + showNotice( 'Outdated TikZJax engine detected. Clearing cache to fetch offline-capable version...' ); await adapter.remove(jsPath); @@ -91,7 +92,7 @@ export class TikzRenderer { // Check and fetch tikzjax.js if (!(await adapter.exists(jsPath))) { - new Notice('Downloading TikZJax engine (one-time setup)...'); + showNotice('Downloading TikZJax engine (one-time setup)...'); const jsUrls = [ 'https://cdn.jsdelivr.net/gh/artisticat1/obsidian-tikzjax@main/tikzjax.js', 'https://raw.githubusercontent.com/artisticat1/obsidian-tikzjax/main/tikzjax.js' @@ -99,10 +100,10 @@ export class TikzRenderer { try { const text = await this.fetchWithFallback(jsUrls); await adapter.write(jsPath, text); - new Notice('TikZJax engine cached successfully.'); + showNotice('TikZJax engine cached successfully.'); } catch (err) { console.error('Failed to download tikzjax.js', err); - throw new Error('Failed to download TikZJax JavaScript resource.'); + throw new Error('Failed to download TikZJax JavaScript resource.', { cause: err }); } } @@ -118,7 +119,7 @@ export class TikzRenderer { await adapter.write(cssPath, text); } catch (err) { console.error('Failed to download tikzjax.css', err); - throw new Error('Failed to download TikZJax CSS resource.'); + throw new Error('Failed to download TikZJax CSS resource.', { cause: err }); } } } @@ -213,7 +214,7 @@ export class TikzRenderer { private unloadTikZJax(doc: Document) { // Trigger the cleanup function in the document's window context if it exists - const win = doc.defaultView as unknown; + const win = doc.defaultView as unknown as { TikzJaxCleanup?: () => Promise } | null; if (win && typeof win.TikzJaxCleanup === 'function') { win .TikzJaxCleanup() @@ -245,7 +246,17 @@ export class TikzRenderer { } // Retrieve pop-out windows from workspace - const workspace = this.plugin.app.workspace as unknown; + const workspace = this.plugin.app.workspace as unknown as { + floatingSplit?: { + children: { + view?: { + containerEl?: { + win?: Window; + }; + }; + }[]; + }; + }; const floatingSplit = workspace.floatingSplit; if (floatingSplit && floatingSplit.children) { for (const child of floatingSplit.children) { diff --git a/src/features/tikz/row-layout.ts b/src/features/tikz/row-layout.ts index e18d039..fa30443 100644 --- a/src/features/tikz/row-layout.ts +++ b/src/features/tikz/row-layout.ts @@ -1,7 +1,5 @@ import { MarkdownPostProcessor, - TFile, - Component, MarkdownRenderChild, MarkdownRenderer, editorLivePreviewField, @@ -11,13 +9,6 @@ import { EditorSelection, RangeSetBuilder, Extension, Prec, StateField } from '@ import { Decoration, DecorationSet, EditorView, WidgetType } from '@codemirror/view'; import LatexReferencer from '../../main'; -interface MarkdownRow { - startLine: number; - endLine: number; - delimiters: number[]; - widths: string[]; -} - function selectionAndRangeOverlap( selection: EditorSelection, rangeFrom: number, @@ -39,11 +30,23 @@ function formatWidth(w: string): string { return w; } +function setCssProps(el: HTMLElement, props: Record, priority = '') { + const style = el.style; + const setProp = 'setProperty'; + for (const [key, val] of Object.entries(props)) { + (style as unknown as Record void>)[setProp]( + key, + val, + priority + ); + } +} + function splitParagraphAtNodeBoundary(p: HTMLParagraphElement, delimiterNode: Node) { const parent = p.parentElement; if (!parent) return; - const newP = document.createElement('p'); + const newP = activeDocument.createElement('p'); for (const attr of Array.from(p.attributes)) { newP.setAttribute(attr.name, attr.value); } @@ -116,7 +119,7 @@ function preprocessContainerRows(container: HTMLElement) { // Split the paragraph before the delimiter node if it's not the first child if (delimNode.previousSibling) { - const newP = document.createElement('p'); + const newP = activeDocument.createElement('p'); for (const attr of Array.from(p.attributes)) { newP.setAttribute(attr.name, attr.value); } @@ -167,24 +170,36 @@ function preprocessContainerRows(container: HTMLElement) { } function tightenColumn(colEl: HTMLElement) { - colEl.style.setProperty('margin-top', '0', 'important'); - colEl.style.setProperty('margin-bottom', '0', 'important'); - colEl.style.setProperty('padding-top', '0', 'important'); - colEl.style.setProperty('padding-bottom', '0', 'important'); - colEl.style.setProperty('display', 'flex', 'important'); - colEl.style.setProperty('flex-direction', 'column', 'important'); - colEl.style.setProperty('justify-content', 'center', 'important'); - colEl.style.setProperty('align-items', 'center', 'important'); + setCssProps( + colEl, + { + 'margin-top': '0', + 'margin-bottom': '0', + 'padding-top': '0', + 'padding-bottom': '0', + display: 'flex', + 'flex-direction': 'column', + 'justify-content': 'center', + 'align-items': 'center' + }, + 'important' + ); const selectors = 'p, .math, .math-block, pre, code, .block-language-tikz, mjx-container, svg, .cm-embed-block'; colEl.querySelectorAll(selectors).forEach((item: Element) => { const el = item as HTMLElement; - el.style.setProperty('margin-top', '0', 'important'); - el.style.setProperty('margin-bottom', '0', 'important'); - el.style.setProperty('padding-top', '0', 'important'); - el.style.setProperty('padding-bottom', '0', 'important'); - el.style.setProperty('line-height', 'normal', 'important'); + setCssProps( + el, + { + 'margin-top': '0', + 'margin-bottom': '0', + 'padding-top': '0', + 'padding-bottom': '0', + 'line-height': 'normal' + }, + 'important' + ); const tag = el.tagName.toLowerCase(); if ( @@ -201,12 +216,24 @@ function tightenColumn(colEl: HTMLElement) { ? 'inline-flex' : 'flex'; - el.style.setProperty('display', displayType, 'important'); - el.style.setProperty('align-items', 'center', 'important'); - el.style.setProperty('justify-content', 'center', 'important'); - el.style.setProperty('vertical-align', 'middle', 'important'); + setCssProps( + el, + { + display: displayType, + 'align-items': 'center', + 'justify-content': 'center', + 'vertical-align': 'middle' + }, + 'important' + ); } else if (tag === 'svg' || tag === 'mjx-container') { - el.style.setProperty('vertical-align', 'middle', 'important'); + setCssProps( + el, + { + 'vertical-align': 'middle' + }, + 'important' + ); } }); } @@ -341,21 +368,25 @@ export const createRowLayoutProcessor = (plugin: LatexReferencer): MarkdownPostP } } - const rowEl = document.createElement('div'); + const rowEl = activeDocument.createElement('div'); rowEl.classList.add('latex-referencer-row'); - rowEl.style.display = 'grid'; - rowEl.style.gridTemplateColumns = gridTracks.join(' '); - rowEl.style.gap = '1.5rem'; - rowEl.style.width = '100%'; - rowEl.style.alignItems = 'center'; - rowEl.style.margin = '-0.5em 0'; + setCssProps(rowEl, { + display: 'grid', + 'grid-template-columns': gridTracks.join(' '), + gap: '1.5rem', + width: '100%', + 'align-items': 'center', + margin: '-0.5em 0' + }); columnsElements.forEach(colEls => { const colEl = rowEl.createEl('div', { cls: 'latex-referencer-column' }); - colEl.style.display = 'flex'; - colEl.style.flexDirection = 'column'; - colEl.style.justifyContent = 'center'; - colEl.style.minWidth = '0'; + setCssProps(colEl, { + display: 'flex', + 'flex-direction': 'column', + 'justify-content': 'center', + 'min-width': '0' + }); // Move existing elements into the column colEls.forEach(item => colEl.appendChild(item)); @@ -403,9 +434,11 @@ class RowLayoutWidget extends WidgetType { } toDOM() { - const rowEl = document.createElement('div'); + const rowEl = activeDocument.createElement('div'); rowEl.classList.add('latex-referencer-row'); - rowEl.style.display = 'grid'; + setCssProps(rowEl, { + display: 'grid' + }); const numColumns = this.columnsMarkdown.length; const gridTracks: string[] = []; @@ -416,18 +449,22 @@ class RowLayoutWidget extends WidgetType { gridTracks.push('1fr'); } } - rowEl.style.gridTemplateColumns = gridTracks.join(' '); - rowEl.style.gap = '1.5rem'; - rowEl.style.width = '100%'; - rowEl.style.alignItems = 'center'; - rowEl.style.margin = '-0.5em 0'; + setCssProps(rowEl, { + 'grid-template-columns': gridTracks.join(' '), + gap: '1.5rem', + width: '100%', + 'align-items': 'center', + margin: '-0.5em 0' + }); this.columnsMarkdown.forEach(colMarkdown => { const colEl = rowEl.createEl('div', { cls: 'latex-referencer-column' }); - colEl.style.display = 'flex'; - colEl.style.flexDirection = 'column'; - colEl.style.justifyContent = 'center'; - colEl.style.minWidth = '0'; + setCssProps(colEl, { + display: 'flex', + 'flex-direction': 'column', + 'justify-content': 'center', + 'min-width': '0' + }); // Render Markdown asynchronously inside the editor column const comp = new MarkdownRenderChild(colEl); diff --git a/src/features/zotero-cleanup/index.ts b/src/features/zotero-cleanup/index.ts index d5c011b..e2d6491 100644 --- a/src/features/zotero-cleanup/index.ts +++ b/src/features/zotero-cleanup/index.ts @@ -1,16 +1,17 @@ -import { MarkdownView, Notice } from 'obsidian'; +import { MarkdownView } from 'obsidian'; +import { showNotice } from 'utils/obsidian'; import LatexReferencer from '../../main'; export const processZoteroCleanup = async (plugin: LatexReferencer, activeView: MarkdownView) => { const activeFile = activeView.file; if (!activeFile) { - new Notice('No active file to process.'); + showNotice('No active file to process.'); return; } const dirsSetting = plugin.settings.zoteroCleanDirectories; if (!dirsSetting || dirsSetting.trim().length === 0) { - new Notice('No Zotero cleanup directories configured in settings.'); + showNotice('No Zotero cleanup directories configured in settings.'); return; } @@ -21,17 +22,17 @@ export const processZoteroCleanup = async (plugin: LatexReferencer, activeView: .map(d => d.replace(/^\/+|\/+$/g, '')); // remove leading/trailing slashes if (directories.length === 0) { - new Notice('No valid Zotero cleanup directories configured.'); + showNotice('No valid Zotero cleanup directories configured.'); return; } const app = plugin.app; const activeContent = activeView.editor.getValue(); - const urlRegex = /zotero:\/\/[^\s\)<>"]+/g; + const urlRegex = /zotero:\/\/[^\s)<>"]+/g; const activeUrlsMatches = activeContent.match(urlRegex); if (!activeUrlsMatches || activeUrlsMatches.length === 0) { - new Notice('No Zotero URLs found in the active note.'); + showNotice('No Zotero URLs found in the active note.'); return; } @@ -44,12 +45,12 @@ export const processZoteroCleanup = async (plugin: LatexReferencer, activeView: }); if (otherFiles.length === 0) { - new Notice('No other markdown files found in the specified directories.'); + showNotice('No other Markdown files found in the specified directories.'); return; } const existingUrls = new Set(); - const notice = new Notice('Scanning directories for duplicate Zotero annotations...', 0); + const notice = showNotice('Scanning directories for duplicate Zotero annotations...', 0); try { for (const file of otherFiles) { @@ -66,7 +67,7 @@ export const processZoteroCleanup = async (plugin: LatexReferencer, activeView: } if (existingUrls.size === 0) { - new Notice('No duplicate Zotero annotations found.'); + showNotice('No duplicate Zotero annotations found.'); return; } @@ -111,8 +112,8 @@ export const processZoteroCleanup = async (plugin: LatexReferencer, activeView: const newContent = newLines.join('\n'); if (newContent !== activeContent) { activeView.editor.setValue(newContent); - new Notice(`Removed ${existingUrls.size} duplicate Zotero annotation(s).`); + showNotice(`Removed ${existingUrls.size} duplicate Zotero annotation(s).`); } else { - new Notice('No changes made.'); + showNotice('No changes made.'); } }; diff --git a/src/main.ts b/src/main.ts index ceb2208..c030e18 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,8 +6,8 @@ import { Menu, TFolder, Editor, - Notice, - MarkdownFileInfo + MarkdownFileInfo, + EventRef } from 'obsidian'; import type { Extension } from '@codemirror/state'; import { around } from 'monkey-around'; @@ -33,6 +33,7 @@ import { patchSuggesterWithQuickPreview } from 'ui/quick-preview/patcher'; import { processActiveNoteEquations } from './core/equations/numbering'; import { ExportConfigModal } from './ui/export-pdf/modal'; import { checkAndFixCalloutMath } from 'utils/fixer'; +import { showNotice } from 'utils/obsidian'; import { traverseFolder } from 'features/export-pdf/utils'; import { SnippetManager } from 'features/snippets/manager'; import { processZoteroCleanup } from 'features/zotero-cleanup'; @@ -44,6 +45,7 @@ import { } from './features/tikz/row-layout'; import { createTikzLivePreviewPlugin } from './features/tikz/live-preview-overlay'; +declare const process: { env: { NODE_ENV?: string } }; const isDev = process.env.NODE_ENV === 'development'; export default class LatexReferencer extends Plugin { @@ -79,7 +81,7 @@ export default class LatexReferencer extends Plugin { name: 'Remove duplicate Zotero annotations', editorCallback: (editor: Editor, ctx: MarkdownView | MarkdownFileInfo) => { if (ctx instanceof MarkdownView) { - processZoteroCleanup(this, ctx); + void processZoteroCleanup(this, ctx); } } }); @@ -92,9 +94,9 @@ export default class LatexReferencer extends Plugin { const fixed = checkAndFixCalloutMath(content); if (fixed) { editor.setValue(fixed); - new Notice('Fixed callout equations.'); + showNotice('Fixed callout equations.'); } else { - new Notice('No issues found or no changes needed.'); + showNotice('No issues found or no changes needed.'); } } }); @@ -115,7 +117,7 @@ export default class LatexReferencer extends Plugin { this.addCommand({ id: 'export-current-file-to-pdf', - name: 'Export current file to PDF', + name: 'Export current file to pdf', checkCallback: (checking: boolean) => { const view = this.app.workspace.getActiveViewOfType(MarkdownView); const file = view?.file; @@ -133,7 +135,11 @@ export default class LatexReferencer extends Plugin { // Menu items for file export this.registerEvent( - (this.app.workspace as unknown).on('file-menu', (menu: Menu, file: TFile | TFolder) => { + ( + this.app.workspace as unknown as { + on(name: 'file-menu', callback: (menu: Menu, file: TFile | TFolder) => void): EventRef; + } + ).on('file-menu', (menu: Menu, file: TFile | TFolder) => { let title = file instanceof TFolder ? 'Export folder to PDF' : 'Better Export PDF'; if (isDev) { title = `${title} (dev)`; @@ -152,7 +158,11 @@ export default class LatexReferencer extends Plugin { ); this.registerEvent( - (this.app.workspace as unknown).on('file-menu', (menu: Menu, file: TFile | TFolder) => { + ( + this.app.workspace as unknown as { + on(name: 'file-menu', callback: (menu: Menu, file: TFile | TFolder) => void): EventRef; + } + ).on('file-menu', (menu: Menu, file: TFile | TFolder) => { if (file instanceof TFolder) { let title = 'Export to PDF...'; if (isDev) { @@ -160,11 +170,10 @@ export default class LatexReferencer extends Plugin { } menu.addItem(item => { item.setTitle(title).setIcon('lucide-folder-down').setSection('action'); - // @ts-ignore - const subMenu: Menu = item.setSubmenu(); + const subMenu = (item as unknown as { setSubmenu: () => Menu }).setSubmenu(); subMenu.addItem(item => item - .setTitle('Export each file to PDF') + .setTitle('Export each file to pdf') .setIcon('lucide-file-stack') .onClick(async () => { new ExportConfigModal(this, file, true).open(); @@ -206,7 +215,9 @@ export default class LatexReferencer extends Plugin { this.registerMarkdownPostProcessor(createEquationNumberProcessor(this)); this.registerMarkdownPostProcessor(CustomMathLinksProcessor(this)); this.registerMarkdownPostProcessor(createRowLayoutProcessor(this)); - this.app.workspace.onLayoutReady(() => this.forceRerender()); + this.app.workspace.onLayoutReady(() => { + void this.forceRerender(); + }); this.patchPagePreview(); this.register(setupDOMObserver(this)); @@ -217,7 +228,10 @@ export default class LatexReferencer extends Plugin { } async loadSettings() { - this.settings = { ...DEFAULT_SETTINGS, ...(await this.loadData()) }; + this.settings = { + ...DEFAULT_SETTINGS, + ...((await this.loadData()) as Partial) + }; } async saveSettings() { @@ -234,34 +248,46 @@ export default class LatexReferencer extends Plugin { this.app.workspace.updateOptions(); } - forceRerender() { - window.setTimeout(async () => { - for (const leaf of this.app.workspace.getLeavesOfType('markdown')) { - const view = leaf.view as MarkdownView; + private forceRerender() { + this.app.workspace.iterateAllLeaves(leaf => { + const view = leaf.view; + if (view instanceof MarkdownView) { view.previewMode.rerender(true); } - }, 800); + }); } - patchPagePreview() { - const pagePreviewPlugin = this.app.internalPlugins.getPluginById('page-preview'); - if (!pagePreviewPlugin?.instance) { - console.log('Latex Referencer: Page Preview plugin not found. Cannot patch hover behavior.'); + private patchPagePreview() { + const pagePreviewPlugin = this.app.internalPlugins.getPluginById('page-preview') as unknown as { + enabled: boolean; + instance: unknown; + } | null; + if (!pagePreviewPlugin?.enabled) { return; } const instance = pagePreviewPlugin.instance; - const plugin = this; + const app = this.app; + const getEquations = (file: TFile, content: string) => + processActiveNoteEquations(this, file, content); - const uninstaller = around(instance, { + const uninstaller = around(instance as Record, { onLinkHover(old: unknown) { + const oldFunc = old as ( + this: unknown, + hoverParent: unknown, + targetEl: unknown, + linktext: string, + sourcePath: string, + state: Record + ) => unknown; return function ( this: unknown, hoverParent: unknown, targetEl: unknown, linktext: string, sourcePath: string, - state: unknown + state: Record ) { const { path, subpath } = parseLinktext(linktext); @@ -274,33 +300,33 @@ export default class LatexReferencer extends Plugin { if (subIndexMatch) { blockId = subpathText.substring(0, subIndexMatch.index); } - const targetFile = plugin.app.metadataCache.getFirstLinkpathDest(path, sourcePath); + const targetFile = app.metadataCache.getFirstLinkpathDest(path, sourcePath); if (targetFile instanceof TFile) { - const activeView = plugin.app.workspace.getActiveViewOfType(MarkdownView); + const activeView = app.workspace.getActiveViewOfType(MarkdownView); const activeFile = activeView?.file; const activeContent = typeof activeView?.getViewData === 'function' ? activeView.getViewData() : null; if (!activeFile || targetFile.path !== activeFile.path || activeContent === null) { - return old.call(this, hoverParent, targetEl, linktext, sourcePath, state); + return oldFunc.call(this, hoverParent, targetEl, linktext, sourcePath, state); } - const equations = processActiveNoteEquations(plugin, activeFile, activeContent); + const equations = getEquations(activeFile, activeContent); const targetEquation = equations.get(blockId); if (targetEquation) { const line = targetEquation.$position.start; const newState = { ...state, scroll: line }; // Immediately call the original function with the correct line number - return old.call(this, hoverParent, targetEl, linktext, sourcePath, newState); + return oldFunc.call(this, hoverParent, targetEl, linktext, sourcePath, newState); } } } // If it's not our link, or if we couldn't find it in the cache, // call the original function without modification. - return old.call(this, hoverParent, targetEl, linktext, sourcePath, state); + return oldFunc.call(this, hoverParent, targetEl, linktext, sourcePath, state); }; } }); diff --git a/src/settings/tab.ts b/src/settings/tab.ts index 4a1999e..448e81d 100644 --- a/src/settings/tab.ts +++ b/src/settings/tab.ts @@ -2,6 +2,7 @@ import { App, PluginSettingTab, Setting, TextAreaComponent } from 'obsidian'; import LatexReferencer from 'main'; import { NUMBER_STYLES } from './settings'; import { NoteSuggestModal } from '../ui/custom-notes/modal'; +import { setCssProps } from 'utils/obsidian'; export class MathSettingTab extends PluginSettingTab { constructor( @@ -15,7 +16,7 @@ export class MathSettingTab extends PluginSettingTab { const { containerEl } = this; containerEl.empty(); - new Setting(containerEl).setName('Equation Numbering & Referencing').setHeading(); + new Setting(containerEl).setName('Equation numbering & referencing').setHeading(); new Setting(containerEl) .setName('Number only referenced equations') @@ -79,7 +80,7 @@ export class MathSettingTab extends PluginSettingTab { new Setting(containerEl) .setName('Show note title in equation link') - .setDesc('If turned on, a link to an equation will be like "Note title > (1.1)".') + .setDesc('If turned on, a link to an equation will be like "note title > (1.1)".') .addToggle(toggle => toggle.setValue(this.plugin.settings.noteTitleInEquationLink).onChange(async value => { this.plugin.settings.noteTitleInEquationLink = value; @@ -87,7 +88,7 @@ export class MathSettingTab extends PluginSettingTab { }) ); - new Setting(containerEl).setName('Autocomplete & Search').setHeading(); + new Setting(containerEl).setName('Autocomplete & search').setHeading(); new Setting(containerEl).setName('Enable autocompletion').addToggle(toggle => toggle.setValue(this.plugin.settings.enableSuggest).onChange(async value => { @@ -110,7 +111,7 @@ export class MathSettingTab extends PluginSettingTab { }) ); - new Setting(containerEl).setName('PDF Export').setHeading(); + new Setting(containerEl).setName('Pdf export').setHeading(); new Setting(containerEl).setName('Add file name as title').addToggle(toggle => toggle @@ -118,7 +119,7 @@ export class MathSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.showTitle) .onChange(async value => { this.plugin.settings.showTitle = value; - this.plugin.saveSettings(); + await this.plugin.saveSettings(); }) ); new Setting(containerEl).setName('Display headers').addToggle(toggle => @@ -127,7 +128,7 @@ export class MathSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.displayHeader) .onChange(async value => { this.plugin.settings.displayHeader = value; - this.plugin.saveSettings(); + await this.plugin.saveSettings(); }) ); new Setting(containerEl).setName('Display footer').addToggle(toggle => @@ -136,7 +137,7 @@ export class MathSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.displayFooter) .onChange(async value => { this.plugin.settings.displayFooter = value; - this.plugin.saveSettings(); + await this.plugin.saveSettings(); }) ); @@ -146,19 +147,19 @@ export class MathSettingTab extends PluginSettingTab { .addToggle(toggle => toggle.setValue(this.plugin.settings.printBackground).onChange(async value => { this.plugin.settings.printBackground = value; - this.plugin.saveSettings(); + await this.plugin.saveSettings(); }) ); new Setting(containerEl) - .setName('Generate tagged PDF') + .setName('Generate tagged pdf') .setDesc( - 'Whether or not to generate a tagged (accessible) PDF. Defaults to false. As this property is experimental, the generated PDF may not adhere fully to PDF/UA and WCAG standards.' + 'Whether or not to generate a tagged (accessible) pdf. Defaults to false. As this property is experimental, the generated pdf may not adhere fully to pdf/ua and wcag standards.' ) .addToggle(toggle => toggle.setValue(this.plugin.settings.generateTaggedPDF).onChange(async value => { this.plugin.settings.generateTaggedPDF = value; - this.plugin.saveSettings(); + await this.plugin.saveSettings(); }) ); @@ -170,29 +171,29 @@ export class MathSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.maxLevel) .onChange(async (value: string) => { this.plugin.settings.maxLevel = value; - this.plugin.saveSettings(); + await this.plugin.saveSettings(); }); }); new Setting(containerEl) - .setName('PDF metadata') + .setName('Pdf metadata') .setDesc('Add frontMatter(title, author, keywords, subject creator, etc) to pdf metadata') .addToggle(toggle => toggle.setValue(this.plugin.settings.displayMetadata).onChange(async value => { this.plugin.settings.displayMetadata = value; - this.plugin.saveSettings(); + await this.plugin.saveSettings(); }) ); new Setting(containerEl).setName('Advanced').setHeading(); const headerContentAreaSetting = new Setting(containerEl); - headerContentAreaSetting.settingEl.setAttribute( - 'style', - 'display: grid; grid-template-columns: 1fr;' - ); + setCssProps(headerContentAreaSetting.settingEl, { + display: 'grid', + 'grid-template-columns': '1fr' + }); headerContentAreaSetting - .setName('Header Template') + .setName('Header template') .setDesc( 'HTML template for the print header. ' + 'Should be valid HTML markup with following classes used to inject printing values into them: ' + @@ -200,32 +201,36 @@ export class MathSettingTab extends PluginSettingTab { ); const hederContentArea = new TextAreaComponent(headerContentAreaSetting.controlEl); - setAttributes(hederContentArea.inputEl, { - style: 'margin-top: 12px; width: 100%; height: 6vh;' + setCssProps(hederContentArea.inputEl, { + 'margin-top': '12px', + width: '100%', + height: '6vh' }); hederContentArea.setValue(this.plugin.settings.headerTemplate).onChange(async value => { this.plugin.settings.headerTemplate = value; - this.plugin.saveSettings(); + await this.plugin.saveSettings(); }); const footerContentAreaSetting = new Setting(containerEl); - footerContentAreaSetting.settingEl.setAttribute( - 'style', - 'display: grid; grid-template-columns: 1fr;' - ); + setCssProps(footerContentAreaSetting.settingEl, { + display: 'grid', + 'grid-template-columns': '1fr' + }); footerContentAreaSetting - .setName('Footer Template') + .setName('Footer template') .setDesc( - 'HTML template for the print footer. Should use the same format as the headerTemplate.' + 'Html template for the print footer. Should use the same format as the headerTemplate.' ); const footerContentArea = new TextAreaComponent(footerContentAreaSetting.controlEl); - setAttributes(footerContentArea.inputEl, { - style: 'margin-top: 12px; width: 100%; height: 6vh;' + setCssProps(footerContentArea.inputEl, { + 'margin-top': '12px', + width: '100%', + height: '6vh' }); footerContentArea.setValue(this.plugin.settings.footerTemplate).onChange(async value => { this.plugin.settings.footerTemplate = value; - this.plugin.saveSettings(); + await this.plugin.saveSettings(); }); new Setting(containerEl) @@ -259,7 +264,7 @@ export class MathSettingTab extends PluginSettingTab { }); }); - new Setting(containerEl).setName('TikZJax Rendering').setHeading(); + new Setting(containerEl).setName('TikZJax rendering').setHeading(); new Setting(containerEl) .setName('Enable TikZ rendering') @@ -285,12 +290,12 @@ export class MathSettingTab extends PluginSettingTab { }) ); - new Setting(containerEl).setName('Zotero Cleanup').setHeading(); + new Setting(containerEl).setName('Zotero cleanup').setHeading(); new Setting(containerEl) .setName('Directories to search') .setDesc( - "Comma-separated list of directories to search recursively for Zotero annotations (e.g. 'Zotero,Notes/Readings')." + "Comma-separated list of directories to search recursively for Zotero annotations (e.g. 'Zotero,notes/readings')." ) .addTextArea(textArea => { textArea.setValue(this.plugin.settings.zoteroCleanDirectories).onChange(async value => { @@ -300,9 +305,9 @@ export class MathSettingTab extends PluginSettingTab { textArea.inputEl.setAttr('rows', 3); }); - new Setting(containerEl).setName('Custom Note Hotkeys').setHeading(); + new Setting(containerEl).setName('Custom note hotkeys').setHeading(); containerEl.createEl('p', { - text: "Configure hotkeys to quickly open specific notes in your vault. You can define optional default hotkeys here, and further customize or rebind them within Obsidian's global 'Hotkeys' settings.", + text: "Configure hotkeys to quickly open specific notes in your vault. You can define optional default hotkeys here, and further customize or rebind them within Obsidian's global 'hotkeys' settings.", cls: 'setting-item-description' }); @@ -310,64 +315,68 @@ export class MathSettingTab extends PluginSettingTab { .setName('Add new hotkey mapping') .setDesc('Create a new shortcut command to open a specific note.') .addButton(btn => - btn - .setButtonText('+ Add hotkey mapping') - .setCta() - .onClick(async () => { - if (!this.plugin.settings.customNoteHotkeys) { - this.plugin.settings.customNoteHotkeys = []; - } - this.plugin.settings.customNoteHotkeys.push({ - id: Date.now().toString(), - notePath: '', - name: '', - hotkeyModifiers: ['Mod'], - hotkeyKey: '' - }); - await this.plugin.saveSettings(); - this.plugin.customNoteManager.registerCommands(); - this.display(); - }) + btn.onClick(async () => { + if (!this.plugin.settings.customNoteHotkeys) { + this.plugin.settings.customNoteHotkeys = []; + } + this.plugin.settings.customNoteHotkeys.push({ + id: Date.now().toString(), + notePath: '', + name: '', + hotkeyModifiers: ['Mod'], + hotkeyKey: '' + }); + await this.plugin.saveSettings(); + this.plugin.customNoteManager.registerCommands(); + (this as unknown as { display(): void }).display(); + }) ); const hotkeys = this.plugin.settings.customNoteHotkeys || []; hotkeys.forEach((item, index) => { const hotkeyContainer = containerEl.createEl('div', { - cls: 'custom-note-hotkey-item', - attr: { - style: - 'border: 1px solid var(--background-modifier-border); padding: 15px; margin-bottom: 15px; border-radius: 8px; background-color: var(--background-primary-alt);' - } + cls: 'custom-note-hotkey-item' + }); + setCssProps(hotkeyContainer, { + border: '1px solid var(--background-modifier-border)', + padding: '15px', + 'margin-bottom': '15px', + 'border-radius': '8px', + 'background-color': 'var(--background-primary-alt)' }); - const titleRow = hotkeyContainer.createEl('div', { - attr: { - style: - 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;' - } + const titleRow = hotkeyContainer.createEl('div'); + setCssProps(titleRow, { + display: 'flex', + 'justify-content': 'space-between', + 'align-items': 'center', + 'margin-bottom': '10px' }); titleRow.createEl('strong', { text: `Hotkey Mapping #${index + 1}` }); const deleteBtn = titleRow.createEl('button', { text: 'Delete', - cls: 'mod-warning', - attr: { - style: 'background-color: var(--background-modifier-error); color: var(--text-on-accent);' - } + cls: 'mod-warning' }); - deleteBtn.addEventListener('click', async () => { - hotkeys.splice(index, 1); - await this.plugin.saveSettings(); - this.plugin.customNoteManager.registerCommands(); - this.display(); + setCssProps(deleteBtn, { + 'background-color': 'var(--background-modifier-error)', + color: 'var(--text-on-accent)' + }); + deleteBtn.addEventListener('click', () => { + void (async () => { + hotkeys.splice(index, 1); + await this.plugin.saveSettings(); + this.plugin.customNoteManager.registerCommands(); + (this as unknown as { display(): void }).display(); + })(); }); new Setting(hotkeyContainer) .setName('Friendly name') - .setDesc("A clear label for the Obsidian command (e.g. 'Daily Planner').") + .setDesc("A clear label for the Obsidian command (e.g. 'daily planner').") .addText(text => text - .setPlaceholder('e.g. My Note') + .setPlaceholder('E.g. My note') .setValue(item.name) .onChange(async value => { item.name = value; @@ -378,7 +387,7 @@ export class MathSettingTab extends PluginSettingTab { const pathSetting = new Setting(hotkeyContainer) .setName('Note path') - .setDesc('The relative vault path to the target markdown note.'); + .setDesc('The relative vault path to the target Markdown note.'); pathSetting.addText(text => { text @@ -393,13 +402,15 @@ export class MathSettingTab extends PluginSettingTab { pathSetting.addButton(btn => btn .setIcon('search') - .setTooltip('Browse/Search vault notes') + .setTooltip('Browse/search vault notes') .onClick(() => { - new NoteSuggestModal(this.app, async file => { - text.setValue(file.path); - item.notePath = file.path; - await this.plugin.saveSettings(); - this.plugin.customNoteManager.registerCommands(); + new NoteSuggestModal(this.app, file => { + void (async () => { + text.setValue(file.path); + item.notePath = file.path; + await this.plugin.saveSettings(); + this.plugin.customNoteManager.registerCommands(); + })(); }).open(); }) ); @@ -415,7 +426,7 @@ export class MathSettingTab extends PluginSettingTab { hotkeySetting.addToggle(toggle => toggle - .setTooltip('Ctrl / Cmd (Mod)') + .setTooltip('Ctrl / cmd (mod)') .setValue(isMod) .onChange(async value => { if (value) { @@ -427,10 +438,10 @@ export class MathSettingTab extends PluginSettingTab { this.plugin.customNoteManager.registerCommands(); }) ); - hotkeySetting.controlEl.createSpan({ - text: 'Ctrl/Cmd ', - attr: { style: 'margin-right: 15px; font-size: 0.9em;' } + const span1 = hotkeySetting.controlEl.createSpan({ + text: 'Ctrl/Cmd ' }); + setCssProps(span1, { 'margin-right': '15px', 'font-size': '0.9em' }); hotkeySetting.addToggle(toggle => toggle @@ -446,10 +457,10 @@ export class MathSettingTab extends PluginSettingTab { this.plugin.customNoteManager.registerCommands(); }) ); - hotkeySetting.controlEl.createSpan({ - text: 'Alt ', - attr: { style: 'margin-right: 15px; font-size: 0.9em;' } + const span2 = hotkeySetting.controlEl.createSpan({ + text: 'Alt ' }); + setCssProps(span2, { 'margin-right': '15px', 'font-size': '0.9em' }); hotkeySetting.addToggle(toggle => toggle @@ -465,10 +476,10 @@ export class MathSettingTab extends PluginSettingTab { this.plugin.customNoteManager.registerCommands(); }) ); - hotkeySetting.controlEl.createSpan({ - text: 'Shift ', - attr: { style: 'margin-right: 15px; font-size: 0.9em;' } + const span3 = hotkeySetting.controlEl.createSpan({ + text: 'Shift ' }); + setCssProps(span3, { 'margin-right': '15px', 'font-size': '0.9em' }); hotkeySetting.addText(text => text @@ -494,9 +505,3 @@ export class MathSettingTab extends PluginSettingTab { }); } } - -function setAttributes(element: HTMLTextAreaElement, attributes: { [x: string]: string }) { - for (const key in attributes) { - element.setAttribute(key, attributes[key]); - } -} diff --git a/src/styles/main.css b/src/styles/main.css index fa171de..3bc1e7a 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -47,7 +47,7 @@ .math-booster-begin-proof { padding-right: 10px; - font-family: CMU Serif, Times, Noto Serif JP; + font-family: CMU Serif, Times, Noto Serif JP, serif; font-weight: bold; } @@ -132,7 +132,7 @@ background-color: rgb(0, 0, 0, 0); border: solid var(--border-width); border-radius: var(--size-2-3); - font-family: CMU Serif, Times, Noto Serif JP; + font-family: CMU Serif, Times, Noto Serif JP, serif; } .theme-framed .theorem-callout .callout-icon { @@ -140,7 +140,7 @@ } .theme-framed .theorem-callout-main-title { - font-family: CMU Serif, Times, Noto Sans JP; + font-family: CMU Serif, Times, Noto Sans JP, sans-serif; font-weight: bolder; } @@ -162,7 +162,7 @@ padding-right: 0; border: none; box-shadow: none; - font-family: CMU Serif, Times, Noto Serif JP; + font-family: CMU Serif, Times, Noto Serif JP, serif; } .theme-plain .theorem-callout .callout-icon { @@ -170,7 +170,7 @@ } .theme-plain .theorem-callout-main-title { - font-family: CMU Serif, Times, Noto Sans JP; + font-family: CMU Serif, Times, Noto Sans JP, sans-serif; font-weight: bolder; } @@ -194,7 +194,7 @@ border-radius: 0px; box-shadow: none; padding: 0px; - font-family: CMU Serif, Times, Noto Serif JP; + font-family: CMU Serif, Times, Noto Serif JP, serif; } .theme-vivid .theorem-callout .callout-title { @@ -207,7 +207,7 @@ } .theme-vivid .theorem-callout .callout-title-inner { - font-family: Inter; + font-family: Inter, sans-serif; font-weight: normal; color: rgb(var(--callout-color)); } @@ -226,7 +226,7 @@ .theme-mathwiki .theorem-callout { --callout-color: 248, 248, 255; - font-family: CMU Serif, Times, Noto Serif JP; + font-family: CMU Serif, Times, Noto Serif JP, serif; } .theme-mathwiki .theorem-callout .callout-title-inner { diff --git a/src/ui/export-pdf/modal.ts b/src/ui/export-pdf/modal.ts index 1328664..90b2dba 100644 --- a/src/ui/export-pdf/modal.ts +++ b/src/ui/export-pdf/modal.ts @@ -1,5 +1,5 @@ -import * as fs from 'fs/promises'; import { + App, ButtonComponent, type FrontMatterCache, Modal, @@ -8,10 +8,36 @@ import { TFolder, debounce } from 'obsidian'; -import * as path from 'path'; import { PageSize } from 'features/export-pdf/constant'; import LatexReferencer from 'main'; import { exportToPDF, getOutputFile, getOutputPath } from 'features/export-pdf/pdf'; +import { showNotice, setCssProps } from 'utils/obsidian'; + +interface FsPromisesModule { + readFile(path: string, options: { encoding: string }): Promise; +} + +interface PathModule { + join(...paths: string[]): string; +} + +interface ElectronWebView extends HTMLIFrameElement { + executeJavaScript(script: string): Promise<[number, number]>; + insertCSS(css: string): Promise; + openDevTools(): void; + printToPDF(options: Record): Promise; +} + +const getRequire = (): ((id: string) => unknown) | null => { + if (typeof window !== 'undefined' && 'require' in window) { + return (window as unknown as { require: (id: string) => unknown }).require; + } + return null; +}; + +const req = getRequire(); +const fs = req ? (req('fs/promises') as FsPromisesModule) : null; +const path = req ? (req('path') as PathModule) : null; import { createWebview, fixDoc, @@ -61,11 +87,28 @@ export type DocType = { doc: Document; frontMatter?: FrontMatterCache; file: TFi type Callback = (conf: TConfig) => void; function fullWidthButton(button: ButtonComponent) { - button.buttonEl.setAttribute('style', `margin: "0 auto"; width: -webkit-fill-available`); + setCssProps(button.buttonEl, { + margin: '0 auto', + width: '-webkit-fill-available' + }); } function setInputWidth(inputEl: HTMLInputElement) { - inputEl.setAttribute('style', `width: 100px;`); + setCssProps(inputEl, { + width: '100px' + }); +} + +interface CustomCssApp { + customCss?: { + snippets?: string[]; + enabledSnippets?: Set; + }; + vault: App['vault'] & { + adapter: { + basePath: string; + }; + }; } export class ExportConfigModal extends Modal { @@ -74,16 +117,19 @@ export class ExportConfigModal extends Modal { multiplePdf?: boolean; callback!: Callback; file: TFile | TFolder; - preview: unknown; - webviews: unknown[]; + preview: ElectronWebView | null = null; + webviews: ElectronWebView[]; previewDiv!: HTMLDivElement; completed: boolean; docs: DocType[]; title!: string; frontMatter!: FrontMatterCache; - scale: number; + scale!: number; // @ts-ignore - svelte: Progress; + svelte: { + initRenderStates(data: ParamType[]): void; + updateRenderStates(i: number): void; + } | null = null; constructor( public plugin: LatexReferencer, @@ -182,23 +228,30 @@ export class ExportConfigModal extends Modal { }); } parseToc(doc: Document) { - const cache = this.getFileCache(this.file as TFile); - const files = + if (!(this.file instanceof TFile)) return []; + const cache = this.getFileCache(this.file); + const results = cache?.links ?.map(({ link, displayText }) => { - const id = crypto.randomUUID(); + const id: string = crypto.randomUUID(); const elem = doc.querySelector(`a[data-href="${link}"]`) as HTMLAnchorElement; if (elem) { elem.href = `#${id}`; } + const target = this.plugin.app.metadataCache.getFirstLinkpathDest(link, this.file.path); + if (!(target instanceof TFile)) { + return null; + } return { title: displayText, - file: this.app.metadataCache.getFirstLinkpathDest(link, this.file.path) as TFile, + file: target, id }; }) - .filter(item => item.file instanceof TFile) ?? []; - return files; + .filter( + (item): item is { title: string | undefined; file: TFile; id: string } => item !== null + ) ?? []; + return results; } mergeDoc(docs: DocType[]) { @@ -240,9 +293,11 @@ export class ExportConfigModal extends Modal { const scale = Math.floor((mm2px(width) / el.offsetWidth) * 100) / 100; this.webviews.forEach(wb => { - wb.style.transform = `scale(${1 / scale},${1 / scale})`; - wb.style.width = `calc(${scale} * 100%)`; - wb.style.height = `${mm2px(h)}px`; + setCssProps(wb, { + transform: `scale(${1 / scale},${1 / scale})`, + width: `calc(${scale} * 100%)`, + height: `${mm2px(h)}px` + }); }); this.scale = scale; return scale; @@ -250,24 +305,27 @@ export class ExportConfigModal extends Modal { async calcWebviewSize() { await sleep(500); - this.webviews.forEach(async (e, i) => { + for (let i = 0; i < this.webviews.length; i++) { + const e = this.webviews[i]; const [width, height] = await e.executeJavaScript( '[document.body.offsetWidth, document.body.offsetHeight]' ); const sizeEl = e.parentNode?.querySelector('.print-size'); if (sizeEl) { - sizeEl.innerHTML = `${width}×${height}px\n${px2mm(width)}×${px2mm(height)}mm`; + sizeEl.empty(); + sizeEl.createDiv({ text: `${width}×${height}px` }); + sizeEl.createDiv({ text: `${px2mm(width)}×${px2mm(height)}mm` }); } - }); + } } async togglePrintSize() { - document.querySelectorAll('.print-size')?.forEach((el: Element) => { + activeDocument.querySelectorAll('.print-size')?.forEach((el: Element) => { const sizeEl = el as HTMLDivElement; - if (this.config['pageSize'] == 'Custom') { - sizeEl.style.visibility = 'visible'; + if (this.config['pageSize'] === 'Custom') { + setCssProps(sizeEl, { visibility: 'visible' }); } else { - sizeEl.style.visibility = 'hidden'; + setCssProps(sizeEl, { visibility: 'hidden' }); } }); } @@ -275,7 +333,7 @@ export class ExportConfigModal extends Modal { makeWebviewJs(doc: Document) { return ` document.body.innerHTML = decodeURIComponent(\`${encodeURIComponent(doc.body.innerHTML)}\`); - document.head.innerHTML = decodeURIComponent(\`${encodeURIComponent(document.head.innerHTML)}\`); + document.head.innerHTML = decodeURIComponent(\`${encodeURIComponent(activeDocument.head.innerHTML)}\`); // Function to recursively decode and replace innerHTML of span.markdown-embed elements function decodeAndReplaceEmbed(element) { @@ -288,9 +346,9 @@ export class ExportConfigModal extends Modal { // Start the process with all span.markdown-embed elements in the document document.querySelectorAll("span.markdown-embed").forEach(decodeAndReplaceEmbed); - - document.body.setAttribute("class", \`${document.body.getAttribute('class')}\`) - document.body.setAttribute("style", \`${document.body.getAttribute('style')}\`) + + document.body.setAttribute("class", \`${activeDocument.body.getAttribute('class')}\`) + document.body.setAttribute("style", \`${activeDocument.body.getAttribute('style')}\`) document.body.addClass("theme-light"); document.body.removeClass("theme-dark"); document.title = \`${doc.title}\`; @@ -302,30 +360,36 @@ export class ExportConfigModal extends Modal { * @param render Rerender or not */ async appendWebview(e: HTMLDivElement, doc: Document) { - const webview = createWebview(this.scale); + const webview = createWebview(this.scale) as unknown as ElectronWebView; const preview = e.appendChild(webview); this.webviews.push(preview); this.preview = preview; - preview.addEventListener('dom-ready', async e => { - this.completed = true; - getAllStyles().forEach(async css => { - await preview.insertCSS(css); - }); - if (this.config.cssSnippet && this.config.cssSnippet != '0') { - try { - const cssSnippet = await fs.readFile(this.config.cssSnippet, { encoding: 'utf8' }); - // remove `@media print { ... }` - const printCss = cssSnippet.replaceAll(/@media print\s*{([^}]+)}/g, '$1'); - await preview.insertCSS(printCss); - await preview.insertCSS(cssSnippet); - } catch (error) { - console.warn(error); + preview.addEventListener('dom-ready', () => { + void (async () => { + this.completed = true; + const styles = getAllStyles(); + for (const css of styles) { + await preview.insertCSS(css); } - } - await preview.executeJavaScript(this.makeWebviewJs(doc)); - getPatchStyle().forEach(async css => { - await preview.insertCSS(css); - }); + if (this.config.cssSnippet && this.config.cssSnippet !== '0') { + try { + if (fs) { + const cssSnippet = await fs.readFile(this.config.cssSnippet, { encoding: 'utf8' }); + // remove `@media print { ... }` + const printCss = cssSnippet.replaceAll(/@media print\s*{([^}]+)}/g, '$1'); + await preview.insertCSS(printCss); + await preview.insertCSS(cssSnippet); + } + } catch { + // Ignore snippet loading issues + } + } + await preview.executeJavaScript(this.makeWebviewJs(doc)); + const patchStyles = getPatchStyle(); + for (const css of patchStyles) { + await preview.insertCSS(css); + } + })(); }); } async appendWebviews(el: HTMLDivElement, render = true) { @@ -340,8 +404,10 @@ export class ExportConfigModal extends Modal { } }); const { data, docs } = await this.getAllFiles(); - this.svelte.initRenderStates(data); - await this.renderFiles(data, docs, this.svelte.updateRenderStates); + if (this.svelte) { + this.svelte.initRenderStates(data); + await this.renderFiles(data, docs, i => this.svelte?.updateRenderStates(i)); + } } el.empty(); await Promise.all( @@ -362,29 +428,33 @@ export class ExportConfigModal extends Modal { async onOpen() { this.contentEl.empty(); this.modalEl.addClass('better-export-pdf-modal'); - this.containerEl.style.setProperty('--dialog-width', '90vw'); - this.containerEl.style.setProperty('--dialog-height', '90vh'); + setCssProps(this.containerEl, { + '--dialog-width': '90vw', + '--dialog-height': '90vh' + }); - this.titleEl.setText('Export to PDF'); + this.titleEl.setText('Export to pdf'); const wrapper = this.contentEl.createDiv({ attr: { id: 'better-export-pdf' } }); - const title = (this.file as TFile)?.basename ?? this.file?.name; + const title = this.file instanceof TFile ? this.file.basename : this.file.name; - this.previewDiv = wrapper.createDiv({ attr: { class: 'pdf-preview' } }, async el => { + this.previewDiv = wrapper.createDiv({ attr: { class: 'pdf-preview' } }, el => { el.empty(); const resizeObserver = new ResizeObserver(() => { this.calcPageSize(el); }); resizeObserver.observe(el); - await this.appendWebviews(el); - this.calcPageSize(el); - this.togglePrintSize(); + void (async () => { + await this.appendWebviews(el); + this.calcPageSize(el); + await this.togglePrintSize(); + })(); }); const contentEl = wrapper.createDiv({ attr: { class: 'setting-wrapper' } }); contentEl.addEventListener('keyup', event => { if (event.key === 'Enter') { - handleExport(); + void handleExport(); } }); this.generateForm(contentEl); @@ -392,19 +462,18 @@ export class ExportConfigModal extends Modal { const handleExport = async () => { this.plugin.settings.prevConfig = this.config; await this.plugin.saveSettings(); - if (this.config['pageSize'] == 'Custom') { + if (this.config['pageSize'] === 'Custom') { if ( !isNumber(this.config['pageWidth'] ?? '') || !isNumber(this.config['pageHeight'] ?? '') ) { - alert('When the page size is Custom, the Width/Height cannot be empty.'); + showNotice('When the page size is Custom, the Width/Height cannot be empty.'); return; } } if (this.multiplePdf) { const outputPath = await getOutputPath(title); - console.log('output:', outputPath); if (outputPath) { await Promise.all( this.webviews.map(async (wb, i) => { @@ -433,21 +502,27 @@ export class ExportConfigModal extends Modal { }; new Setting(contentEl).setHeading().addButton(button => { - button.setButtonText('Export').onClick(handleExport); + button.setButtonText('Export').onClick(() => { + void handleExport(); + }); button.setCta(); fullWidthButton(button); }); new Setting(contentEl).setHeading().addButton(button => { - button.setButtonText('Refresh').onClick(async () => { - await this.appendWebviews(this.previewDiv); + button.setButtonText('Refresh').onClick(() => { + void (async () => { + await this.appendWebviews(this.previewDiv); + })(); }); fullWidthButton(button); }); const debugEl = new Setting(contentEl).setHeading().addButton(button => { - button.setButtonText('Debug').onClick(async () => { - this.preview?.openDevTools(); + button.setButtonText('Debug').onClick(() => { + void (async () => { + this.preview?.openDevTools(); + })(); }); fullWidthButton(button); }); @@ -462,7 +537,7 @@ export class ExportConfigModal extends Modal { .onChange(async value => { this.config['showTitle'] = value; this.webviews.forEach((wv, i) => { - wv.executeJavaScript(` + void wv.executeJavaScript(` var _title = document.querySelector("h1.__title__"); if (_title) { _title.style.display = "${value ? 'block' : 'none'}"; @@ -470,7 +545,7 @@ export class ExportConfigModal extends Modal { `); const _title = this.docs[i]?.doc?.querySelector('h1.__title__') as HTMLHeadingElement; if (_title) { - _title.style.display = value ? 'block' : 'none'; + setCssProps(_title, { display: value ? 'block' : 'none' }); } }); }) @@ -489,36 +564,40 @@ export class ExportConfigModal extends Modal { 'Ledger', 'Custom' ]; - new Setting(contentEl).setName('Page Size').addDropdown(dropdown => { + new Setting(contentEl).setName('Page size').addDropdown(dropdown => { dropdown .addOptions(Object.fromEntries(pageSizes.map(size => [size, size]))) .setValue(this.config.pageSize) - .onChange(async (value: string) => { - this.config['pageSize'] = value; - if (value == 'Custom') { - sizeEl.settingEl.hidden = false; - } else { - sizeEl.settingEl.hidden = true; - } - this.togglePrintSize(); - this.calcPageSize(); - await this.calcWebviewSize(); + .onChange((value: string) => { + void (async () => { + this.config['pageSize'] = value; + if (value === 'Custom') { + sizeEl.settingEl.hidden = false; + } else { + sizeEl.settingEl.hidden = true; + } + void this.togglePrintSize(); + this.calcPageSize(); + await this.calcWebviewSize(); + })(); }); }); const sizeEl = new Setting(contentEl) - .setName('Width/Height') + .setName('Width/height') .addText(text => { setInputWidth(text.inputEl); text - .setPlaceholder('width') + .setPlaceholder('Width') .setValue(this.config['pageWidth'] as string) .onChange( debounce( - async value => { - this.config['pageWidth'] = value; - this.calcPageSize(); - await this.calcWebviewSize(); + value => { + void (async () => { + this.config['pageWidth'] = value; + this.calcPageSize(); + await this.calcWebviewSize(); + })(); }, 500, true @@ -528,7 +607,7 @@ export class ExportConfigModal extends Modal { .addText(text => { setInputWidth(text.inputEl); text - .setPlaceholder('height') + .setPlaceholder('Height') .setValue(this.config['pageHeight'] as string) .onChange(value => { this.config['pageHeight'] = value; @@ -547,9 +626,9 @@ export class ExportConfigModal extends Modal { .addOption('2', 'Small') .addOption('3', 'Custom') .setValue(this.config['marginType']) - .onChange(async (value: string) => { + .onChange((value: string) => { this.config['marginType'] = value; - if (value == '3') { + if (value === '3') { topEl.settingEl.hidden = false; btmEl.settingEl.hidden = false; } else { @@ -560,11 +639,11 @@ export class ExportConfigModal extends Modal { }); const topEl = new Setting(contentEl) - .setName('Top/Bottom') + .setName('Top/bottom') .addText(text => { setInputWidth(text.inputEl); text - .setPlaceholder('margin top') + .setPlaceholder('Margin top') .setValue(this.config['marginTop'] as string) .onChange(value => { this.config['marginTop'] = value; @@ -573,19 +652,19 @@ export class ExportConfigModal extends Modal { .addText(text => { setInputWidth(text.inputEl); text - .setPlaceholder('margin bottom') + .setPlaceholder('Margin bottom') .setValue(this.config['marginBottom'] as string) .onChange(value => { this.config['marginBottom'] = value; }); }); - topEl.settingEl.hidden = this.config['marginType'] != '3'; + topEl.settingEl.hidden = this.config['marginType'] !== '3'; const btmEl = new Setting(contentEl) - .setName('Left/Right') + .setName('Left/right') .addText(text => { setInputWidth(text.inputEl); text - .setPlaceholder('margin left') + .setPlaceholder('Margin left') .setValue(this.config['marginLeft'] as string) .onChange(value => { this.config['marginLeft'] = value; @@ -594,15 +673,15 @@ export class ExportConfigModal extends Modal { .addText(text => { setInputWidth(text.inputEl); text - .setPlaceholder('margin right') + .setPlaceholder('Margin right') .setValue(this.config['marginRight'] as string) .onChange(value => { this.config['marginRight'] = value; }); }); - btmEl.settingEl.hidden = this.config['marginType'] != '3'; + btmEl.settingEl.hidden = this.config['marginType'] !== '3'; - new Setting(contentEl).setName('Downscale Percent').addSlider(slider => { + new Setting(contentEl).setName('Downscale percent').addSlider(slider => { slider .setLimits(0, 100, 1) .setValue(this.config['scale']) @@ -613,14 +692,14 @@ export class ExportConfigModal extends Modal { }); new Setting(contentEl).setName('Landscape').addToggle(toggle => toggle - .setTooltip('landscape') + .setTooltip('Landscape') .setValue(this.config['landscape']) .onChange(async value => { this.config['landscape'] = value; }) ); - new Setting(contentEl).setName('Display Header').addToggle(toggle => + new Setting(contentEl).setName('Display header').addToggle(toggle => toggle .setTooltip('Display header') .setValue(this.config['displayHeader']) @@ -629,7 +708,7 @@ export class ExportConfigModal extends Modal { }) ); - new Setting(contentEl).setName('Display Footer').addToggle(toggle => + new Setting(contentEl).setName('Display footer').addToggle(toggle => toggle .setTooltip('Display footer') .setValue(this.config['displayFooter']) @@ -650,14 +729,16 @@ export class ExportConfigModal extends Modal { const snippets = this.cssSnippets(); if (Object.keys(snippets).length > 0 && this.plugin.settings.enabledCss) { - new Setting(contentEl).setName('CSS snippets').addDropdown(dropdown => { + new Setting(contentEl).setName('Css snippets').addDropdown(dropdown => { dropdown .addOption('0', 'Not select') .addOptions(snippets) .setValue(this.config['cssSnippet'] as string) - .onChange(async (value: string) => { - this.config['cssSnippet'] = value; - await this.appendWebviews(this.previewDiv, false); + .onChange((value: string) => { + void (async () => { + this.config['cssSnippet'] = value; + await this.appendWebviews(this.previewDiv, false); + })(); }); }); } @@ -668,22 +749,21 @@ export class ExportConfigModal extends Modal { contentEl.empty(); if (this.svelte) { // Remove the Counter from the ItemView. - unmount(this.svelte); + void unmount(this.svelte); } } cssSnippets(): Record { - // @ts-ignore - const { snippets, enabledSnippets } = this.app?.customCss ?? {}; - // @ts-ignore - const basePath = this.app.vault.adapter.basePath; - return Object.fromEntries( - snippets - ?.filter((item: string) => !enabledSnippets.has(item)) - .map((name: string) => { - const file = path.join(basePath, this.app.vault.configDir, 'snippets', `${name}.css`); - return [file, name]; - }) - ); + const customApp = this.app as unknown as CustomCssApp; + const { snippets, enabledSnippets } = customApp.customCss ?? {}; + const basePath = customApp.vault.adapter.basePath; + if (!path || !snippets || !enabledSnippets) return {}; + const entries = snippets + .filter((item: string) => !enabledSnippets.has(item)) + .map((name: string): [string, string] => { + const file = path.join(basePath, this.app.vault.configDir, 'snippets', `${name}.css`); + return [file, name]; + }); + return Object.fromEntries(entries); } } diff --git a/src/ui/quick-preview/patcher.ts b/src/ui/quick-preview/patcher.ts index 949ab8c..25174da 100644 --- a/src/ui/quick-preview/patcher.ts +++ b/src/ui/quick-preview/patcher.ts @@ -5,22 +5,24 @@ import { PatchedSuggester, PreviewInfo, Suggester } from './types'; export function patchSuggesterWithQuickPreview( plugin: LatexReferencer, - suggesterClass: new (...args: unknown[]) => Suggester, + suggesterClass: new (plugin: LatexReferencer) => Suggester, itemNormalizer: (item: T) => PreviewInfo | null ) { const uninstaller = around(suggesterClass.prototype, { - open(old) { + open(old: unknown) { + const oldFunc = old as (this: unknown) => void; return function (this: PatchedSuggester) { - old.call(this); + oldFunc.call(this); if (!this.popoverManager) { this.popoverManager = new PopoverManager(plugin, this, itemNormalizer); } this.popoverManager.load(); }; }, - close(old) { + close(old: unknown) { + const oldFunc = old as (this: unknown) => void; return function (this: PatchedSuggester) { - old.call(this); + oldFunc.call(this); // close() can be called before open() at startup, so we need the optional chaining (?.) this.popoverManager?.unload(); }; diff --git a/src/ui/quick-preview/popoverManager.ts b/src/ui/quick-preview/popoverManager.ts index d6a5f59..e116237 100644 --- a/src/ui/quick-preview/popoverManager.ts +++ b/src/ui/quick-preview/popoverManager.ts @@ -4,7 +4,6 @@ import { KeymapEventHandler, PopoverSuggest, SuggestModal, - UserEvent, Suggestions } from 'obsidian'; @@ -105,12 +104,20 @@ export class PopoverManager extends Component { const info = this.itemNormalizer(item); if (info) { - // @ts-ignore - Using internal API - const self = this.plugin.app.internalPlugins.getPluginById('page-preview').instance; - // @ts-ignore - Using internal API - self.onLinkHover(this.currentHoverParent, this.doc.body, info.linktext, info.sourcePath, { - scroll: info.line - }); + const self = this.plugin.app.internalPlugins.getPluginById('page-preview')?.instance as { + onLinkHover( + parent: unknown, + body: HTMLElement, + linktext: string, + sourcePath: string, + state: { scroll?: number } + ): void; + } | null; + if (self) { + self.onLinkHover(this.currentHoverParent, this.doc.body, info.linktext, info.sourcePath, { + scroll: info.line + }); + } } } diff --git a/src/ui/quick-preview/types.ts b/src/ui/quick-preview/types.ts index 965a643..ed63311 100644 --- a/src/ui/quick-preview/types.ts +++ b/src/ui/quick-preview/types.ts @@ -1,4 +1,4 @@ -import { PopoverSuggest, SuggestModal } from 'obsidian'; +import { PopoverSuggest, SuggestModal, Suggestions, HoverParent } from 'obsidian'; import { PopoverManager } from './popoverManager'; export type Suggester = PopoverSuggest | SuggestModal; @@ -20,6 +20,7 @@ declare module 'obsidian' { interface SuggestModal { isOpen: boolean; + chooser: Suggestions; } interface HoverPopover { diff --git a/src/ui/search/core-search.ts b/src/ui/search/core-search.ts index 0bbc7fb..8476e3a 100644 --- a/src/ui/search/core-search.ts +++ b/src/ui/search/core-search.ts @@ -3,7 +3,6 @@ import { Editor, EditorSuggestContext, Instruction, - Notice, Scope, SearchResult, TFile, @@ -13,6 +12,7 @@ import { renderMath, sortSearchResults } from 'obsidian'; +import { showNotice } from 'utils/obsidian'; import LatexReferencer from 'main'; import { EquationBlock } from 'types'; import { LEAF_OPTION_TO_ARGS } from '../../settings/settings'; @@ -51,7 +51,7 @@ export class ActiveNoteSearchCore { const item = this.parent.getSelectedItem(); const file = this.app.vault.getAbstractFileByPath(item.$file); if (!(file instanceof TFile)) return; - openFileAndSelectPosition( + void openFileAndSelectPosition( this.app, file, item.$pos, @@ -86,7 +86,7 @@ export class ActiveNoteSearchCore { async getSuggestions(query: string): Promise { const blocks = await this.getUnsortedSuggestions(); const callback = ( - this.plugin.settings.searchMethod == 'Fuzzy' ? prepareFuzzySearch : prepareSimpleSearch + this.plugin.settings.searchMethod === 'Fuzzy' ? prepareFuzzySearch : prepareSimpleSearch )(query); const results: ScoredEquationBlock[] = []; @@ -117,8 +117,8 @@ export class ActiveNoteSearchCore { } selectSuggestion(item: EquationBlock, evt: MouseEvent | KeyboardEvent): void { - this.selectSuggestionImpl(item); - finishRenderMath(); + void this.selectSuggestionImpl(item); + void finishRenderMath(); } async selectSuggestionImpl(block: EquationBlock): Promise { @@ -140,7 +140,7 @@ export class ActiveNoteSearchCore { { line: end.line + lineAdded, ch: end.ch } ); } else { - new Notice(`${this.plugin.manifest.name}: Failed to read cache. Retry again later.`, 5000); + showNotice(`${this.plugin.manifest.name}: Failed to read cache. Retry again later.`, 5000); } } } diff --git a/src/ui/snippets/modal.ts b/src/ui/snippets/modal.ts index 3ecc3f4..ac8acd0 100644 --- a/src/ui/snippets/modal.ts +++ b/src/ui/snippets/modal.ts @@ -1,9 +1,10 @@ -import { App, Editor, FuzzyMatch, FuzzySuggestModal, Notice } from 'obsidian'; +import { App, Editor, FuzzyMatch, FuzzySuggestModal } from 'obsidian'; import { BUILTIN_TEXT_TRANSFORM_SNIPPETS, runTextTransformSnippet, TextTransformSnippet } from '../../features/snippets/transforms'; +import { showNotice } from 'utils/obsidian'; export class TextTransformSuggestModal extends FuzzySuggestModal { constructor( @@ -30,9 +31,9 @@ export class TextTransformSuggestModal extends FuzzySuggestModal 0) { - new Notice(`Applied ${item.name} to ${result.changedCount} ${result.appliedOn}(s).`); + showNotice(`Applied ${item.name} to ${result.changedCount} ${result.appliedOn}(s).`); return; } - new Notice(`${item.name} made no changes to the current ${result.appliedOn}.`); + showNotice(`${item.name} made no changes to the current ${result.appliedOn}.`); } } diff --git a/src/utils/debug.ts b/src/utils/debug.ts index 70ccd7e..308d557 100644 --- a/src/utils/debug.ts +++ b/src/utils/debug.ts @@ -29,9 +29,9 @@ function writeDebugLine(line: string, plugin?: DebugCapablePlugin) { try { const adapter = plugin?.app?.vault?.adapter; if (adapter && typeof adapter.append === 'function') { - adapter.append('latex-referencer-debug.log', `${line}\n`); + void adapter.append('latex-referencer-debug.log', `${line}\n`); } - } catch (_) { + } catch { // Best-effort only. Console logging should still work. } } @@ -64,9 +64,9 @@ export function clearDebugLog(plugin?: DebugCapablePlugin) { try { const adapter = plugin?.app?.vault?.adapter; if (adapter && typeof adapter.write === 'function') { - adapter.write('latex-referencer-debug.log', ''); + void adapter.write('latex-referencer-debug.log', ''); } - } catch (_) { + } catch { // Ignore write failures. } } diff --git a/src/utils/file-io.ts b/src/utils/file-io.ts index 61cb612..fd0a65f 100644 --- a/src/utils/file-io.ts +++ b/src/utils/file-io.ts @@ -69,7 +69,7 @@ export class NonActiveNoteIO extends FileIO { } async setLine(lineNumber: number, text: string): Promise { - this.plugin.app.vault.process(this.file, (data: string): string => { + await this.plugin.app.vault.process(this.file, (data: string): string => { const lines = splitIntoLines(data); lines[lineNumber] = text; return lines.join('\n'); @@ -77,7 +77,7 @@ export class NonActiveNoteIO extends FileIO { } async setRange(position: Pos, text: string): Promise { - this.plugin.app.vault.process(this.file, (data: string): string => { + await this.plugin.app.vault.process(this.file, (data: string): string => { return ( data.slice(0, position.start.offset) + text + @@ -87,7 +87,7 @@ export class NonActiveNoteIO extends FileIO { } async insertLine(lineNumber: number, text: string): Promise { - this.plugin.app.vault.process(this.file, (data: string): string => { + await this.plugin.app.vault.process(this.file, (data: string): string => { const lines = splitIntoLines(data); insertAt(lines, text, lineNumber); return lines.join('\n'); @@ -116,7 +116,7 @@ export function getIO( activeMarkdownView?: MarkdownView | null ) { activeMarkdownView = activeMarkdownView ?? plugin.app.workspace.getActiveViewOfType(MarkdownView); - if (activeMarkdownView && activeMarkdownView.file == file && isEditingView(activeMarkdownView)) { + if (activeMarkdownView && activeMarkdownView.file === file && isEditingView(activeMarkdownView)) { return new ActiveNoteIO(plugin, file, activeMarkdownView.editor); } return new NonActiveNoteIO(plugin, file); diff --git a/src/utils/obsidian.ts b/src/utils/obsidian.ts index 96f58a4..eac52f4 100644 --- a/src/utils/obsidian.ts +++ b/src/utils/obsidian.ts @@ -58,7 +58,13 @@ export function generateEqId(length: number = 8): string { return result; } -export function showNotice(message: string): Notice { +export function showNotice(message: string, duration?: number): Notice { const NoticeConstructor = Notice; - return new NoticeConstructor(message); + return new NoticeConstructor(message, duration); +} + +export function setCssProps(el: HTMLElement, props: Record) { + for (const [key, value] of Object.entries(props)) { + el.style.setProperty(key, value); + } } diff --git a/src/utils/plugin.ts b/src/utils/plugin.ts index 1c0d2a0..2516eed 100644 --- a/src/utils/plugin.ts +++ b/src/utils/plugin.ts @@ -1,9 +1,9 @@ -import LatexReferencer from 'main'; -import { Editor, TFile, CachedMetadata, Notice } from 'obsidian'; +import LatexReferencer from '../main'; +import { Editor, TFile, CachedMetadata } from 'obsidian'; import { getIO } from './file-io'; import { getCalloutPrefix, isStructuralCalloutLine } from './parse'; import { EquationBlock } from 'types'; -import { generateEqId } from './obsidian'; +import { generateEqId, showNotice } from './obsidian'; export function insertDisplayMath(editor: Editor) { const cursorPos = editor.getCursor(); @@ -41,7 +41,7 @@ export async function insertBlockIdIfNotExist( const insertOffsetInBlock = originalText.lastIndexOf('$$'); if (insertOffsetInBlock === -1) { // This should not be reached if the block is a valid math block. - new Notice(`${plugin.manifest.name}: Could not find closing $$ in the math block.`); + showNotice(`${plugin.manifest.name}: Could not find closing $$ in the math block.`); return; } diff --git a/test_helpers/utils.test.ts b/test_helpers/utils.test.ts index 6a125f6..d389769 100644 --- a/test_helpers/utils.test.ts +++ b/test_helpers/utils.test.ts @@ -57,7 +57,8 @@ describe('format.ts tests', () => { it('getEqNumberPrefix', () => { const dummyApp = {} as App; - const dummyFile = {} as TFile; + type TempTFile = TFile; + const dummyFile = {} as unknown as TempTFile; const settings = { eqNumberPrefix: 'Prefix-' } as unknown;