lin fix I

This commit is contained in:
JK 2026-06-04 21:01:36 +02:00
parent b6392a5523
commit 157c0b57ab
23 changed files with 276 additions and 202 deletions

View file

@ -1,5 +1,9 @@
import { StateField } from '@codemirror/state';
if (typeof window !== 'undefined' && !('require' in window)) {
(window as unknown as { require: unknown }).require = require;
}
const toForwardSlashes = (value: string): string => value.replace(/\\/g, '/');
const joinPath = (...parts: string[]): string => {
const normalizedParts = parts.map(toForwardSlashes).filter(part => part !== '' && part !== '.');

View file

@ -184,7 +184,7 @@ function createTagManagerPlugin(
): ViewPlugin<object> {
return ViewPlugin.fromClass(
class {
timeout: any = null;
timeout: number | null = null;
blockedBySelection = false;
constructor(view: EditorView) {

View file

@ -1,4 +1,4 @@
import { TFile, App, CachedMetadata } from 'obsidian';
import { TFile, App, CachedMetadata, Pos } from 'obsidian';
import { EquationBlock } from 'types';
import { trimMathText, parseMarkdownComment, parseYamlLike } from 'utils/parse';
@ -53,7 +53,7 @@ export class ActiveNoteEquationProvider {
$file: file.path,
$type: 'equation' as const,
$blockId: blockId,
$pos: position as any,
$pos: position as unknown as Pos,
$position: { start: position.start.line, end: position.end.line },
$mathText: trimmedMathText,
$manualTag: manualTag,

View file

@ -14,7 +14,7 @@ export const createEquationNumberProcessor = (plugin: LatexReferencer): Markdown
// In reading view, we need to read the file content directly.
// We cannot rely on an active editor being open.
plugin.app.vault.cachedRead(file).then(content => {
void plugin.app.vault.cachedRead(file).then(content => {
const equations = processActiveNoteEquations(plugin, file, content);
if (equations.size === 0) return;

View file

@ -75,7 +75,7 @@ function processEquationLinksInElement(node: HTMLElement, plugin: LatexReference
* (e.g., on initial page load).
*/
function scanExistingCallouts(plugin: LatexReferencer): void {
const calloutBlocks = document.querySelectorAll('.cm-embed-block.cm-callout');
const calloutBlocks = activeDocument.querySelectorAll('.cm-embed-block.cm-callout');
for (const block of calloutBlocks) {
if (block.instanceOf(HTMLElement)) {
processEquationLinksInElement(block, plugin);
@ -84,7 +84,7 @@ function scanExistingCallouts(plugin: LatexReferencer): void {
}
/**
* Sets up a MutationObserver on document.body to detect callout rendering
* Sets up a MutationObserver on activeDocument.body to detect callout rendering
* and reprocess equation links within them. Also performs an initial scan
* after a delay to catch callouts rendered before the cache was ready.
*
@ -97,7 +97,6 @@ export function setupDOMObserver(plugin: LatexReferencer): () => void {
if (mutation.type === 'childList') {
for (const node of mutation.addedNodes) {
if (node.instanceOf(HTMLElement)) {
if (!node.querySelector && !node.matches) continue;
processEquationLinksInElement(node, plugin);
}
}
@ -105,7 +104,7 @@ export function setupDOMObserver(plugin: LatexReferencer): () => void {
}
});
observer.observe(document.body, { childList: true, subtree: true });
observer.observe(activeDocument.body, { childList: true, subtree: true });
// Initial scan: process callouts that were rendered before the observer
// started or before the equation cache was ready. We use onLayoutReady

View file

@ -18,7 +18,7 @@ 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 any);
const provided = provider.provide({ path, subpath }, targetFile, subpathResult as unknown);
if (provided) {
return provided;
}

View file

@ -88,16 +88,20 @@ export const createLivePreviewLinkRendererPlugin = (plugin: LatexReferencer): Ex
mathLinkWrapper.onclick = (evt: MouseEvent) => {
evt.preventDefault();
app.workspace.openLinkText(this.outLinkText, this.sourcePath, evt.ctrlKey || evt.metaKey);
void app.workspace.openLinkText(
this.outLinkText,
this.sourcePath,
evt.ctrlKey || evt.metaKey
);
};
mathLinkWrapper.onmousedown = (evt: MouseEvent) => {
if (evt.button == 1) evt.preventDefault();
if (evt.button === 1) evt.preventDefault();
};
mathLinkWrapper.onauxclick = (evt: MouseEvent) => {
if (evt.button == 1) {
app.workspace.openLinkText(this.outLinkText, this.sourcePath, true);
if (evt.button === 1) {
void app.workspace.openLinkText(this.outLinkText, this.sourcePath, true);
}
};
@ -182,7 +186,7 @@ export const createLivePreviewLinkRendererPlugin = (plugin: LatexReferencer): Ex
this.decorations = builder.finish();
if (this.decorations.size > 0) {
finishRenderMath();
void finishRenderMath();
}
}
},

View file

@ -61,7 +61,7 @@ export class LatexRenderChild extends MarkdownRenderChild {
const linkEl = this.containerEl;
setMathLink(mathLink, linkEl);
}
finishRenderMath();
void finishRenderMath();
}
}

22
src/declarations.d.ts vendored
View file

@ -1,4 +1,4 @@
import { PaneType, SplitDirection, UserEvent } from 'obsidian';
import { PaneType, SplitDirection, UserEvent, Scope, View, MarkdownFileInfo } from 'obsidian';
import { EditorView } from '@codemirror/view';
declare module 'obsidian' {
@ -12,16 +12,26 @@ declare module 'obsidian' {
setSelectedItem(index: number, event: UserEvent | null): void;
}
interface Workspace {
getActiveFileView(): View | null;
activeEditor?: MarkdownFileInfo | null;
}
interface App {
workspace: Workspace;
commands: {
commands: Record<string, unknown>;
removeCommand(id: string): void;
};
plugins: {
enabledPlugins: Set<string>;
plugins: {
[id: string]: any;
[id: string]: unknown;
};
getPlugin: (id: string) => Plugin | null;
};
internalPlugins: {
getPluginById(id: string): Plugin & { instance: any };
getPluginById(id: string): Plugin & { instance: unknown };
};
}
interface Editor {
@ -39,6 +49,12 @@ declare module 'obsidian' {
// 3. USE the complete interface here as well
chooser: Suggestions<T>;
}
interface Vault {
getConfig(key: string): unknown;
}
namespace MarkdownRenderer {
function postProcess(app: App, context: unknown): Promise<void>;
}
}
export type LeafArgs =

View file

@ -1,5 +1,6 @@
import { Notice, TFile } from 'obsidian';
import { Command, TFile } from 'obsidian';
import type LatexReferencer from '../../main';
import { showNotice } from 'utils/obsidian';
export class CustomNoteManager {
private registeredCommandIds: string[] = [];
@ -12,7 +13,7 @@ export class CustomNoteManager {
registerCommands() {
// 1. Unregister all previously registered commands
const appCommands = (this.plugin.app as any).commands;
const appCommands = this.plugin.app.commands;
if (appCommands && typeof appCommands.removeCommand === 'function') {
for (const cmdId of this.registeredCommandIds) {
try {
@ -34,11 +35,11 @@ export class CustomNoteManager {
const commandId = `open-custom-note-${item.id}`;
const fullCommandId = `${this.plugin.manifest.id}:${commandId}`;
const commandConfig: any = {
const commandConfig: Command = {
id: commandId,
name: `Open custom note: ${displayName}`,
callback: () => {
this.openCustomNote(item.notePath);
void this.openCustomNote(item.notePath);
}
};
@ -66,7 +67,7 @@ export class CustomNoteManager {
const leaf = this.plugin.app.workspace.getLeaf('tab');
await leaf.openFile(file);
} else {
new Notice(
showNotice(
`Custom note not found at path: ${notePath}. Please verify your configuration in settings.`
);
}

View file

@ -1,6 +1,36 @@
import * as electron from 'electron';
import * as fs from 'fs/promises';
import { type FrontMatterCache, Notice } from 'obsidian';
import { type FrontMatterCache } from 'obsidian';
import { showNotice } from 'utils/obsidian';
interface ElectronModule {
remote: {
shell: {
openPath(path: string): Promise<boolean>;
};
dialog: {
showSaveDialog(
options: Record<string, unknown>
): Promise<{ canceled: boolean; filePath?: string }>;
showOpenDialog(
options: Record<string, unknown>
): Promise<{ canceled: boolean; filePaths: string[] }>;
};
};
}
interface FsModule {
writeFile(path: string, data: Uint8Array): Promise<void>;
}
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 FsModule) : null;
const electron = req ? (req('electron') as ElectronModule) : null;
import {
PDFArray,
PDFDict,
@ -337,11 +367,11 @@ export async function editPDF(
const pdfDoc = await PDFDocument.load(data);
const posistions = await getDestPosition(pdfDoc);
setAnchors(pdfDoc, posistions);
await setAnchors(pdfDoc, posistions);
const outlines = generateOutlines(headings, posistions, maxLevel);
setOutline(pdfDoc, outlines);
await setOutline(pdfDoc, outlines);
if (displayMetadata) {
setMetadata(pdfDoc, frontMatter ?? {});
}
@ -354,37 +384,44 @@ export function setMetadata(
pdfDoc: PDFDocument,
{ title, author, keywords, subject, creator, created_at, updated_at }: FrontMatterCache
) {
if (title) {
if (title && typeof title === 'string') {
pdfDoc.setTitle(title, { showInWindowTitleBar: true });
}
if (author) {
if (Array.isArray(author)) {
pdfDoc.setAuthor(author.join(', '));
pdfDoc.setAuthor(author.map(a => String(a)).join(', '));
} else {
pdfDoc.setAuthor(author.toString());
pdfDoc.setAuthor(String(author));
}
}
if (keywords) {
pdfDoc.setKeywords(typeof keywords == 'string' ? [keywords] : keywords);
const kwList = Array.isArray(keywords)
? keywords.map(k => String(k))
: typeof keywords === 'string'
? [keywords]
: [String(keywords)];
pdfDoc.setKeywords(kwList);
}
if (subject) {
if (subject && typeof subject === 'string') {
pdfDoc.setSubject(subject);
}
pdfDoc.setCreator(creator ?? 'Obsidian');
const creatorStr = typeof creator === 'string' ? creator : 'Obsidian';
pdfDoc.setCreator(creatorStr);
pdfDoc.setProducer('Obsidian');
pdfDoc.setCreationDate(new Date(created_at ?? new Date()));
pdfDoc.setModificationDate(new Date(updated_at ?? new Date()));
const creationDate = created_at ? new Date(created_at as string | number | Date) : new Date();
pdfDoc.setCreationDate(creationDate);
const modDate = updated_at ? new Date(updated_at as string | number | Date) : new Date();
pdfDoc.setModificationDate(modDate);
}
export async function exportToPDF(
outputFile: string,
config: TConfig & PluginSettings, // Changed from BetterExportPdfPluginSettings
w: any,
w: { printToPDF(options: Record<string, unknown>): Promise<ArrayBuffer> },
{ doc, frontMatter }: DocType
) {
console.log('output pdf:', outputFile);
let pageSize = config['pageSize'] as PageSizeType;
if (config['pageSize'] == 'Custom' && config['pageWidth'] && config['pageHeight']) {
if (config['pageSize'] === 'Custom' && config['pageWidth'] && config['pageHeight']) {
pageSize = {
width: safeParseFloat(config['pageWidth'], 210) / 25.4,
height: safeParseFloat(config['pageHeight'], 297) / 25.4
@ -395,7 +432,7 @@ export async function exportToPDF(
if (scale > 200 || scale < 10) {
scale = 100;
}
const printOptions: any = {
const printOptions: Record<string, unknown> = {
landscape: config?.['landscape'],
printBackground: config?.['printBackground'],
generateTaggedPDF: config?.['generateTaggedPDF'],
@ -413,7 +450,7 @@ export async function exportToPDF(
: '<span></span>'
};
if (config.marginType == '0') {
if (config.marginType === '0') {
printOptions['margins'] = {
marginType: 'custom',
top: 0,
@ -421,11 +458,11 @@ export async function exportToPDF(
left: 0,
right: 0
};
} else if (config.marginType == '1') {
} else if (config.marginType === '1') {
printOptions['margins'] = {
marginType: 'default'
};
} else if (config.marginType == '2') {
} else if (config.marginType === '2') {
printOptions['margins'] = {
marginType: 'custom',
top: 0.1,
@ -433,7 +470,7 @@ export async function exportToPDF(
left: 0.1,
right: 0.1
};
} else if (config.marginType == '3') {
} else if (config.marginType === '3') {
// Custom Margin
printOptions['margins'] = {
marginType: 'custom',
@ -455,21 +492,22 @@ export async function exportToPDF(
maxLevel: safeParseInt(config?.maxLevel, 6)
});
await fs.writeFile(outputFile, data);
if (fs) {
await fs.writeFile(outputFile, data);
}
if (config.open) {
// @ts-ignore
electron.remote.shell.openPath(outputFile);
if (config.open && electron) {
void electron.remote.shell.openPath(outputFile);
}
} catch (error) {
console.error(error);
new Notice(`Export to PDF failed: ${error}`);
showNotice(`Export to PDF failed: ${String(error)}`);
}
}
export async function getOutputFile(filename: string, isTimestamp?: boolean) {
// @ts-ignore
const result: Electron.SaveDialogReturnValue = await electron.remote.dialog.showSaveDialog({
if (!electron) return;
const result = await electron.remote.dialog.showSaveDialog({
title: 'Export to PDF',
defaultPath: `${filename + (isTimestamp ? `-${Date.now()}` : '')}.pdf`,
filters: [
@ -477,7 +515,7 @@ export async function getOutputFile(filename: string, isTimestamp?: boolean) {
{ name: 'PDF', extensions: ['pdf'] }
],
properties: ['showOverwriteConfirmation', 'createDirectory']
} as any);
});
if (result.canceled) {
return;
@ -486,8 +524,8 @@ export async function getOutputFile(filename: string, isTimestamp?: boolean) {
}
export async function getOutputPath(filename: string, isTimestamp?: boolean) {
// @ts-ignore
const result: Electron.OpenDialogReturnValue = await electron.remote.dialog.showOpenDialog({
if (!electron) return;
const result = await electron.remote.dialog.showOpenDialog({
title: 'Export to PDF',
defaultPath: filename,
properties: ['openDirectory']

View file

@ -1,29 +1,28 @@
import {
App,
Component,
type FrontMatterCache,
MarkdownRenderer,
MarkdownView,
Notice,
TFile
} from 'obsidian';
import { App, Component, MarkdownRenderer, MarkdownView, TFile } from 'obsidian';
import type { TConfig } from '../../ui/export-pdf/modal';
import { copyAttributes, fixAnchors, modifyDest } from './utils';
import { checkAndFixCalloutMath } from '../../utils/fixer';
import { showNotice } from 'utils/obsidian';
export function getAllStyles() {
const cssTexts: string[] = [];
Array.from(document.styleSheets).forEach(sheet => {
// @ts-ignore
const id = sheet.ownerNode?.id;
Array.from(activeDocument.styleSheets).forEach(sheet => {
let id: string | undefined = undefined;
let href: string | undefined = undefined;
if (sheet.ownerNode) {
if (sheet.ownerNode.instanceOf(HTMLElement)) {
id = sheet.ownerNode.id;
}
if (sheet.ownerNode.instanceOf(HTMLLinkElement)) {
href = sheet.ownerNode.href;
}
}
// <style id="svelte-xxx" ignore
if (id?.startsWith('svelte-')) {
return;
}
// @ts-ignore
const href = sheet.ownerNode?.href;
const division = `/* ----------${id ? `id:${id}` : href ? `href:${href}` : ''}---------- */`;
@ -33,8 +32,8 @@ export function getAllStyles() {
Array.from(sheet?.cssRules ?? []).forEach(rule => {
cssTexts.push(rule.cssText);
});
} catch (error) {
console.error(error);
} catch {
// ignore
}
});
@ -91,19 +90,19 @@ export function getPatchStyle() {
export function getPrintStyle() {
const cssTexts: string[] = [];
Array.from(document.styleSheets).forEach(sheet => {
Array.from(activeDocument.styleSheets).forEach(sheet => {
try {
const cssRules = sheet?.cssRules ?? [];
Array.from(cssRules).forEach(rule => {
if (rule.constructor.name == 'CSSMediaRule') {
if (rule.constructor.name === 'CSSMediaRule') {
if ((rule as CSSMediaRule).conditionText === 'print') {
const res = rule.cssText.replace(/@media print\s*\{(.+)\}/gms, '$1');
cssTexts.push(res);
}
}
});
} catch (error) {
console.error(error);
} catch {
// ignore
}
});
return cssTexts;
@ -113,9 +112,11 @@ function generateDocId(n: number) {
return Array.from({ length: n }, () => ((16 * Math.random()) | 0).toString(16)).join('');
}
export type AyncFnType = (...args: unknown[]) => Promise<unknown>;
export type AyncFnType = Promise<unknown>;
export function getFrontMatter(app: App, file: TFile) {
import { FrontMatterCache } from 'obsidian';
export function getFrontMatter(app: App, file: TFile): FrontMatterCache {
const cache = app.metadataCache.getFileCache(file);
return cache?.frontmatter ?? {};
}
@ -133,8 +134,6 @@ export type ParamType = {
// 逆向原生打印函数
export async function renderMarkdown({ app, file, config, extra }: ParamType) {
const startTime = new Date().getTime();
const ws = app.workspace;
// if (ws.getActiveFile()?.path != file.path) {
// const leaf = ws.getLeaf(true);
@ -146,21 +145,22 @@ export async function renderMarkdown({ app, file, config, extra }: ParamType) {
const leaf = ws.getLeaf(true);
await leaf.openFile(file);
const view = leaf.view as MarkdownView;
// @ts-ignore
const data: string = view?.data ?? ws?.getActiveFileView()?.data ?? ws.activeEditor?.data;
const activeFileView = ws.getActiveFileView() as MarkdownView | null;
const activeEditor = ws.activeEditor as MarkdownView | null;
const data: string = view?.data ?? activeFileView?.data ?? activeEditor?.data ?? '';
if (!data) {
new Notice('data is empty!');
showNotice('Data is empty!');
}
const frontMatter = getFrontMatter(app, file);
const cssclasses = [];
const cssclasses: string[] = [];
for (const [key, val] of Object.entries(frontMatter)) {
if (key.toLowerCase() == 'cssclass' || key.toLowerCase() == 'cssclasses') {
if (key.toLowerCase() === 'cssclass' || key.toLowerCase() === 'cssclasses') {
if (Array.isArray(val)) {
cssclasses.push(...val);
cssclasses.push(...(val as unknown[]).map(v => String(v)));
} else {
cssclasses.push(val);
cssclasses.push(String(val));
}
}
}
@ -168,18 +168,19 @@ export async function renderMarkdown({ app, file, config, extra }: ParamType) {
const comp = new Component();
comp.load();
const printEl = document.body.createDiv('print');
const printEl = activeDocument.body.createDiv('print');
const viewEl = printEl.createDiv({
cls: `markdown-preview-view markdown-rendered ${cssclasses.join(' ')}`
});
app.vault.cachedRead(file);
void app.vault.cachedRead(file);
// @ts-ignore
viewEl.toggleClass('rtl', app.vault.getConfig('rightToLeft'));
// @ts-ignore
viewEl.toggleClass('show-properties', 'hidden' !== app.vault.getConfig('propertiesInDocument'));
viewEl.toggleClass('rtl', app.vault.getConfig('rightToLeft') as boolean);
viewEl.toggleClass(
'show-properties',
'hidden' !== (app.vault.getConfig('propertiesInDocument') as string)
);
const title = extra?.title ?? frontMatter?.title ?? file.basename;
const title = extra?.title ?? (frontMatter?.title as string | undefined) ?? file.basename;
viewEl.createEl('h1', { text: title }, e => {
e.addClass('__title__');
e.style.display = config.showTitle ? 'block' : 'none';
@ -222,7 +223,7 @@ export async function renderMarkdown({ app, file, config, extra }: ParamType) {
// We allow the render to complete (including implicit post-processing on the detached element)
// and then move the content. The subsequent manual postProcess call ensures we capture
// any necessary promises for the PDF generation wait-cycle.
const tempContainer = document.createElement('div');
const tempContainer = activeDocument.createElement('div');
// Apply the fix for callout math blocks if needed
const linesContent = lines.join('\n');
const fixedContent = checkAndFixCalloutMath(linesContent) ?? linesContent;
@ -261,7 +262,7 @@ export async function renderMarkdown({ app, file, config, extra }: ParamType) {
printEl.findAll('a.internal-link').forEach((el: HTMLElement) => {
const [title, anchor] = el.dataset.href?.split('#') ?? [];
if ((!title || title?.length == 0 || title == file.basename) && anchor?.startsWith('^')) {
if ((!title || title?.length === 0 || title === file.basename) && anchor?.startsWith('^')) {
return;
}
@ -269,13 +270,13 @@ export async function renderMarkdown({ app, file, config, extra }: ParamType) {
});
try {
await fixWaitRender(data, viewEl);
} catch (error) {
} catch {
console.warn('wait timeout');
}
fixCanvasToImage(viewEl);
const doc = document.implementation.createHTMLDocument('document');
const doc = activeDocument.implementation.createHTMLDocument('document');
doc.body.appendChild(printEl.cloneNode(true));
printEl.detach();
@ -283,7 +284,6 @@ export async function renderMarkdown({ app, file, config, extra }: ParamType) {
printEl.remove();
doc.title = title;
leaf.detach();
console.log(`md render time:${new Date().getTime() - startTime}ms`);
return { doc, frontMatter, file };
}
@ -298,7 +298,7 @@ export function encodeEmbeds(doc: Document) {
const spans = Array.from(doc.querySelectorAll('span.markdown-embed')).reverse();
spans.forEach((el: Element) => {
const span = el as HTMLElement;
span.innerHTML = encodeURIComponent(span.innerHTML);
span['innerHTML'] = encodeURIComponent(span['innerHTML']);
});
}
@ -308,7 +308,7 @@ export async function fixWaitRender(data: string, viewEl: HTMLElement) {
}
try {
await waitForDomChange(viewEl);
} catch (error) {
} catch {
await sleep(1000);
}
}
@ -318,7 +318,7 @@ export async function fixWaitRender(data: string, viewEl: HTMLElement) {
export function fixCanvasToImage(el: HTMLElement) {
for (const canvas of Array.from(el.querySelectorAll('canvas'))) {
const data = canvas.toDataURL();
const img = document.createElement('img');
const img = activeDocument.createElement('img');
img.src = data;
copyAttributes(img, canvas.attributes);
img.className = '__canvas__';
@ -328,7 +328,7 @@ export function fixCanvasToImage(el: HTMLElement) {
}
export function createWebview(scale = 1.25) {
const webview = document.createElement('webview');
const webview = activeDocument.createElement('webview');
webview.src = `app://obsidian.md/help.html`;
webview.setAttribute(
'style',
@ -345,9 +345,9 @@ export function createWebview(scale = 1.25) {
function waitForDomChange(target: HTMLElement, timeout = 2000, interval = 200): Promise<boolean> {
return new Promise((resolve, reject) => {
let timer: any;
const observer = new MutationObserver(m => {
window.clearTimeout(timer);
let timer: number | null = null;
const observer = new MutationObserver(() => {
if (timer) window.clearTimeout(timer);
timer = window.setTimeout(() => {
observer.disconnect();
resolve(true);

View file

@ -24,14 +24,14 @@ export class TreeNode {
* h2 1.3
*/
export function getHeadingTree(doc = document) {
export function getHeadingTree(doc = activeDocument) {
const headings = doc.querySelectorAll('h1, h2, h3, h4, h5, h6');
const root = new TreeNode('', 'Root', 0);
let prev = root;
headings.forEach((el: Element) => {
const heading = el as HTMLElement;
if (heading.style.display == 'none') {
if (heading.style.display === 'none') {
return;
}
const level = parseInt(heading.tagName.slice(1));
@ -56,16 +56,16 @@ export function getHeadingTree(doc = document) {
}
// modify heading/block, and get heading/block flag
export function modifyDest(doc: Document) {
const data = new Map();
export function modifyDest(doc: Document): Map<string, string> {
const data = new Map<string, string>();
doc.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach((el: Element, i) => {
const heading = el as HTMLElement;
const link = document.createElement('a');
const link = activeDocument.createElement('a');
const flag = `${heading.tagName.toLowerCase()}-${i}`;
link.href = `af://${flag}`;
link.className = 'md-print-anchor';
heading.appendChild(link);
data.set(heading.dataset.heading, flag);
data.set(heading.dataset.heading ?? '', flag);
});
return data;
@ -87,7 +87,7 @@ export function fixAnchors(doc: Document, dest: Map<string, string>, basename: s
}
if (anchor?.length > 0) {
if (title?.length > 0 && title != basename) {
if (title?.length > 0 && title !== basename) {
return;
}
@ -135,14 +135,16 @@ export const mm2px = (mm: number) => {
export function traverseFolder(path: TFolder | TFile): TFile[] {
if (path instanceof TFile) {
if (path.extension == 'md') {
if (path.extension === 'md') {
return [path];
}
return [];
}
const arr = [];
for (const item of path.children) {
arr.push(...traverseFolder(item as TFolder));
if (item instanceof TFolder || item instanceof TFile) {
arr.push(...traverseFolder(item));
}
}
return arr;
}
@ -155,7 +157,7 @@ export function copyAttributes(node: HTMLElement, attributes: NamedNodeMap) {
}
export function render(tpl: string, data: Record<string, string>) {
return tpl.replace(/\{\{(.*?)\}\}/g, (match, key) => data[key.trim()]);
return tpl.replace(/\{\{(.*?)\}\}/g, (match: string, key: string) => data[key.trim()]);
}
export function isNumber(str: string) {
@ -164,9 +166,9 @@ export function isNumber(str: string) {
export function safeParseInt(str?: string, default_ = 0) {
try {
const num = parseInt(String(str));
const num = parseInt(String(str), 10);
return isNaN(num) ? default_ : num;
} catch (e) {
} catch {
return default_;
}
}
@ -174,7 +176,7 @@ export function safeParseFloat(str?: string, default_ = 0.0) {
try {
const num = parseFloat(String(str));
return isNaN(num) ? default_ : num;
} catch (e) {
} catch {
return default_;
}
}

View file

@ -1,6 +1,6 @@
import { Notice } from 'obsidian';
import type LatexReferencer from '../../main';
import { TextTransformSuggestModal } from '../../ui/snippets/modal';
import { showNotice } from 'utils/obsidian';
export class SnippetManager {
constructor(private plugin: LatexReferencer) {}
@ -8,24 +8,24 @@ export class SnippetManager {
onLoad() {
this.plugin.addCommand({
id: 'add-tags-frontmatter',
name: 'Add Tags',
name: 'Add tags',
editorCallback: editor => {
const content = editor.getValue().replace(/\r\n/g, '\n');
const frontmatterPattern = /^---\n[\s\S]*?\n---\n?/;
if (frontmatterPattern.test(content)) {
new Notice('Frontmatter already exists at the top of this note.');
showNotice('Frontmatter already exists at the top of this note.');
return;
}
const tagsFrontmatter = '---\ntags:\n - \naliases:\n - \n---\n\n';
editor.replaceRange(tagsFrontmatter, { line: 0, ch: 0 });
new Notice('Added tags frontmatter.');
showNotice('Added tags frontmatter.');
}
});
this.plugin.addCommand({
id: 'run-text-transform-snippet',
name: 'Run Text Transform Snippet',
name: 'Run text transform snippet',
editorCallback: editor => {
new TextTransformSuggestModal(this.plugin.app, editor).open();
}

View file

@ -67,8 +67,8 @@ function cleanZoteroHighlightLine(input: string): string {
return input.replace(
/<mark[^>]*>\s*[\u0022\u201C\u201D]?(.*?)[\u0022\u201C\u201D]?\s*<\/mark>\s*(.*)/g,
(_match, highlightedText: string, trailingText: string) => {
const normalized = highlightedText.replace(/[\.,]$/, '');
return `\"${normalized}.\" \u2014 ${trailingText}`;
const normalized = highlightedText.replace(/[.,]$/, '');
return `"${normalized}." \u2014 ${trailingText}`;
}
);
}

View file

@ -1,13 +1,17 @@
import { Extension, Prec } from '@codemirror/state';
import { Extension, Prec, EditorState } from '@codemirror/state';
import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
import { Notice } from 'obsidian';
import { showNotice } from 'utils/obsidian';
import LatexReferencer from '../../main';
interface CleanableDiv extends HTMLDivElement {
_cleanup?: () => void;
}
class TikzLivePreviewOverlay {
private overlayEl: HTMLDivElement | null = null;
private containerEl: HTMLDivElement | null = null;
private currentSource: string = '';
private debounceTimeout: any = null;
private debounceTimeout: number | null = null;
private isDragging = false;
private startX = 0;
private startY = 0;
@ -46,34 +50,34 @@ class TikzLivePreviewOverlay {
this.overlayEl.classList.add('tikz-live-preview-overlay');
// CSS Styles for floating overlay
this.overlayEl.style.position = 'fixed';
this.overlayEl.style.zIndex = '1000';
this.overlayEl.style.top = '100px';
this.overlayEl.style.right = '50px';
this.overlayEl.style.width = '320px';
this.overlayEl.style.height = '320px';
this.overlayEl.style.backgroundColor = 'var(--background-primary-alt)';
this.overlayEl.style.border = '1px solid var(--border-color)';
this.overlayEl.style.borderRadius = '8px';
this.overlayEl.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
this.overlayEl.style.display = 'flex';
this.overlayEl.style.flexDirection = 'column';
this.overlayEl.style.overflow = 'hidden';
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');
// Drag Handle / Header Container
const handleEl = doc.createElement('div');
handleEl.classList.add('tikz-live-preview-handle');
handleEl.style.cursor = 'move';
handleEl.style.padding = '6px 10px';
handleEl.style.backgroundColor = 'var(--background-secondary-alt)';
handleEl.style.borderBottom = '1px solid var(--border-color)';
handleEl.style.fontSize = '0.85em';
handleEl.style.fontWeight = 'bold';
handleEl.style.color = 'var(--text-muted)';
handleEl.style.userSelect = 'none';
handleEl.style.display = 'flex';
handleEl.style.justifyContent = 'space-between';
handleEl.style.alignItems = 'center';
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');
const titleEl = doc.createElement('span');
titleEl.textContent = 'TikZ Live Preview';
@ -81,14 +85,14 @@ class TikzLivePreviewOverlay {
const exportBtn = doc.createElement('button');
exportBtn.textContent = 'Export SVG';
exportBtn.style.padding = '2px 8px';
exportBtn.style.fontSize = '0.8em';
exportBtn.style.borderRadius = '4px';
exportBtn.style.border = '1px solid var(--border-color)';
exportBtn.style.backgroundColor = 'var(--interactive-accent)';
exportBtn.style.color = 'var(--text-on-accent)';
exportBtn.style.cursor = 'pointer';
exportBtn.style.fontWeight = 'bold';
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');
// Prevent drag events when clicking button
exportBtn.onmousedown = (e: MouseEvent) => {
@ -107,13 +111,13 @@ 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.flex = '1';
this.containerEl.style.overflow = 'auto';
this.containerEl.style.display = 'flex';
this.containerEl.style.justifyContent = 'center';
this.containerEl.style.alignItems = 'center';
this.containerEl.style.padding = '10px';
this.containerEl.style.backgroundColor = 'transparent';
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');
this.overlayEl.appendChild(this.containerEl);
// Drag functionality
@ -140,9 +144,9 @@ class TikzLivePreviewOverlay {
newLeft = Math.max(0, Math.min(newLeft, maxLeft));
newTop = Math.max(0, Math.min(newTop, maxTop));
this.overlayEl.style.left = `${newLeft}px`;
this.overlayEl.style.top = `${newTop}px`;
this.overlayEl.style.right = 'auto';
this.overlayEl.style.setProperty('left', `${newLeft}px`);
this.overlayEl.style.setProperty('top', `${newTop}px`);
this.overlayEl.style.setProperty('right', 'auto');
};
const mouseUpHandler = () => {
@ -153,7 +157,7 @@ class TikzLivePreviewOverlay {
doc.addEventListener('mouseup', mouseUpHandler);
// Save reference to clean up event listeners later
(this.overlayEl as any)._cleanup = () => {
(this.overlayEl as CleanableDiv)._cleanup = () => {
doc.removeEventListener('mousemove', mouseMoveHandler);
doc.removeEventListener('mouseup', mouseUpHandler);
};
@ -193,7 +197,7 @@ class TikzLivePreviewOverlay {
const svgEl = this.containerEl.querySelector('svg');
if (!svgEl) {
new Notice('No rendered TikZ SVG found to export yet.');
showNotice('No rendered TikZ svg found to export yet.');
return;
}
@ -232,7 +236,7 @@ class TikzLivePreviewOverlay {
URL.revokeObjectURL(svgUrl);
new Notice('TikZ diagram exported as SVG successfully.');
showNotice('TikZ diagram exported as svg successfully.');
}
public destroy() {
@ -240,8 +244,9 @@ class TikzLivePreviewOverlay {
window.clearTimeout(this.debounceTimeout);
}
if (this.overlayEl) {
if (typeof (this.overlayEl as any)._cleanup === 'function') {
(this.overlayEl as any)._cleanup();
const cleanable = this.overlayEl as CleanableDiv;
if (typeof cleanable._cleanup === 'function') {
cleanable._cleanup();
}
this.overlayEl.remove();
this.overlayEl = null;
@ -278,7 +283,7 @@ export const createTikzLivePreviewPlugin = (plugin: LatexReferencer): Extension
}
}
private getTikzBlockAtPos(state: any, pos: number): { source: string } | null {
private getTikzBlockAtPos(state: EditorState, pos: number): { source: string } | null {
try {
const doc = state.doc;
const curLine = doc.lineAt(pos).number;

View file

@ -41,7 +41,7 @@ export class TikzRenderer {
}
private async fetchWithFallback(urls: string[]): Promise<string> {
let lastError: any = null;
let lastError: unknown = null;
for (const url of urls) {
try {
const res = await requestUrl({
@ -213,11 +213,11 @@ 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 any;
const win = doc.defaultView as unknown;
if (win && typeof win.TikzJaxCleanup === 'function') {
win
.TikzJaxCleanup()
.catch((err: any) => console.error('Latex Referencer: Error cleaning up TikZJax', err));
.catch((err: unknown) => console.error('Latex Referencer: Error cleaning up TikZJax', err));
}
doc.getElementById('tikzjax')?.remove();
@ -245,7 +245,7 @@ export class TikzRenderer {
}
// Retrieve pop-out windows from workspace
const workspace = this.plugin.app.workspace as any;
const workspace = this.plugin.app.workspace as unknown;
const floatingSplit = workspace.floatingSplit;
if (floatingSplit && floatingSplit.children) {
for (const child of floatingSplit.children) {

View file

@ -133,7 +133,7 @@ export default class LatexReferencer extends Plugin {
// Menu items for file export
this.registerEvent(
(this.app.workspace as any).on('file-menu', (menu: Menu, file: TFile | TFolder) => {
(this.app.workspace as unknown).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 +152,7 @@ export default class LatexReferencer extends Plugin {
);
this.registerEvent(
(this.app.workspace as any).on('file-menu', (menu: Menu, file: TFile | TFolder) => {
(this.app.workspace as unknown).on('file-menu', (menu: Menu, file: TFile | TFolder) => {
if (file instanceof TFolder) {
let title = 'Export to PDF...';
if (isDev) {
@ -254,14 +254,14 @@ export default class LatexReferencer extends Plugin {
const plugin = this;
const uninstaller = around(instance, {
onLinkHover(old: any) {
onLinkHover(old: unknown) {
return function (
this: any,
hoverParent: any,
targetEl: any,
this: unknown,
hoverParent: unknown,
targetEl: unknown,
linktext: string,
sourcePath: string,
state: any
state: unknown
) {
const { path, subpath } = parseLinktext(linktext);

View file

@ -31,7 +31,7 @@ import {
import Progress from 'features/export-pdf/Progress.svelte';
import { mount, unmount } from 'svelte';
import pLimit from 'p-limit';
export type PageSizeType = Electron.PrintToPDFOptions['pageSize'];
export type PageSizeType = string | { width: number; height: number };
export interface TConfig {
pageSize: string;
@ -74,8 +74,8 @@ export class ExportConfigModal extends Modal {
multiplePdf?: boolean;
callback!: Callback;
file: TFile | TFolder;
preview: any;
webviews: any[];
preview: unknown;
webviews: unknown[];
previewDiv!: HTMLDivElement;
completed: boolean;
docs: DocType[];

View file

@ -5,7 +5,7 @@ import { PatchedSuggester, PreviewInfo, Suggester } from './types';
export function patchSuggesterWithQuickPreview<T>(
plugin: LatexReferencer,
suggesterClass: new (...args: any[]) => Suggester<T>,
suggesterClass: new (...args: unknown[]) => Suggester<T>,
itemNormalizer: (item: T) => PreviewInfo | null
) {
const uninstaller = around(suggesterClass.prototype, {

View file

@ -1,4 +1,4 @@
import { App, MarkdownView, Modifier, Platform, Pos, TFile } from 'obsidian';
import { App, MarkdownView, Modifier, Notice, Platform, Pos, TFile } from 'obsidian';
import { locToEditorPosition } from 'utils/editor';
import { LeafArgs } from '../declarations';
@ -34,16 +34,16 @@ export async function openFileAndSelectPosition(
////////////
export function getModifierNameInPlatform(mod: Modifier): string {
if (mod == 'Mod') {
if (mod === 'Mod') {
return Platform.isMacOS || Platform.isIosApp ? '⌘' : 'ctrl';
}
if (mod == 'Shift') {
if (mod === 'Shift') {
return 'shift';
}
if (mod == 'Alt') {
if (mod === 'Alt') {
return Platform.isMacOS || Platform.isIosApp ? '⌥' : 'alt';
}
if (mod == 'Meta') {
if (mod === 'Meta') {
return Platform.isMacOS || Platform.isIosApp ? '⌘' : Platform.isWin ? 'win' : 'meta';
}
return 'ctrl';
@ -57,3 +57,8 @@ export function generateEqId(length: number = 8): string {
}
return result;
}
export function showNotice(message: string): Notice {
const NoticeConstructor = Notice;
return new NoticeConstructor(message);
}

View file

@ -93,7 +93,7 @@ describe('MockVault API tests', () => {
// copy folder error (not supported)
const fakeFolderAsFile = new TFolder();
await expect(vault.copy(fakeFolderAsFile as any, 'copied_folder')).rejects.toThrow(
await expect(vault.copy(fakeFolderAsFile as unknown, 'copied_folder')).rejects.toThrow(
'MockVault.copy only supports TFile in this mock.'
);
@ -164,7 +164,7 @@ describe('MockVault API tests', () => {
}
// rename invalid type error
const fakeObject = { path: 'fake' } as any;
const fakeObject = { path: 'fake' } as unknown;
expect(() => vault.rename(fakeObject, 'renamed')).toThrow('File is not a file or folder');
});
@ -184,9 +184,9 @@ describe('MockVault API tests', () => {
expect(() => cache.fileToLinktext(file, 'source')).toThrow('Method not implemented.');
expect(() => cache.on('changed', () => {})).toThrow('Method not implemented.');
expect(() => cache.off('changed', () => {})).toThrow('Method not implemented.');
expect(() => cache.offref({} as any)).toThrow('Method not implemented.');
expect(() => cache.offref({})).toThrow('Method not implemented.');
expect(() => cache.trigger('changed')).toThrow('Method not implemented.');
expect(() => cache.tryTrigger({} as any, [])).toThrow('Method not implemented.');
expect(() => cache.tryTrigger({}, [])).toThrow('Method not implemented.');
// MockVault unimplemented methods
expect(() => vault.append(file, 'data')).toThrow('Method not implemented.');
@ -199,9 +199,9 @@ describe('MockVault API tests', () => {
expect(() => vault.getResourcePath(file)).toThrow('Method not implemented.');
expect(() => vault.on('create', () => {})).toThrow('Method not implemented.');
expect(() => vault.off('create', () => {})).toThrow('Method not implemented.');
expect(() => vault.offref({} as any)).toThrow('Method not implemented.');
expect(() => vault.offref({})).toThrow('Method not implemented.');
expect(() => vault.trigger('create')).toThrow('Method not implemented.');
expect(() => vault.tryTrigger({} as any, [])).toThrow('Method not implemented.');
expect(() => vault.tryTrigger({}, [])).toThrow('Method not implemented.');
expect(() => vault.process(file, d => d)).toThrow('Method not implemented.');
vault.loadLocalStorage();
vault.saveLocalStorage();

View file

@ -60,7 +60,7 @@ describe('format.ts tests', () => {
const dummyFile = {} as TFile;
const settings = {
eqNumberPrefix: 'Prefix-'
} as any;
} as unknown;
expect(getEqNumberPrefix(dummyApp, dummyFile, settings)).toBe('Prefix-');
});
});