From 94cbb95ba4985400784c3cbd9887de534e7c9b11 Mon Sep 17 00:00:00 2001
From: FrankLiu007
Date: Wed, 22 Jul 2026 14:23:18 +0800
Subject: [PATCH] expose onMathClick for math node
---
src/lib/edra/extensions.ts | 24 +++++-----
src/lib/edra/headless/editor.ts | 47 +++++++++++++++++--
src/lib/edra/headless/index.ts | 2 +-
src/lib/edra/shadcn/editor.ts | 47 +++++++++++++++++--
src/lib/edra/shadcn/index.ts | 2 +-
.../docs/extensions/mathematics/+page.svelte | 25 +++++++++-
6 files changed, 125 insertions(+), 22 deletions(-)
diff --git a/src/lib/edra/extensions.ts b/src/lib/edra/extensions.ts
index 23b58af1..de478c7d 100644
--- a/src/lib/edra/extensions.ts
+++ b/src/lib/edra/extensions.ts
@@ -19,10 +19,22 @@ import {
} from './tiptap/index.ts';
import { TaskItem, TaskList } from '@tiptap/extension-list';
import { Markdown } from '@tiptap/markdown';
-import Mathematics from '@tiptap/extension-mathematics';
+import type { KatexOptions } from 'katex';
+
+/**
+ * Shared KaTeX options for Mathematics (wired in createEditor so onMathClick can be injected).
+ */
+export const katexOptions: KatexOptions = {
+ throwOnError: true, // don't throw an error if the LaTeX code is invalid
+ macros: {
+ '\\R': '\\mathbb{R}', // add a macro for the real numbers
+ '\\N': '\\mathbb{N}' // add a macro for the natural numbers
+ }
+};
/**
* Contains all the default extensions the editor uses.
+ * Mathematics is configured in createEditor so hosts can pass onMathClick.
*/
export default [
StarterKit.configure({
@@ -89,16 +101,6 @@ export default [
TableRow,
TableCell,
Markdown,
- Mathematics.configure({
- // Options for the KaTeX renderer. See here: https://katex.org/docs/options.html
- katexOptions: {
- throwOnError: true, // don't throw an error if the LaTeX code is invalid
- macros: {
- '\\R': '\\mathbb{R}', // add a macro for the real numbers
- '\\N': '\\mathbb{N}' // add a macro for the natural numbers
- }
- }
- }),
// Drag-select across images/math/media; decorate selected atoms; keep selection on right-click
SelectAcrossAtoms
] as Extensions;
diff --git a/src/lib/edra/headless/editor.ts b/src/lib/edra/headless/editor.ts
index 7acf042a..445a97a3 100644
--- a/src/lib/edra/headless/editor.ts
+++ b/src/lib/edra/headless/editor.ts
@@ -7,10 +7,11 @@ import {
SlashCommand,
SvelteNodeViewRenderer,
useEditor,
- VideoExtended
+ VideoExtended,
+ type Editor
} from '../tiptap/index.ts';
import { all, createLowlight } from 'lowlight';
-import extensions from '../extensions.ts';
+import extensions, { katexOptions } from '../extensions.ts';
const lowlight = createLowlight(all);
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
import CodeBlock from './components/CodeBlock.svelte';
@@ -22,6 +23,15 @@ import IFrameComp from './components/IFrame.svelte';
import MermaidComp from './components/Mermaid.svelte';
import SlashCommandComp from './components/SlashCommand.svelte';
import CalloutComp from './components/Callout.svelte';
+import Mathematics from '@tiptap/extension-mathematics';
+import type { Node } from '@tiptap/pm/model';
+
+export type MathClickHandler = (
+ node: Node,
+ pos: number,
+ isBlock: boolean,
+ editor: Editor
+) => void;
export interface EdraEditorProps {
onUpdate?: () => void;
@@ -40,12 +50,37 @@ export interface EdraEditorProps {
onChunk: (chunk: string) => void,
onError: (error: Error) => void
) => Promise;
+ /**
+ * Called when the user clicks a math node. Forwards TipTap Mathematics `onClick`
+ * for both block and inline math, and includes `isBlock` plus the editor instance.
+ */
+ onMathClick?: MathClickHandler;
}
-export const createEditor = (props?: EdraEditorProps) =>
- useEditor({
+export const createEditor = (props?: EdraEditorProps) => {
+ let editorRef: Editor | undefined;
+ const onMathClick = props?.onMathClick;
+
+ const editor = useEditor({
extensions: [
...extensions,
+ Mathematics.configure({
+ katexOptions,
+ blockOptions: onMathClick
+ ? {
+ onClick: (node, pos) => {
+ if (editorRef) onMathClick(node, pos, true, editorRef);
+ }
+ }
+ : undefined,
+ inlineOptions: onMathClick
+ ? {
+ onClick: (node, pos) => {
+ if (editorRef) onMathClick(node, pos, false, editorRef);
+ }
+ }
+ : undefined
+ }),
CodeBlockLowlight.configure({
lowlight
}).extend({
@@ -68,3 +103,7 @@ export const createEditor = (props?: EdraEditorProps) =>
],
onUpdate: props?.onUpdate || (() => {})
});
+
+ editorRef = editor;
+ return editor;
+};
diff --git a/src/lib/edra/headless/index.ts b/src/lib/edra/headless/index.ts
index e825a933..01a9a144 100644
--- a/src/lib/edra/headless/index.ts
+++ b/src/lib/edra/headless/index.ts
@@ -2,7 +2,7 @@ import { Tiptap } from '../tiptap/index.ts';
import Toolbar from './components/Toolbar.svelte';
import DragHandle from './drag-handle.svelte';
import Editor from './editor.svelte';
-export { createEditor } from './editor.ts';
+export { createEditor, type EdraEditorProps, type MathClickHandler } from './editor.ts';
export * from '@tiptap/core';
import UseAI from './components/menu/AI.svelte';
import BubbleMenu from './components/menu/BubbleMenu.svelte';
diff --git a/src/lib/edra/shadcn/editor.ts b/src/lib/edra/shadcn/editor.ts
index 882859a9..5a3f093f 100644
--- a/src/lib/edra/shadcn/editor.ts
+++ b/src/lib/edra/shadcn/editor.ts
@@ -7,10 +7,11 @@ import {
SlashCommand,
SvelteNodeViewRenderer,
useEditor,
- VideoExtended
+ VideoExtended,
+ type Editor
} from '../tiptap/index.ts';
import { all, createLowlight } from 'lowlight';
-import extensions from '../extensions.ts';
+import extensions, { katexOptions } from '../extensions.ts';
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
import CodeBlock from './components/CodeBlock.svelte';
import { MediaPlaceholder } from '../tiptap/extensions/MediaPlaceHolder.ts';
@@ -23,9 +24,18 @@ import SlashCommandComp from './components/SlashCommand.svelte';
import CalloutComp from './components/Callout.svelte';
import TableOfContents, { getHierarchicalIndexes } from '@tiptap/extension-table-of-contents';
import { setTocItems } from './toc.svelte';
+import Mathematics from '@tiptap/extension-mathematics';
+import type { Node } from '@tiptap/pm/model';
const lowlight = createLowlight(all);
+export type MathClickHandler = (
+ node: Node,
+ pos: number,
+ isBlock: boolean,
+ editor: Editor
+) => void;
+
export interface EdraEditorProps {
onUpdate?: () => void;
/**
@@ -43,12 +53,37 @@ export interface EdraEditorProps {
onChunk: (chunk: string) => void,
onError: (error: Error) => void
) => Promise;
+ /**
+ * Called when the user clicks a math node. Forwards TipTap Mathematics `onClick`
+ * for both block and inline math, and includes `isBlock` plus the editor instance.
+ */
+ onMathClick?: MathClickHandler;
}
-export const createEditor = (props?: EdraEditorProps) =>
- useEditor({
+export const createEditor = (props?: EdraEditorProps) => {
+ let editorRef: Editor | undefined;
+ const onMathClick = props?.onMathClick;
+
+ const editor = useEditor({
extensions: [
...extensions,
+ Mathematics.configure({
+ katexOptions,
+ blockOptions: onMathClick
+ ? {
+ onClick: (node, pos) => {
+ if (editorRef) onMathClick(node, pos, true, editorRef);
+ }
+ }
+ : undefined,
+ inlineOptions: onMathClick
+ ? {
+ onClick: (node, pos) => {
+ if (editorRef) onMathClick(node, pos, false, editorRef);
+ }
+ }
+ : undefined
+ }),
CodeBlockLowlight.configure({
lowlight
}).extend({
@@ -77,3 +112,7 @@ export const createEditor = (props?: EdraEditorProps) =>
],
onUpdate: props?.onUpdate || (() => {})
});
+
+ editorRef = editor;
+ return editor;
+};
diff --git a/src/lib/edra/shadcn/index.ts b/src/lib/edra/shadcn/index.ts
index 30dfefbd..0620a397 100644
--- a/src/lib/edra/shadcn/index.ts
+++ b/src/lib/edra/shadcn/index.ts
@@ -2,7 +2,7 @@ import { Tiptap } from '../tiptap/index.ts';
import Toolbar from './components/Toolbar.svelte';
import DragHandle from './drag-handle.svelte';
import Editor from './editor.svelte';
-export { createEditor } from './editor.ts';
+export { createEditor, type EdraEditorProps, type MathClickHandler } from './editor.ts';
export * from '@tiptap/core';
import UseAI from './components/menu/AI.svelte';
import BubbleMenu from './components/menu/BubbleMenu.svelte';
diff --git a/src/routes/docs/extensions/mathematics/+page.svelte b/src/routes/docs/extensions/mathematics/+page.svelte
index e487478d..4c2f592d 100644
--- a/src/routes/docs/extensions/mathematics/+page.svelte
+++ b/src/routes/docs/extensions/mathematics/+page.svelte
@@ -15,6 +15,15 @@ Mathematics.configure({
}
})`;
+ const onMathClickCode = `import { createEditor } from '$lib/edra/headless';
+
+const editor = createEditor({
+ onMathClick: (node, pos, isBlock, editor) => {
+ // Open your own math UI (e.g. MathLive) instead of the default prompt
+ console.log(node.attrs.latex, { pos, isBlock });
+ }
+});`;
+
const exampleEquation = `$$f(x) = \\int_{-\\infty}^{\\infty} \\hat{f}(\\xi) e^{2 \\pi i x \\xi} d\\xi$$`;
@@ -35,13 +44,27 @@ Mathematics.configure({
The math extension uses @tiptap/extension-mathematics as a wrapper around the super
fast katex renderer. Edra pre-configures standard options and macros (such as
\R
- and \N) globally in extensions.ts:
+ and \N) via shared katexOptions in extensions.ts, and
+ wires Mathematics inside createEditor so hosts can pass onMathClick:
+ Custom math click handler
+
+ Pass onMathClick to createEditor to handle clicks on math nodes
+ (for example, open a custom equation editor). The callback receives the ProseMirror
+ node, document pos, whether it is block math (isBlock),
+ and the editor instance. This forwards TipTap Mathematics'
+ onClick for both block and inline math.
+
+
+
+
+
+
How to use in Editor
To write math equations, wrap your LaTeX string in dollar signs: