Transforms – How to apply a mark to the parent of selection.$cursor

I have created a Links Plugin that displays a form when a link is clicked. The view part of it is working, but I am having trouble writing the updateMark command needed to set the attrs of the <a> tag.

I used the prosemirror-command's toggleMark command as a base, and it works if the user selects the link before clicking it.

My issue is when there is no selection, only a cursor.

function updateMark(markType: MarkType, attrs: object) {
  return function(state: any, dispatch: any) {
    let {$cursor, ranges} = state.selection
    if (dispatch) {
      if ($cursor) {
        // This path does not work.
        dispatch(state.tr.setStoredMarks([markType.create(attrs)]))
      } else {
        // This path seems fine
        let tr = state.tr
        for (let i = 0; i < ranges.length; i++) {
          let {$from, $to} = ranges[i]
          tr.addMark($from.pos, $to.pos, markType.create(attrs))
        }
        dispatch(tr.scrollIntoView())
      }
    }
    return true
  }
}

I was able to figure out how to make an updateLink command.

function updateLink(schema: Schema, attrs: object) {
  const linkMarkType = schema.marks["link"]
  const isLink = (child: NodeChild) =>
    child.node && child.node.marks.find(
      mark => mark.type === linkMarkType
    )
  return function(state: any, dispatch: any) {
    if (dispatch) {
      const $cursor: ResolvedPos = state.selection.$cursor
      const tr = state.tr
      if ($cursor) {
        const childBefore = $cursor.doc.childBefore($cursor.pos)
        const childAfter = $cursor.doc.childAfter($cursor.pos)
        const link = isLink(childBefore) ? childBefore : childAfter
        const from = link.offset
        const to = from + link.node.nodeSize
        tr.addMark(from, to, linkMarkType.create(attrs))
      }
      dispatch(tr.scrollIntoView())
    }
    return true
  }
}

The only issue that I have with this command now, is that the link must only have unmarked text as a child.

For example, given this link <a href="/">H<strong>om</strong>e</a>

If you click at the start of the word, right before H, and set href="/about" the result will be:

<a href="/about">H</a><a href="/"><strong>om</strong>e</a>

Part of the problem is that the link described above is actually 3 different nodes, each with the same Link mark on it. I think the solution would be to only allow text inside a tags. Any other suggestions?

This thread may be relevant.

This works for updating marks:

function updateMark(markType: MarkType, attrs: object) {
  return function (state: EditorState, dispatch: any) {
    if (dispatch) {
      let from = state.selection.from
      let to = state.selection.to
      if (from === to) {
        const position = state.tr.doc.resolve(from)
        const parentInfo = position.parent.childBefore(position.parentOffset)
        from = parentInfo.offset + 1
        to = from + (parentInfo.node?.nodeSize || 0) + 1
      }
      dispatch(state.tr.addMark(from, to, markType.create(attrs)), state.tr.scrollIntoView())
    }
    return true
  }
}
1 Like