-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
008d159
commit 15b4428
Showing
7 changed files
with
257 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Button } from "@/components/tailwind/ui/button"; | ||
import { cn } from "@/lib/utils"; | ||
import { SigmaIcon } from "lucide-react"; | ||
import { useEditor } from "novel"; | ||
|
||
export const MathSelector = () => { | ||
const { editor } = useEditor(); | ||
|
||
if (!editor) return null; | ||
|
||
return ( | ||
<Button | ||
variant="ghost" | ||
size="sm" | ||
className="rounded-none w-12" | ||
onClick={(evt) => { | ||
if (editor.isActive("math")) { | ||
editor.chain().focus().unsetLatex().run(); | ||
} else { | ||
const { from, to } = editor.state.selection; | ||
const latex = editor.state.doc.textBetween(from, to); | ||
|
||
if (!latex) return; | ||
|
||
editor.chain().focus().setLatex({ latex }).run(); | ||
} | ||
}} | ||
> | ||
<SigmaIcon | ||
className={cn("size-4", { "text-blue-500": editor.isActive("math") })} | ||
strokeWidth={2.3} | ||
/> | ||
</Button> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import 'katex/dist/katex.min.css'; | ||
|
||
import { Node, mergeAttributes } from "@tiptap/core"; | ||
import { EditorState } from "@tiptap/pm/state"; | ||
import katex, { type KatexOptions } from "katex"; | ||
|
||
export interface MathematicsOptions { | ||
/** | ||
* By default LaTeX decorations can render when mathematical expressions are not inside a code block. | ||
* @param state - EditorState | ||
* @param pos - number | ||
* @returns boolean | ||
*/ | ||
shouldRender: (state: EditorState, pos: number) => boolean; | ||
|
||
/** | ||
* @see https://katex.org/docs/options.html | ||
*/ | ||
katexOptions?: KatexOptions; | ||
|
||
HTMLAttributes: Record<string, any>; | ||
} | ||
|
||
declare module "@tiptap/core" { | ||
interface Commands<ReturnType> { | ||
LatexCommand: { | ||
|
||
/** | ||
* Set selection to a LaTex symbol | ||
*/ | ||
setLatex: ({ latex }: { latex: string }) => ReturnType; | ||
|
||
/** | ||
* Unset a LaTex symbol | ||
*/ | ||
unsetLatex: () => ReturnType; | ||
|
||
}; | ||
} | ||
} | ||
|
||
/** | ||
* This extension adds support for mathematical symbols with LaTex expression. | ||
* | ||
* @see https://katex.org/ | ||
*/ | ||
export const Mathematics = Node.create<MathematicsOptions>({ | ||
name: "math", | ||
inline: true, | ||
group: "inline", | ||
atom: true, | ||
selectable: true, | ||
marks: "", | ||
|
||
addAttributes() { | ||
return { | ||
latex: "", | ||
}; | ||
}, | ||
|
||
addOptions() { | ||
return { | ||
shouldRender: (state, pos) => { | ||
const $pos = state.doc.resolve(pos); | ||
|
||
if (!$pos.parent.isTextblock) { | ||
return false; | ||
} | ||
|
||
return $pos.parent.type.name !== "codeBlock"; | ||
}, | ||
katexOptions: { | ||
throwOnError: false, | ||
}, | ||
HTMLAttributes: {}, | ||
}; | ||
}, | ||
|
||
addCommands() { | ||
return { | ||
setLatex: | ||
({ latex }) => | ||
({ chain, state }) => { | ||
if (!latex) { | ||
return false; | ||
} | ||
const { from, to, $anchor } = state.selection; | ||
|
||
if (!this.options.shouldRender(state, $anchor.pos)) { | ||
return false; | ||
} | ||
|
||
return chain() | ||
.insertContentAt( | ||
{ from: from, to: to }, | ||
{ | ||
type: "math", | ||
attrs: { | ||
latex: latex, | ||
}, | ||
} | ||
) | ||
.setTextSelection({ from: from, to: from + 1 }) | ||
.run(); | ||
}, | ||
unsetLatex: | ||
() => | ||
({ editor, state, chain }) => { | ||
const latex = editor.getAttributes(this.name).latex; | ||
if (typeof latex !== "string") { | ||
return false; | ||
} | ||
|
||
const { from, to } = state.selection; | ||
|
||
return chain() | ||
.command(({ tr }) => { | ||
tr.insertText(latex, from, to); | ||
return true; | ||
}) | ||
.setTextSelection({ | ||
from: from, | ||
to: from + latex.length, | ||
}) | ||
.run(); | ||
}, | ||
}; | ||
}, | ||
|
||
parseHTML() { | ||
return [{ tag: `span[data-type="${this.name}"]` }]; | ||
}, | ||
|
||
renderHTML({ node, HTMLAttributes }) { | ||
const latex = node.attrs["latex"] ?? ""; | ||
return [ | ||
"span", | ||
mergeAttributes(HTMLAttributes, { | ||
"data-type": this.name, | ||
}), | ||
latex, | ||
]; | ||
}, | ||
|
||
renderText({ node }) { | ||
return node.attrs["latex"] ?? ""; | ||
}, | ||
|
||
addNodeView() { | ||
return ({ node, HTMLAttributes, getPos, editor }) => { | ||
const dom = document.createElement("span"); | ||
const latex: string = node.attrs["latex"] ?? ""; | ||
|
||
Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => { | ||
dom.setAttribute(key, value); | ||
}); | ||
|
||
Object.entries(HTMLAttributes).forEach(([key, value]) => { | ||
dom.setAttribute(key, value); | ||
}); | ||
|
||
dom.addEventListener("click", (evt) => { | ||
if (editor.isEditable && typeof getPos === "function") { | ||
const pos = getPos(); | ||
const nodeSize = node.nodeSize; | ||
editor.commands.setTextSelection({ from: pos, to: pos + nodeSize }); | ||
} | ||
}); | ||
|
||
dom.contentEditable = "false"; | ||
|
||
dom.innerHTML = katex.renderToString(latex, this.options.katexOptions); | ||
|
||
return { | ||
dom: dom, | ||
}; | ||
}; | ||
}, | ||
}); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.