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: