TipTap/ProseMirror wrap selection with arbitrary text

315 views Asked by At

I am using TipTap to write markdown text (not html).

I'm doing this by not using any extensions like Bold and just doing them myself, and then using the getText() function.

All this works very well for my simple use case, even though I'm sure it's not ideal without using proper extensions etc.

(Note that I want all markdown to be visible, and not interpreted by the editor, which is why im doing it this way)

The problem is my wrapping/unwrapping code has some bugs, specifically related to going out of bounds.

The main time this occurs is if there is only a single word being highlighted, as well as multiple words but highlighting the last word

TypeError: Cannot read properties of undefined (reading 'nodeSize')

Is there a simpler way to do this, or what am I missing about my current implementation? I found it weird that the selections are not starting at 0 if i highlight just one word

    const wrapUnwrap = marker => {
      const { state, view } = toRaw(editor.value);
      const { from, to } = state.selection;
      const len = marker.length;

      // exit if nothing selected
      if (from === to) {
        return;
      }

      // there is a current bug here where there are "node size" errors if you
      // try to select a single word and make it bold... as well as the last word
      let textBefore, textAfter;
      try {
        textBefore = state.doc.textBetween(from - len, from);
        textAfter = state.doc.textBetween(to, to + len);
      } catch (e) {
        console.log(e);
        return;
      }
      let tr = state.tr;

      if (textBefore === marker && textAfter === marker) {
        tr.delete(to, to + len);
        tr.delete(from - len, from);
      } else {
        tr.insertText(marker, to, to);
        tr.insertText(marker, from, from);
      }
      view.dispatch(tr);
    };

    const makeBold = () => {
      wrapUnwrap("**");
    };

    const makeItalic = () => {
      wrapUnwrap("*");
    };
1

There are 1 answers

0
Tallboy On

I solved like this...

    const wrapUnwrap = marker => {
      const { state, view, from, to, tr } = buildTr();
      const len = marker.length;

      // exit if nothing selected
      if (from === to) {
        return;
      }

      let textBefore = "";
      let textAfter = "";

      if (from - len >= 1) {
        textBefore = state.doc.textBetween(from - len, from);
      }
      if (to + len <= state.doc.content.size) {
        textAfter = state.doc.textBetween(to, to + len);
      }

      if (textBefore === marker && textAfter === marker) {
        tr.delete(to, to + len);
        tr.delete(from - len, from);
      } else {
        tr.insertText(marker, to, to);
        tr.insertText(marker, from, from);
      }
      view.dispatch(tr);
    };

    const makeBold = () => {
      wrapUnwrap("**");
    };

    const makeItalic = () => {
      wrapUnwrap("*");
    };