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 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 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 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; | |
} |
For posterity, it was mention here that it was an issue involving Nuxt: ueberdosis/tiptap#179 (comment). I'm not using Nuxt right now so I can't help with that :(
It's okay. Thanks for the reply.
Sorry for silly question how to use it on my code ??
i already add it into my extensions like this
this.editor = new Editor({
content: '<p>I’m running tiptap with Vue.js. 🎉</p>',
extensions: [
StarterKit,
Math
]
})
but it not working
@huntz20 This was written for tiptap v1. You would have to modify it to work with the api of tiptap v2, or use prosemirror-math.
@huntz20 any luck implementing prosemirror-math into tiptap v2 or modifying this gist?
@BrianHung please provide us modified gist for tiptap v2.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@BrianHung Thanks for the gist. But when I try to render by typing$a$ , when I type the last $, it is giving the error.
Cannot set property 'textContent' of undefined
This is what I've done.
image
I kept all the files in the same directory. And then, importing it. import Math from '@/utils/editor/Math.js' and then adding new Math() to the extensions array when initializing the editor.
I think that's enough.
Is there anything else that I've missed ?
Update:
After some debugging, I found the root cause, but I don't know the solution. katex was not able to render it properly.
this.$refs.render came undefined. Because this.$refs is giving empty object instead of reference to the span. Is there any additional step that I have to do ?
Is there any code sandbox where it is implemented, I can compare these two and that might be able to fix the error by myself