I have implemented a StateField that uses Decoration.replace() to replace parts of a markdown document with actual html elements using widgets. However, if the widget is clickable (e.g. <a> links) and the editor has focus, the element needs to be clicked twice: the first time the editor just loses focus and only the second time the element is actually clicked.
Am I doing something wrong? Ideally, I’d like to click only once while preserving the editor’s focus.
Here’s a simplified version of the code I’m running
class Link extends WidgetType {
constructor(
readonly node: SyntaxNode,
readonly state: EditorState,
) {
super();
}
toDOM() {
let link = document.createElement("a");
let marks = this.node.getChildren("LinkMark");
if (marks.length >= 2) {
link.innerText = this.state.sliceDoc(marks[0].to, marks[1].from);
}
let url = this.node.getChild("URL");
if (url) link.href = this.state.sliceDoc(url.from, url.to);
return link;
}
}
let decorationsField = StateField.define<DecorationSet>({
create() {
return Decoration.none;
},
update(_, tr) {
const builder = new RangeSetBuilder<Decoration>();
let cursor = tr.state.selection.main.head;
syntaxTree(tr.state).iterate({
enter: (node) => {
if ((cursor < node.from || cursor > node.to) && node.name == "Link") {
builder.add(
node.from,
node.to,
Decoration.replace({
widget: new Link(node.node, tr.state),
}),
);
return false;
}
return true;
},
});
return builder.finish();
},
provide: (f) => EditorView.decorations.from(f),
});
new EditorView({
doc: "[Test](https://example.com)",
extensions: [markdown(), decorationsField],
parent: document.body,
});
The issue is that your widget doesn’t define an eq method (and generally isn’t set up to be easily comparable), causing the editor to re-render it every time you recreate your decorations. This causes the link you’re clicking to disappear as the editor loses focus, breaking the browser’s native behavior.
I’m trying to adapt this to a slightly different use case.
1). In my test the hyperlink isn’t rendered until the editor first gains focus. How can I get the decorator to run immediately?
2). I want to adapt this using the tooltip to navigate (I have this working), so instead of rendering a hyperlink I want to just render the link text – but cause this to expand to the markdown when clicked. Currently the expansion happens when moving the cursor into the text but I don’t understand how that is triggered.
3). Later I might try to create a popup window to edit the link itself – is there a better starting point than the current search dialog demo?
I think I found some of the answers, but would appreciate any hints on #1 and #3.
Re #2. I realize that the widget’s toDom gets passed the view object, so I’ve setup an event handler directly there, then I pass in the anchor position from the statefield iterator so that when clicked, the widget’s handler can dispatch to the start of the range: