Defining more than one span mark?


I’m not sure if I’m approaching something in the right way.

Essentially, the user should have the ability to underline/strikethrough text and color that text and text adjacent to the underline/strikethrough without interference. My approach to this is to have a span mark that can accept changes depending on the command fired by the user. However, things get sticky when marks overlap. I think it best to show you what I mean with a video:


What would need to happen in the example above is that - when the colour is applied - the command should create a new blue mark and split the underlined mark so that half of it is blue (and underlined) and the rest is just black and underlined. I can see a potential way to deal with this using one span tag but it’s going to take a LOT of iterating over Selection objects to find out the POS of different marks within the selection.

here’s the schema for that object:

export default {
  attrs: {
    class: { default: '' },
    style: { default: 'color: rgba(0,0,0,255)' }
  group: 'inline',
  parseDOM: [{
    tag: 'span',
    getAttrs: node => {
      return {
  toDOM: (mark) => {
    return [
        {}, ? { style: } : {},
          class: mark && mark.attrs && mark.attrs.class
            ? mark.attrs.class
            : ''

Ideally, I’d like to have a span mark that handles underline, another for strikethrough and another for colour (etc.) but is that even possible?

Alternatively, have I missed a trick? Or can you think of a way to handle this problem in a painless way! I’ve been fiddling with it all night!


I’ve figured it out. multiple marks - each using span and a custom variant of toggleMarks. bish bosh. I’ve gotta straighten out some bugs but i’ll post up the result once it’s ready


This is effectively a variant of the toggleMark method

    function replaceMark (markType, attrs) {
      return function (state, dispatch) {
        var ref = state.selection
        var $cursor = ref.$cursor
        var ranges = ref.ranges
        if (dispatch) {
          if ($cursor) {
            if (markType.isInSet(state.storedMarks || $cursor.marks())) {
            } else {
          } else {
            var has = false, tr =
            for (var i = 0; !has && i < ranges.length; i++) {
              var ref$1 = ranges[i]
              var $from = ref$1.$from
              var $to = ref$1.$to
              has = state.doc.rangeHasMark($from.pos, $to.pos, markType)
            for (var i$1 = 0; i$1 < ranges.length; i$1++) {
              var ref$2 = ranges[i$1]
              var $from$1 = ref$2.$from
              var $to$1 = ref$2.$to
              if (has) {
                tr.removeMark($from$1.pos, $to$1.pos, markType)
              } else {
                tr.addMark($from$1.pos, $to$1.pos, markType.create(attrs))
        return true

and to call replaceMark I have a couple of generic methods

    this.setSelectionColor = function ({ color }, view) {
      if (view.state.selection.$anchor.pos !== view.state.selection.$head.pos) {
        const command =
            { style: `color: ${color}` })
        command(view.state, view.dispatch)

    this.setSelectionClass = function (className, name, view) {
      if (view.state.selection.$anchor.pos !== view.state.selection.$head.pos) {
        const command =
            { class: className })
        command(view.state, view.dispatch)

And here’s a demo :slight_smile: