Adding style on the fly

Thanks @marijn.

For those landing here, you’ll find my full code below (comments welcome). It dynamically adds the <small> tag to the end of any text following the last ‘(’ in a heading. For example:

<h2>Hello World! (I mean it!)</h2>

becomes:

<h2>Hello World! <small>(I mean it!)</small></h2>

This is the code to add the <small> tag to the schema:

const { nodes, marks } = require('prosemirror-schema-basic')
marks.small = {
  excludes: '_',		// Prevent any other mark from being applied to this mark
  parseDOM: [{ tag: 'small' }],
  toDOM: function toDOM(node) { 
    return ['small', { style: 'font-size:10px;color:dodgerblue' }] 
  }
}
mySchema = new Schema({ nodes, marks })

The plugin:

const { Plugin } = require("prosemirror-state")

const addSmallMark = new Plugin({
  appendTransaction(tr, oldState, newState) {
    // Initialize an empty transaction. We will enrich it so that, when applied, 
    // it adds the <small> tag to the end of any text following the last '(' 
    // in a heading.
    let newTr = newState.tr

    // Loop through the transactions
    _.each(tr, transaction => {
      // Loop through the steps
      _.each(transaction.steps, step => {
        // Loop through positions affected by the step
        step.getMap().forEach((oldStart, oldEnd, newStart, newEnd) => {
          // Loop through nodes affected by the transaction in the new state
          newState.doc.nodesBetween(newStart, newEnd, (parentNode, parentPos) => {
            // We consider headings only
            if (parentNode.type.name !== 'heading')
              return
            
            // Loop through children nodes of the heading
            parentNode.forEach((childNode, childOffset) => {
              // We consider text nodes only
              if (!childNode.isText)
                return
              
              // See if the child node has a '(' near the end. If it doesn't, quit
              const relativePos = childNode.text.lastIndexOf('(')
              if (relativePos === -1)
                return

              // If it does, remove any <small> style up to the '(' and add
              // a <small> style from the '(' to the end of the heading
              const absolutePos = parentPos + childOffset + relativePos + 1 // Why +1 ?
              const mark = mySchema.mark('small')							
              newTr = newTr.removeMark(parentPos, absolutePos, mark)
              newTr = newTr.addMark(absolutePos, parentPos + parentNode.nodeSize, mark)
            })
          })
        })
      })
    })

    // Apply the transaction we have jsut built
    return newTr
  }
}) 	
5 Likes