Apply decorations directly through an editor method?

Is there a way to apply decorations directly through an editor method?

For example, let’s say my application knows that it wants to make everything from position 123 to position 456 purple. Assuming the EditorView object is called “editor,” what I’d really like to do is something as simple as:

editor.applyDecoration(Decoration.inline(123, 456, {style: "color: purple"})

Is there any way to do it as simple as that? If not, what’s the simplest way to go about it?

(Apologies if this is a basic question. From the documentation on decorations, I can see how to set them relative to things already in ProseMirror’s state, such as the current selection, but I couldn’t find any simple example of how to invoke them on the fly based on business logic sitting outside of ProseMirror.)

EDIT: I wrote an example plugin + helper method to enact the behavior I describe here, code is below.

2 Likes

No, that doesn’t exist. Decorations are always owned by a plugin, and ProseMirror’s state is only updated through transactions, not with imperative methods.

So you could write a plugin that manages a set of decorations, and write a function applyDecoration that fires off a transaction to add a given decoration. But there’s no built-in editor method like that.

Thanks Marjin- makes sense and I do want to write said plugin.

After spending several hours on the docs and testing, though, I’m still not clear on how one can “fire off a transaction to add a decoration.”

I’ve run and tweaked the examples on “Decoration” from the guide (purplePlugin and specklePlugin) but they appear to work by returning a DecorationSet from props, I didn’t see anything about applying transactions there. (The only bit I saw referencing transactions was the apply() method, but that appears to be about allowing the plugin to have a transaction applied to it, not generating one.)

I’ve also looked through the transaction section of the reference guide, and I saw lots of methods that deal with either text or marks, but I didn’t see any that refer to decorations.

Are there any examples that show how to “fire off a transaction to add a decoration?”

The view’s dispatch method is the usual way to run a transaction. A plugin’s state apply method can look for specific metadata properties in a transaction, and update its state (including its decoration set) based on that. So you’d define a metadata property, have a helper function that creates a transaction with that metadata, and a plugin that interprets it to add/remove decorations.

1 Like

Hmm, so if I understand you correctly, the two pieces I need are:

  1. A helper function that calls the view’s dispatch method to run a transaction which includes metadata
  2. A plugin with an apply method that’s capable of interpreting that metadata, and updating its state (including decoration set) accordingly

Does that sound about right?

I’m starting with piece 1), but before attempt to I trigger a transaction with metadata, I first want to make sure I can successfully trigger any kind of very simple transaction without getting an error message before moving on.

Here’s what I tried:

function insertSomeText(state, view) {
      let transaction = state.tr.insertText("abcd", 19,23)
      view.dispatch(transaction)
    }

However, this gives me the following error:

vue.runtime.esm.js?2b0e:1888 TypeError: field.apply is not a function
    at EditorState.applyInner (index.es.js?5313:855)
    at EditorState.applyTransaction (index.es.js?5313:819)
    at EditorState.apply (index.es.js?5313:795)
    at Editor.dispatchTransaction (tiptap.esm.js?cd42:1293)
    at EditorView.dispatch (index.es.js?576a:4703)
    at insertSomeText (Transcript.vue?8397:70)
    at VueComponent.triggerTransaction (Transcript.vue?8397:192)
    at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
    at HTMLButtonElement.invoker (vue.runtime.esm.js?2b0e:2179)
    at HTMLButtonElement.original._wrapper (vue.runtime.esm.js?2b0e:6911)

I’m not sure what this means, “field.apply is not a function”?

I get this same error message if I omit the “19,23” arguments, so I assume that means it’s not related to the pos values, and I’ve tried a few other types of transactions in place of insertText and received the same error as well. Any idea what am I doing wrong here?

That’s not from the code you showed, but from a broken plugin with an invalid state property.

Thanks, now I’m starting to get it!

I managed to get it to work, code is below.

Helper Function:

function applyDecoration (state, view, fromPos, toPos) {
  let transaction = state.tr.setMeta("directDecoration", {fromPos: fromPos, toPos: toPos})
  view.dispatch(transaction)
}

Plugin:

let directDecoration= new Plugin({
  state: {
    init() {
      // No decorations set by default
    },
    apply(tr, value) {
      if (tr.getMeta("directDecoration")) {
        const {fromPos, toPos} = tr.getMeta("directDecoration")
        return DecorationSet.create(tr.doc, [
          Decoration.inline(fromPos, toPos, {style: "color: purple"})
        ])
      } else {
         // map "other" changes so our decoration "stays put" 
         // (e.g. user is typing so decoration's pos must change)
        return value.map(tr.mapping, tr.doc)
      }
    }
  },
  props: {
    decorations(state) {
      return directDecoration.getState(state)
    }
  }
})
2 Likes