Last active
October 30, 2022 04:58
-
-
Save BrianHung/b72126c98fa08cb1c09170b1394771a0 to your computer and use it in GitHub Desktop.
Math NodeView for TipTap
This file contains hidden or 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
.ProseMirror .Math { | |
display: contents; | |
} | |
.ProseMirror .Math .katex-editor { | |
display: inline; | |
} | |
.ProseMirror .Math .katex-render .katex { | |
font-size: 1em; | |
} | |
.ProseMirror .Math .katex-render .katex-error { | |
font-family: "Inter", sans-serif; | |
padding: 0; | |
} | |
.ProseMirror .Math .decoration-inline-math { | |
color: #8e9297; | |
} | |
.Math .hidden { | |
position: absolute; | |
width: 1px; | |
height: 1px; | |
margin: -1px; | |
border: 0; | |
padding: 0; | |
white-space: nowrap; | |
clip-path: inset(100%); | |
clip: rect(0 0 0 0); | |
overflow: hidden; | |
} | |
.Math .active { | |
position: static; | |
width: auto; | |
height: auto; | |
margin: 0; | |
clip: auto; | |
overflow: visible; | |
} |
This file contains hidden or 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
import {InputRule } from "prosemirror-inputrules" | |
import {Node } from "tiptap" | |
import katex from "katex" | |
import "katex/dist/katex.min.css" | |
import './Math.css' | |
import { deleteMath } from "./MathKeymaps.js" | |
/* | |
* Defines a ComponentView for Math. | |
*/ | |
export default class Math extends Node { | |
get name() { | |
return "math"; | |
} | |
get schema() { | |
return { | |
code: true, | |
content: "text*", | |
marks: "", | |
group: "inline", | |
inline: true, | |
defining: true, | |
isolating: true, | |
parseDOM: [{tag: "span.Math"}], | |
toDOM: node => ["span", {class: "Math"}, | |
["span", {class: "katex-render", contenteditable: "false"}], | |
["span", {contenteditable: "false"}, "$"], | |
["span", {class: "katex-editor"}, 0], | |
["span", {contenteditable: "false"}, "$"], | |
], | |
}; | |
} | |
get view() { | |
return { | |
name: "Math", | |
props: ["node", "view", "getPos"], | |
computed: { | |
active() { return this.parentHasSelection() ? "active" : "hidden" }, | |
hidden() { return this.parentHasSelection() ? "hidden" : "active" }, | |
}, | |
watch: { | |
"node.textContent": function(textContent) { this.render(textContent); }, | |
}, | |
mounted() { | |
if (this.node && this.node.textContent) this.render(this.node.textContent); | |
}, | |
methods: { | |
// Updates katex-render with node textContent. | |
render(textContent) { | |
katex.render(textContent, this.$refs.render, { | |
throwOnError: false, displayMode: false | |
}); | |
}, | |
// Shows katex-render and hides katex-editor when selection is on parent. | |
parentHasSelection() { | |
const {doc, selection: {from, to, anchor}} = this.view.state; | |
const rpos = doc.resolve(this.getPos()); | |
const parentNodeFrom = this.getPos() - rpos.parentOffset; | |
const parentNodeTo = parentNodeFrom + rpos.parent.nodeSize; | |
const hasAnchor = parentNodeFrom <= anchor && anchor < parentNodeTo; | |
const hasSelect = from < parentNodeTo && parentNodeFrom <= to; | |
return hasAnchor || hasSelect; | |
}, | |
}, | |
template: ` | |
<span class="Math"> | |
<span class="katex-render" v-bind:class="hidden" ref="render" v-show="!parentHasSelection()" contenteditable="false"></span><span :contenteditable="false" class="decoration-inline-math" v-bind:class="active">$</span><span class="katex-editor" v-bind:class="active" ref="content"></span><span :contenteditable="false" class="decoration-inline-math" v-bind:class="active">$</span> | |
</span> | |
` | |
} | |
} | |
inputRules({type, getAttrs}) { | |
return [ | |
new InputRule(/(?:\$)([^\$\s]+(?:\s+[^\$\s]+)*)(?:\$)$/, (state, match, start, end) => { | |
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs; | |
const [matchedText, content] = match; | |
const {tr, schema} = state; | |
if (matchedText) // Create the new Math node. | |
tr.replaceWith(start, end, type.create(attrs, schema.text(content))); | |
return tr | |
}) | |
]; | |
} | |
keys({ type }) { | |
return { | |
"Backspace": deleteMath, | |
} | |
} | |
} |
This file contains hidden or 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
import { Selection } from "prosemirror-state" | |
export const deleteMath = (state, dispatch, view) => { | |
const { tr, selection: {$from: from, $to: to, $cursor}} = state; | |
if (!from.sameParent(to)) | |
return false; | |
// Handle deletion of right $. | |
const rborder = from.parent.type !== state.schema.nodes.math | |
&& from.doc.resolve(from.pos - 1).parent.type === state.schema.nodes.math; | |
if (rborder) { | |
const mathNode = from.doc.resolve(from.pos - 1).parent; | |
const startPos = from.pos; | |
tr.replaceRangeWith(startPos - mathNode.nodeSize, startPos, | |
state.schema.text("$" + mathNode.textContent)); | |
const selection = Selection.near(tr.doc.resolve(startPos), -1); | |
tr.setSelection(selection).scrollIntoView() | |
dispatch(tr); | |
return true; | |
} | |
// Handle deletion of left $. | |
const lborder = from.parent.type === state.schema.nodes.math | |
&& from.doc.resolve(from.pos - 1).parent.type !== state.schema.nodes.math; | |
if (lborder) { | |
const mathNode = from.parent; | |
const startPos = from.pos - 1; | |
tr.replaceRangeWith(startPos, startPos + mathNode.nodeSize, | |
state.schema.text(mathNode.textContent + "$")); | |
const selection = Selection.near(tr.doc.resolve(startPos), 1); | |
tr.setSelection(selection).scrollIntoView() | |
dispatch(tr); | |
return true; | |
} | |
// Prevent default behavior of partial node-deletion of katex editor. | |
const textLength = $cursor ? $cursor.node().textContent.length : 0; | |
if (textLength == 1) | |
{ | |
tr.delete($cursor.pos - 1, $cursor.pos); | |
dispatch(tr); | |
return true; | |
} | |
// Allow default ProseMirror behavior of character- or node-deletion. | |
return false; | |
} | |
function isSelectionEntirelyInsideMath(state) { | |
return state.selection.$from.sameParent(state.selection.$to) && | |
state.selection.$from.parent.type === state.schema.nodes.math; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@huntz20 any luck implementing prosemirror-math into tiptap v2 or modifying this gist?