Tiptap: KaTeX Integration

Created on 27 Jan 2019  路  11Comments  路  Source: ueberdosis/tiptap

It would be great if there was an extension that uses KaTeX, sort of like Dropbox Paper but better.
There should be one for inline mode and another for display mode. When editing maths, especially for display mode, it would be great if it allows multiple lines.

This is my attempt in display math mode.

import { Node } from "tiptap"
import { toggleBlockType, setBlockType, textblockTypeInputRule } from "tiptap-commands"
import katex from "katex"

export default class DisplayMath extends Node {

  get name() {
    return "displaymath"
  }

  get schema() {
    return {
      attrs: {
        content: {
          default: ""
        }
      },
      content: "text*",
      marks: "",
      group: "block",
      defining: true,
      draggable: false,
      parseDOM: [
        {
          tag: "displaymath",
          getAttrs: dom => ({
            content: dom.getAttribute("content")
          })
        }
      ],
      toDOM: node => [
        "displaymath", { content: node.attrs.content }, ["div"], ["textarea", 0]
      ]
    }
  }

  get view() {
    return {
      props: ["node", "updateAttrs", "editable"],
      data: function () {
        return {
          edit: true
        }
      },
      watch: {
        content: function (newContent, oldContent) {
          katex.render(this.content, this.$refs.disp, {
            throwOnError: false
          })
        }
      },
      computed: {
        content: {
          get() {
            return this.node.attrs.content
          },
          set(content) {
            this.updateAttrs({
              content
            })
          }
        }
      },
      mounted() {
        katex.render(this.content, this.$refs.disp, {
          throwOnError: false
        })
      },
      methods: {
        change() {
          this.edit = true;
        },
        update() {
          this.edit = false;
        }
      },
      template: `
      <div>
        <div ref="disp" @click="change"></div>
        <textarea v-if="edit" class="katex-input" v-model="content" @focusout="update"></textarea>
      </div>
      `
    }
  }

  inputRules({ type }) {
    return [
      textblockTypeInputRule(/^\$\$\$$/, type)
    ]
  }

}

I modeled it after Iframe.js.

feature request

Most helpful comment

@mvind @ninest I updated the math and mathblock node gists. To use them, just import and use in your editor like any other nodes.

All 11 comments

Maybe you could see how it's implemented here. Since I don't want to implement it, I will close the ticket.

@wngu6 Were you able to figure out how to do this?

@wngu6 @themindstorm

I've implemented a working prototype for inline math. You may have to add additional css to get it working as depicted here.

https://gist.github.com/BrianHung/b72126c98fa08cb1c09170b1394771a0

Edit

I've also implemented a working prototype for mathblocks. You'd have to figure out how to get the arrow_handler to work properly but mostly seems functional.

https://gist.github.com/BrianHung/60efde8536f3fa76334f759c33a741e5

@BrianHung can you explain how to use the katex inline extension?
I've tried to use just like an regular extensions, but it does not render and I get the following error:
VM241 app.js:125340 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "selected"

@mvind @ninest I updated the math and mathblock node gists. To use them, just import and use in your editor like any other nodes.

@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.
image

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 fix the error.

I got the solution. We need to add this to the nuxt confit( if you are using nuxt) so that it renders at pre compilation stage. Same is requred for youtube embeds too.

  build: {
    extend(config) {
      config.resolve.alias.vue = 'vue/dist/vue.common'
    },
  },

One request
image

It won't render when the parent element is active. It should render immediately when the cursor leaves the math node. And get back to edit mode when the cursor moves between $ symbol.

I've tried but couldn't figure it out. All I know is to change the logic here to make it false when the cursor leaves the node.
image

Can someone help me with this logic. With this, I think inline math functionality would be usable.

Detecting whether a cursor leaves or enter an inline node is harder than detecting whether it leaves a block-level node like a paragraph. See https://discuss.prosemirror.net/t/prosemirror-math-at-desmos/707/8. I would recommend getting comfortable with ProseMirror's plugin system first: https://prosemirror.net/docs/ref/#state.Plugin_System.

pheww.... I figured it out. finally.

parentHasSelection() {
  .....
  // decides whether the anchor is between the Math node or not
  const hasAnchor = this.getPos() < anchor && anchor < this.getPos() + this.node.nodeSize
  ...
  ...
  // some other cases where it is necessary like when the editor is out of focus OR displaying in non-editable mode.
  // I removed the selection as it doesn't change anything when I paste the math. It is adding extra $ at both ends. Don't know 
  // why.
  return hasAnchor && this.view.focused && this.view.editable

@BrianHung if possible, could you please update the gist with this logic with some changes if required. And if you have time, please add comments so that we understand what is going on. It saves lot of time.

And, for mathblock.js too, we need to change few things Otherwise it won't work properly.

...
      computed: {
        visibleClass() {
          return this.hasProseMirrorSelection() ? "active" : "hidden"
        },
      },
...

And this

 hasProseMirrorSelection() {
          let anchor = this.view.state.selection.anchor
          const hasAnchor = this.getPos() <= anchor && anchor < this.node.nodeSize + this.getPos()
          return hasAnchor && this.view.focused && this.view.editable
  },

Hope these are updated in the gist so that others won't face this issue.

@ramsane @BrianHung hello. I'm no expert but I managed to use different extension except this one. Could you provide a sandbox or a working exemple ? I can't figure why it doesnt work for me. I'd love to be able to add maths to a project.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nekooee picture nekooee  路  3Comments

afwn90cj93201nixr2e1re picture afwn90cj93201nixr2e1re  路  3Comments

pk-pressf1 picture pk-pressf1  路  3Comments

klaasgeldof picture klaasgeldof  路  3Comments

git-mischa picture git-mischa  路  3Comments