mirror of
https://github.com/youfoundjk/TeXcore.git
synced 2026-07-22 07:33:31 +00:00
fix(callout): Implement custom rendering for equation links and numbers in dynamically rendered callouts.
This commit is contained in:
parent
94cd23a4c3
commit
a3d9e1e494
10 changed files with 210 additions and 47 deletions
|
|
@ -53,4 +53,5 @@ if (prod) {
|
|||
await context.watch();
|
||||
}
|
||||
|
||||
fs.copyFileSync("src/styles/main.css", "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/styles.css");
|
||||
fs.copyFileSync("src/styles/main.css", "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/styles.css");
|
||||
fs.copyFileSync("./manifest.json", "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/manifest.json");
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"id": "math-booster",
|
||||
"id": "latex-referencer",
|
||||
"name": "LaTeX Equation Referencer",
|
||||
"version": "2.2.4",
|
||||
"version": "2.2.5",
|
||||
"minAppVersion": "1.3.5",
|
||||
"description": "A powerful indexing & referencing system for theorems & equations in your vault. Bring LaTeX-like workflow into Obsidian with theorem environments, automatic equation numbering, and more.",
|
||||
"author": "Ryota Ushio",
|
||||
"authorUrl": "https://github.com/RyotaUshio",
|
||||
"fundingUrl": "https://www.buymeacoffee.com/ryotaushio",
|
||||
"author": "Jovi Koikkara",
|
||||
"authorUrl": "https://github.com/YouFoundJK",
|
||||
"fundingUrl": "https://github.com/YouFoundJK",
|
||||
"helpUrl": "https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
21
package-lock.json
generated
21
package-lock.json
generated
|
|
@ -1,15 +1,16 @@
|
|||
{
|
||||
"name": "obsidian-math-booster",
|
||||
"version": "2.2.4",
|
||||
"version": "2.2.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-math-booster",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.5.4",
|
||||
"@lezer/common": "^1.0.3",
|
||||
"@lucide/svelte": "^0.562.0",
|
||||
"esbuild": "^0.25.12",
|
||||
|
|
@ -59,9 +60,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.1.tgz",
|
||||
"integrity": "sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw=="
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz",
|
||||
"integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.18.1",
|
||||
|
|
@ -660,6 +665,12 @@
|
|||
"svelte": "^5"
|
||||
}
|
||||
},
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-math-booster",
|
||||
"version": "2.2.4",
|
||||
"version": "2.2.5",
|
||||
"description": "An Obsidian.md plugin that provides a powerful indexing & referencing system for theorems & equations in your vault. Bring LaTeX-like workflow into Obsidian with theorem environments, automatic equation numbering, and more.",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.5.4",
|
||||
"@lezer/common": "^1.0.3",
|
||||
"@lucide/svelte": "^0.562.0",
|
||||
"esbuild": "^0.25.12",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import LatexReferencer from 'main';
|
|||
import { processActiveNoteEquations } from './numbering';
|
||||
import { EquationBlock } from 'types';
|
||||
|
||||
|
||||
export const createEquationNumberProcessor = (plugin: LatexReferencer): MarkdownPostProcessor => {
|
||||
return (el, ctx) => {
|
||||
const file = plugin.app.vault.getAbstractFileByPath(ctx.sourcePath);
|
||||
|
|
|
|||
119
src/features/linker/dom-observer.ts
Normal file
119
src/features/linker/dom-observer.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/**
|
||||
* DOM Observer for Callout Equation Links
|
||||
*
|
||||
* When Obsidian renders a callout in Live Preview (cursor leaves the callout),
|
||||
* it replaces the CodeMirror content with a native HTML Block Widget. This
|
||||
* native rendering bypasses MarkdownPostProcessor hooks, so our equation link
|
||||
* rendering never fires.
|
||||
*
|
||||
* Additionally, Obsidian copies the 'math-link-processed' class from the
|
||||
* CodeMirror state into the callout HTML, which means we must strip it
|
||||
* before reprocessing.
|
||||
*
|
||||
* This observer watches for DOM mutations and manually processes equation
|
||||
* links inside dynamically rendered callout blocks. It also performs an
|
||||
* initial scan on setup to catch callouts rendered before the cache was ready.
|
||||
*/
|
||||
|
||||
import { MarkdownView } from "obsidian";
|
||||
import LatexReferencer from "main";
|
||||
import { processInternalLink } from "./reading-view-linker";
|
||||
|
||||
/**
|
||||
* Process all equation links within a given HTML element, stripping any
|
||||
* stale 'math-link-processed' class and reprocessing them.
|
||||
*/
|
||||
function processEquationLinksInElement(node: HTMLElement, plugin: LatexReferencer): void {
|
||||
let sourcePath: string | undefined = undefined;
|
||||
|
||||
const resolveSourcePath = () => {
|
||||
if (sourcePath) return;
|
||||
const leaves = plugin.app.workspace.getLeavesOfType("markdown");
|
||||
for (const leaf of leaves) {
|
||||
if (leaf.view.containerEl.contains(node)) {
|
||||
sourcePath = (leaf.view as MarkdownView).file?.path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!sourcePath) {
|
||||
const active = plugin.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
sourcePath = active?.file?.path;
|
||||
}
|
||||
};
|
||||
|
||||
const equationLinks = node.querySelectorAll?.('a.internal-link') as NodeListOf<HTMLAnchorElement>;
|
||||
if (equationLinks && equationLinks.length > 0) {
|
||||
for (let i = 0; i < equationLinks.length; i++) {
|
||||
const link = equationLinks[i];
|
||||
const dataHref = link.getAttribute('data-href');
|
||||
if (dataHref && dataHref.includes('#^eq-')) {
|
||||
link.classList.remove('math-link-processed');
|
||||
resolveSourcePath();
|
||||
if (sourcePath) {
|
||||
processInternalLink(link, plugin, sourcePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The node itself could be the link
|
||||
if (node.matches && node.matches('a.internal-link')) {
|
||||
const dataHref = (node as HTMLAnchorElement).getAttribute('data-href');
|
||||
if (dataHref && dataHref.includes('#^eq-')) {
|
||||
node.classList.remove('math-link-processed');
|
||||
resolveSourcePath();
|
||||
if (sourcePath) {
|
||||
processInternalLink(node as HTMLAnchorElement, plugin, sourcePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan all existing callout blocks in the DOM and process any equation links.
|
||||
* This handles callouts that were rendered before the equation cache was ready
|
||||
* (e.g., on initial page load).
|
||||
*/
|
||||
function scanExistingCallouts(plugin: LatexReferencer): void {
|
||||
const calloutBlocks = document.querySelectorAll('.cm-embed-block.cm-callout');
|
||||
for (const block of calloutBlocks) {
|
||||
if (block instanceof HTMLElement) {
|
||||
processEquationLinksInElement(block, plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a MutationObserver on document.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.
|
||||
*
|
||||
* @param plugin - The LatexReferencer plugin instance
|
||||
* @returns A cleanup function that disconnects the observer
|
||||
*/
|
||||
export function setupDOMObserver(plugin: LatexReferencer): () => void {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type === "childList") {
|
||||
for (const node of mutation.addedNodes) {
|
||||
if (node instanceof HTMLElement) {
|
||||
if (!node.querySelector && !node.matches) continue;
|
||||
processEquationLinksInElement(node, plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.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
|
||||
// to ensure the DOM is fully initialized, plus a small delay to ensure
|
||||
// the equation cache has been built.
|
||||
plugin.app.workspace.onLayoutReady(() => {
|
||||
setTimeout(() => scanExistingCallouts(plugin), 1000);
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { editorLivePreviewField, finishRenderMath, renderMath, editorInfoField } from "obsidian";
|
||||
import { EditorSelection, RangeSetBuilder, Extension } from "@codemirror/state";
|
||||
import { EditorSelection, RangeSetBuilder, Extension, Prec } from "@codemirror/state";
|
||||
import { Decoration, DecorationSet, ViewUpdate, EditorView, ViewPlugin, PluginValue, WidgetType } from "@codemirror/view";
|
||||
import { syntaxTree } from "@codemirror/language";
|
||||
import { getMathLink } from "./helper";
|
||||
|
|
@ -21,22 +21,22 @@ function selectionAndRangeOverlap(selection: EditorSelection, rangeFrom: number,
|
|||
* A helper function to render a string with inline math.
|
||||
*/
|
||||
function setMathLink(source: string, mathLinkEl: HTMLElement) {
|
||||
mathLinkEl.replaceChildren();
|
||||
const mathPattern = /\$(.*?[^\s])\$/g;
|
||||
let textFrom = 0, textTo = 0;
|
||||
let result;
|
||||
while ((result = mathPattern.exec(source)) !== null) {
|
||||
const mathString = result[1];
|
||||
textTo = result.index;
|
||||
if (textTo > textFrom) mathLinkEl.createSpan().replaceWith(source.slice(textFrom, textTo));
|
||||
mathLinkEl.replaceChildren();
|
||||
const mathPattern = /\$(.*?[^\s])\$/g;
|
||||
let textFrom = 0, textTo = 0;
|
||||
let result;
|
||||
while ((result = mathPattern.exec(source)) !== null) {
|
||||
const mathString = result[1];
|
||||
textTo = result.index;
|
||||
if (textTo > textFrom) mathLinkEl.createSpan().replaceWith(source.slice(textFrom, textTo));
|
||||
|
||||
const mathEl = renderMath(mathString, false);
|
||||
mathLinkEl.createSpan({ cls: ["math", "math-inline", "is-loaded"] }).replaceWith(mathEl);
|
||||
const mathEl = renderMath(mathString, false);
|
||||
mathLinkEl.createSpan({ cls: ["math", "math-inline", "is-loaded"] }).replaceWith(mathEl);
|
||||
|
||||
textFrom = mathPattern.lastIndex;
|
||||
}
|
||||
textFrom = mathPattern.lastIndex;
|
||||
}
|
||||
|
||||
if (textFrom < source.length) mathLinkEl.createSpan().replaceWith(source.slice(textFrom));
|
||||
if (textFrom < source.length) mathLinkEl.createSpan().replaceWith(source.slice(textFrom));
|
||||
}
|
||||
|
||||
/** Given a LatexReferencer plugin instance, create a CodeMirror6 view plugin that renders equation links. */
|
||||
|
|
@ -44,12 +44,12 @@ export const createLivePreviewLinkRendererPlugin = (plugin: LatexReferencer): Ex
|
|||
const { app } = plugin;
|
||||
|
||||
class MathWidget extends WidgetType {
|
||||
constructor(public outLinkText: string, public outLinkMathLink: string, public sourcePath: string) {
|
||||
constructor(public outLinkText: string, public outLinkMathLink: string, public sourcePath: string, public from: number, public to: number) {
|
||||
super();
|
||||
}
|
||||
|
||||
eq(other: MathWidget) {
|
||||
return this.outLinkText === other.outLinkText && this.outLinkMathLink === other.outLinkMathLink && this.sourcePath === other.sourcePath;
|
||||
return this.outLinkText === other.outLinkText && this.outLinkMathLink === other.outLinkMathLink && this.sourcePath === other.sourcePath && this.from === other.from && this.to === other.to;
|
||||
}
|
||||
|
||||
toDOM() {
|
||||
|
|
@ -127,18 +127,16 @@ export const createLivePreviewLinkRendererPlugin = (plugin: LatexReferencer): Ex
|
|||
endNode?.name.includes("formatting-link-end")
|
||||
) {
|
||||
const linkText = state.sliceDoc(linkNode.from, linkNode.to);
|
||||
|
||||
|
||||
if (linkText.startsWith("#^eq-")) {
|
||||
const start = startNode.from;
|
||||
const end = endNode.to;
|
||||
const outLinkMathLink = getMathLink(plugin, linkText, sourcePath);
|
||||
|
||||
if (outLinkMathLink && !selectionAndRangeOverlap(state.selection, start, end)) {
|
||||
if (outLinkMathLink && !selectionAndRangeOverlap(state.selection, linkNode.from, linkNode.to)) {
|
||||
builder.add(
|
||||
start,
|
||||
end,
|
||||
linkNode.from,
|
||||
linkNode.to,
|
||||
Decoration.replace({
|
||||
widget: new MathWidget(linkText, outLinkMathLink, sourcePath),
|
||||
widget: new MathWidget(linkText, outLinkMathLink, sourcePath, linkNode.from, linkNode.to),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
@ -156,6 +154,5 @@ export const createLivePreviewLinkRendererPlugin = (plugin: LatexReferencer): Ex
|
|||
}
|
||||
}, { decorations: v => v.decorations }
|
||||
);
|
||||
|
||||
return viewPlugin;
|
||||
return Prec.highest(viewPlugin);
|
||||
}
|
||||
|
|
@ -48,25 +48,38 @@ export class LatexRenderChild extends MarkdownRenderChild {
|
|||
if (mathLink) {
|
||||
// The containerEl is now the link element itself
|
||||
const linkEl = this.containerEl as HTMLElement;
|
||||
// Verify it's still in the DOM (though MarkdownRenderChild handles unloading)
|
||||
if (linkEl.isConnected) {
|
||||
setMathLink(mathLink, linkEl);
|
||||
}
|
||||
setMathLink(mathLink, linkEl);
|
||||
}
|
||||
finishRenderMath();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually process a single internal link element, creating a LatexRenderChild
|
||||
* and calling onload() to render the equation number. Used by the DOM observer
|
||||
* to handle links inside dynamically rendered callouts.
|
||||
*/
|
||||
export const processInternalLink = (link: HTMLAnchorElement, plugin: LatexReferencer, sourcePath: string) => {
|
||||
if (link.classList.contains("math-link-processed")) return;
|
||||
const dataHref = link.getAttribute('data-href');
|
||||
if (dataHref && dataHref.includes('#^eq-')) {
|
||||
link.classList.add("math-link-processed");
|
||||
const child = new LatexRenderChild(link, plugin, sourcePath, dataHref);
|
||||
child.onload();
|
||||
}
|
||||
};
|
||||
|
||||
export const CustomMathLinksProcessor = (plugin: LatexReferencer): MarkdownPostProcessor => {
|
||||
return (element: HTMLElement, context: MarkdownPostProcessorContext) => {
|
||||
const links = element.querySelectorAll<HTMLAnchorElement>('a.internal-link');
|
||||
for (const link of links) {
|
||||
const href = link.getAttribute('data-href');
|
||||
if (href && href.contains('#^eq-')) {
|
||||
// This is one of our equation links.
|
||||
// Pass the specific link element as the container to scope the child correctly.
|
||||
if (link.classList.contains("math-link-processed")) continue;
|
||||
const dataHref = link.getAttribute('data-href');
|
||||
|
||||
if (dataHref && dataHref.includes('#^eq-')) {
|
||||
link.classList.add("math-link-processed");
|
||||
context.addChild(
|
||||
new LatexRenderChild(link, plugin, context.sourcePath, href)
|
||||
new LatexRenderChild(link, plugin, context.sourcePath, dataHref)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { Provider } from './features/linker/provider-link-render';
|
|||
import { LatexLinkProvider } from 'latex-provider';
|
||||
import { createEquationNumberProcessor } from 'features/equations/reading-view-equations';
|
||||
import { CustomMathLinksProcessor } from './features/linker/reading-view-linker';
|
||||
import { setupDOMObserver } from './features/linker/dom-observer';
|
||||
import { createEquationNumberPlugin } from 'features/equations/live-preview-equations';
|
||||
import { createLivePreviewLinkRendererPlugin } from './features/linker/live-preview-link-renderer';
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ import { checkAndFixCalloutMath } from 'utils/fixer';
|
|||
import { traverseFolder } from "./features/export-pdf/utils";
|
||||
import { SnippetManager } from 'features/snippets/manager';
|
||||
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
|
||||
export default class LatexReferencer extends Plugin {
|
||||
|
|
@ -190,6 +192,7 @@ export default class LatexReferencer extends Plugin {
|
|||
this.app.workspace.onLayoutReady(() => this.forceRerender());
|
||||
|
||||
this.patchPagePreview();
|
||||
this.register(setupDOMObserver(this));
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
|
|
@ -260,6 +263,9 @@ export default class LatexReferencer extends Plugin {
|
|||
this.register(uninstaller);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
async generateToc(root: TFolder | TFile) {
|
||||
// @ts-ignore
|
||||
const basePath = this.app.vault.adapter.basePath;
|
||||
|
|
|
|||
16
src/utils/debug.ts
Normal file
16
src/utils/debug.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* A simple synchronous file logger used for intense UI diagnostic tracing.
|
||||
*
|
||||
* Usage:
|
||||
* Inject `logDebug("My message")` aggressively into CodeMirror rendering loops
|
||||
* or DOM observers to output an absolute timeline of execution to `debug.log`.
|
||||
* This is especially useful for capturing logs in contexts where the Obsidian
|
||||
* Developer Console might be silenced, asynchronous, or overwhelmed.
|
||||
*/
|
||||
import * as fs from "fs";
|
||||
|
||||
export function logDebug(msg: string) {
|
||||
try {
|
||||
fs.appendFileSync("d:\\Codes\\plugin-latex-referencer\\debug.log", msg + "\n", { encoding: "utf-8" });
|
||||
} catch (e) { }
|
||||
}
|
||||
Loading…
Reference in a new issue