Replace specific text with a visual element

So, I’ve started using Codemirror and I like it very much so far. However since I’m new I’m not sure on how I can do this particular required feature (or if it’s possible).

Suppose I have a text like:
<custom id="1">Something</custom> This is another text. Hello World <custom id="2">Else</custom>

How could I be able to let the editor know that the ‘custom’ tag is not text to be shown. Instead replace it for something that can be visually styled, so that the user knows (and can see) that is there, but not edit.

Imagine something like: [Something] This is another text. Hello World [Else]
where the bracketed items in bold behave like a single character or element

And also not loose the content, meaning the user will be able to treat said tags as a ‘token’ of sorts he can move around (or possibly delete), however if it’s left in the content I want to be able to retrieve the original value (Something for example). Hope I am explaining myself

See the markText method’s replacedWith option.

Thanks for your super quick reply. That was actually my first clue, however I’m not entirely sure on how to find the elements I speak of. According to the docs
doc.markText(from: {line, ch}, to: {line, ch}, ?options: object)

I’d need to know where the custom tag begins and ends. I have a regexp to find them however I’m not sure on how to tell the editor to find me all the matches, so I can then somehow get the from and to objects to feed to the markText method

Scanning the text is something you’ll have to build yourself (though the search cursor addon might help). And then you must also register a "changes" handler that re-scans changed content, so this will require some coding.

I’ve managed to make some progress. I’m bound to the editor change event. where I:

1- get the document content;
2- do a matching search for the cases I need
3- for every case
-Get the start and end pos in the string
-Create a TextMarker using said positions (with atomic option set to true), and some styles

That creates the markers on the matched elements. However I’m not sure on how to make the marker behave like a draggable element. I can drag it, but i have to “select it”, which for my use case is a bit unintuitive.
However I’m not sure if what i’m saying can be done since I understand the purpose of markText is to, well, mark text.

Do you think this approach is viable?

@marijn thanks for your previous help and so sorry to bother you again.

I was able to successfully replace the items i want with a custom element of my choosing.
I’m having another issue now however. When trying to drag that element I must add a drag event listener to set the dataTransfer event’s data. Which works, but the editor always interprets it as a “paste” rather than a move which is what I need.

const sp = document.createElement('span');
  sp.classList.add('tag');
  sp.setAttribute('draggable', 'true');
  sp.textContent = myContent;
  sp.addEventListener('dragstart', (event) => {
    const { target } = event;
    event.dataTransfer.effectAllowed = 'move';   // this also doen't work
    event.dataTransfer.setData('text/plain', (target as HTMLSpanElement).textContent);
  });

this.editor.doc.markText(startPos, endPos, { 
  replacedWith: sp,
  inclusiveLeft: false,
  inclusiveRight: false,
  attributes: {
    draggable: true,
  },
})

When I drag the element, the content is copied to the dropped location. I understand that I’d need to recreate the marker since the content is pasted as text. However what i’m unclear on is how to make it behave as a “move”. Even if i remove the marker via its clear() method the content is left in the original place.

Hey @hriverahdez

Do you mind sharing your code for this? I am trying to do the same (i.e. replace some specific text with a visual element).

2 Likes