Multiline EditorView placeholder with Codemirror.next

Hi there!

I am trying to create a multiline placeholder to show a suggestion when the editor is empty. For this purpose, I created a custom plugin and registered it as an extension when an EditorState is created:

const placeholderPlugin = ViewPlugin.fromClass(class implements PluginValue {
    constructor(readonly view: EditorView) {
    }

    update(update: ViewUpdate) {
        const doc = update.state.doc;

        if (doc.length > 0) {
            return;
        }

        const placeHolder = document.createElement('div');
        placeHolder.classList.add('placeholder');
        placeHolder.textContent = '<script>\n    function abc() { }\n</script>';

        // How to append placeHolder to the EditorView?
    }
});

The plugin is called properly but I cannot figure out how to append my placeHolder element to the view. Any example would be really appreciated.

I think the cleanest way to do this would be with a block widget decoration set at the start of the document, provided via the decorations method on the plugin.

Thanks for the references. Following the tip, I have created a widget class:

class PlaceholderWidget extends WidgetType<HTMLElement> {

    constructor() {
        super(PlaceholderWidget.createHtmlElement());
    }

    private static createHtmlElement() {
        const placeHolder = document.createElement('div');
        placeHolder.classList.add('placeholder');
        placeHolder.textContent = '<script>\n    function abc() { }\n</script>';
        return placeHolder;
    }

    toDOM(view: EditorView): HTMLElement {
        return super.value;
    }

}

and a decoration as such:

Decoration.widget({
    widget: new PlaceholderWidget()
})

However, I do not get how to make the placeholder ViewPlugin to use this decoration. Any guidance?

Firstly, you don’t want to put the DOM node in the widget’s value field. The placeholder text would be a more reasonable thing to put there. Don’t worry about caching the DOM content, the editor won’t recreate DOM nodes for widget values that compare as equal, so just put the DOM creation in toDOM.

Your plugin could look something like this:

class PlaceholderWidget extends WidgetType<string> {
  toDOM() {
    const placeHolder = document.createElement('div')
    placeHolder.classList.add('placeholder')
    placeHolder.textContent = this.value
    return placeHolder
  }
}

function placeholder(text: string) {
  return ViewPlugin.fromClass(class {
    decorations = Decoration.none
    constructor(view: EditorView) { this.updateDeco(view.state) }
    update(update: ViewUpdate) { this.updateDeco(update.state) }
    updateDeco(state: EditorState) {
      this.decorations = state.doc.length ? Decoration.none
        : Decoration.set(Decoration.widget({widget: new PlaceholderWidget(text), side: 1}).range(0))
    }
  }).decorations()
}

But trying that out, I noticed that it still runs into the contentEditable issue with editing around widgets. The cursor becomes invisible and on Chrome I can’t even continue typing. So it looks like some more work will be needed before this is usable.

1 Like

Thanks a lot for the sample and your help. Setting the side property to 0 and styling the placeholder with absolute positioning seems to solve the issue you mentioned.

1 Like