Is this possible with ProseMirror?

Hi everyone,

I’m currently working on a project that requires a rich text editor with some unique functionality. I’m new to working with editors in general and have been trying to implement what I want in Slate js over the past few days and I’m not really getting anywhere. I thought I might take a look at ProseMirror (which I would be using via TipTap, I think, in my React project), but figured I should ask the community about this first before committing to diving into the documentation as this looks like a fairly complex framework.

In my editor I would like to give users the ability to add a translation for each word they type using a custom markup syntax like this: word(translation) or like this: [word]words with spaces[/word](translation).

However, where this gets complicated is that I want the markup itself to disappear when the user hits space after completing it, so e.g. “word(translation)” just becomes “word” maybe with a custom css class to indicate the word has a translation. The translation itself will need to be stored somehow, likely as a data attribute on the element. Then, whenever the user moves their caret back into the word I want the markup to reappear, e.g. “word” becomes “word(translation)”. It’s this last part I just can’t figure out in Slate js.

So my question is, does this sound like something that could be done in ProseMirror? Any examples out there of similar behavior I could look at? Any tips as to a general approach I might take to accomplish this?

It’s still very early days in the project so if I need to switch editor frameworks and do a little more learning that’s fine by me I’d just like to avoid swapping one for another with no reason.

Thank you!

This is definitely something you can do within ProseMirror, but it’d be complex as one would expect. Let me try to give some pointers.

In my editor I would like to give users the ability to add a translation for each word they type using a custom markup syntax like this: word(translation) or like this: [word]words with spaces/word.

The translation itself will need to be stored somehow, likely as a data attribute on the element.

The first question to think about is whether you would want to use a node or a mark. Both can support storing data within the attrs field.

However, where this gets complicated is that I want the markup itself to disappear when the user hits space after completing it, so e.g. “word(translation)” just becomes “word” maybe with a custom css class to indicate the word has a translation.

Markup to disappear (or to appear) when the user hits space can be accomplished with a mix of decorations and perhaps an inputrule to create the node or mark to store the translation attribute.

Then, whenever the user moves their caret back into the word I want the markup to reappear, e.g. “word” becomes “word(translation)”. It’s this last part I just can’t figure out in Slate js.

This would involve checking on each editor update whether the current selection intersects with a word node or mark. If it does, then you recompute the decorations (or the nodeview) to show (translation).

Overall, I would try grokking the source code of prosemirror-math because they do something similar with the cursor selection to switch between editing latex source and latex render. Best of luck!

Thank you for taking the time to provide those thoughts.

I’ll admit that reference manual looks daunting but it makes me feel better knowing that what I’m attempting is doable.

Hey jerobar, what you want to do is possible IMO. I’ll just share what I think you’ll need so you can start somewhere in the docs ( it’s hard, I know :slight_smile: ): I’d start with adding a new node to the schema (let’s call it translated), and I’d add an inputRule which matches [a-Z]*/([a-Z]*/) and converts it to a translated node and adds the translation as an attribute ( just like bhl wrote ).

The on-selected-show-translation could be done a lot of ways, the first thing that came to my mind is to add a small piece of code in dispatchTransaction which sends metadata to a plugin to add a Node decoration if the cursor is inside a translated node. Maybe there’s an easier way by adding a NodeView to the translated node and maybe there’s a hook that gets called whenever the cursor is inside the node ( I’m not sure about that, I’d sniff around ).

In general this is not a hard problem at all ( for ex. pagination is a hard/impossible problem ) IMO. Good luck!

Here’s my 2 cents: I’d avoid using inline nodes if possible since they require additional work for adjusting positions and handling merging and whatnot. Marks are pretty easy and two marks of the same type won’t merge if their attributes are not equal. You can also add excludes rule to disallow duplicate marks. Then you can create a plugin that has appendTransaction method apply method as you don’t have to change the doc with decorations which checks whether the selection wraps content (using nodesBetween) with translated mark, either decorating them or setting their visible attribute to true. Keeping UI data in the attribute might not make sense though so maybe a decoration is the better option.

Maybe that could work, good luck though.