Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions src/lib/edra/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the documented KaTeX error behavior with the actual default.

katexOptions enables throwOnError, while the inline comment and displayed configuration indicate invalid LaTeX will not throw.

  • src/lib/edra/extensions.ts#L28-L28: correct the comment, or change the option if non-throwing rendering is intended.
  • src/routes/docs/extensions/mathematics/+page.svelte#L47-L53: show the same behavior as the shared default.
📍 Affects 2 files
  • src/lib/edra/extensions.ts#L28-L28 (this comment)
  • src/routes/docs/extensions/mathematics/+page.svelte#L47-L53
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/edra/extensions.ts` at line 28, Align the KaTeX error behavior across
the shared katexOptions configuration and documentation: update
src/lib/edra/extensions.ts lines 28-28 so the throwOnError setting and inline
comment accurately describe the intended default, then update
src/routes/docs/extensions/mathematics/+page.svelte lines 47-53 to display that
same behavior. Ensure both sites consistently indicate whether invalid LaTeX
throws.

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({
Expand Down Expand Up @@ -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;
47 changes: 43 additions & 4 deletions src/lib/edra/headless/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -40,12 +50,37 @@ export interface EdraEditorProps {
onChunk: (chunk: string) => void,
onError: (error: Error) => void
) => Promise<void>;
/**
* 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the single-assignment let declarations.

The configured prefer-const rule reports both declarations as errors; capture the useEditor(...) result directly in a const and remove the later editorRef = editor assignment.

  • src/lib/edra/headless/editor.ts#L61-L61: replace the deferred editorRef assignment pattern.
  • src/lib/edra/shadcn/editor.ts#L64-L64: apply the same change.
🧰 Tools
🪛 ESLint

[error] 61-61: 'editorRef' is never reassigned. Use 'const' instead.

(prefer-const)

📍 Affects 2 files
  • src/lib/edra/headless/editor.ts#L61-L61 (this comment)
  • src/lib/edra/shadcn/editor.ts#L64-L64
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/edra/headless/editor.ts` at line 61, The deferred editorRef
declarations violate prefer-const because they are assigned only once. In
src/lib/edra/headless/editor.ts lines 61-61 and src/lib/edra/shadcn/editor.ts
lines 64-64, capture the useEditor(...) result directly in a const and remove
the later editorRef = editor assignment in each file.

Source: Linters/SAST tools

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({
Expand All @@ -68,3 +103,7 @@ export const createEditor = (props?: EdraEditorProps) =>
],
onUpdate: props?.onUpdate || (() => {})
});

editorRef = editor;
return editor;
};
2 changes: 1 addition & 1 deletion src/lib/edra/headless/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
47 changes: 43 additions & 4 deletions src/lib/edra/shadcn/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
/**
Expand All @@ -43,12 +53,37 @@ export interface EdraEditorProps {
onChunk: (chunk: string) => void,
onError: (error: Error) => void
) => Promise<void>;
/**
* 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({
Expand Down Expand Up @@ -77,3 +112,7 @@ export const createEditor = (props?: EdraEditorProps) =>
],
onUpdate: props?.onUpdate || (() => {})
});

editorRef = editor;
return editor;
};
2 changes: 1 addition & 1 deletion src/lib/edra/shadcn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
25 changes: 24 additions & 1 deletion src/routes/docs/extensions/mathematics/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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$$`;
</script>

Expand All @@ -35,13 +44,27 @@ Mathematics.configure({
The math extension uses <code>@tiptap/extension-mathematics</code> as a wrapper around the super
fast <code>katex</code> renderer. Edra pre-configures standard options and macros (such as
<code>\R</code>
and <code>\N</code>) globally in <code>extensions.ts</code>:
and <code>\N</code>) via shared <code>katexOptions</code> in <code>extensions.ts</code>, and
wires Mathematics inside <code>createEditor</code> so hosts can pass <code>onMathClick</code>:
</p>

<div class="my-4">
<Code code={configCode} language="typescript" />
</div>

<h2>Custom math click handler</h2>
<p>
Pass <code>onMathClick</code> to <code>createEditor</code> to handle clicks on math nodes
(for example, open a custom equation editor). The callback receives the ProseMirror
<code>node</code>, document <code>pos</code>, whether it is block math (<code>isBlock</code>),
and the <code>editor</code> instance. This forwards TipTap Mathematics'
<code>onClick</code> for both block and inline math.
</p>

<div class="my-4">
<Code code={onMathClickCode} language="typescript" />
</div>

<h2>How to use in Editor</h2>
<p>To write math equations, wrap your LaTeX string in dollar signs:</p>
<ul class="mt-4 list-disc space-y-2 pl-6">
Expand Down