Defining more than one span mark?


#1

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:


underline%20issue


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 {
        style: node.attributes.style.value
      }
    }
  }],
  toDOM: (mark) => {
    console.log(mark.attrs.class)
    return [
      'span',
      Object.assign(
        {},
        mark.attrs.style ? { style: mark.attrs.style } : {},
        {
          class: mark && mark.attrs && mark.attrs.class
            ? mark.attrs.class
            : ''
        }),
      0]
  }
}

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!


#2

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


#3

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())) {
              dispatch(state.tr.removeStoredMark(markType))
            } else {
              dispatch(state.tr.addStoredMark(markType.create(attrs)))
            }
          } else {
            var has = false, tr = state.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))
              }
            }
            dispatch(tr.scrollIntoView())
          }
        }
        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 =
          replaceMark(
            view.state.schema.marks.span,
            { 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 =
          replaceMark(
            view.state.schema.marks[name],
            { class: className })
        command(view.state, view.dispatch)
      }
    }

And here’s a demo :slight_smile:


fixed