From bb1254117933390e69cce22169400f8206393e9d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 19 Jan 2022 23:22:48 +0000 Subject: [PATCH] Improved anchor updating/remove action Now will update the link mark if you have a no-range selection on the link. --- .../js/editor/menu/item-anchor-button.js | 31 +++++++++---- resources/js/editor/util.js | 43 +++++++++++++++++++ 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/resources/js/editor/menu/item-anchor-button.js b/resources/js/editor/menu/item-anchor-button.js index f38985a9c..405d3ab4d 100644 --- a/resources/js/editor/menu/item-anchor-button.js +++ b/resources/js/editor/menu/item-anchor-button.js @@ -6,6 +6,7 @@ import schema from "../schema"; import {MenuItem} from "./menu"; import {icons} from "./icons"; +import {markRangeAtPosition, nullifyEmptyValues} from "../util"; /** * @param {PmMarkType} markType @@ -46,7 +47,7 @@ function getLinkDialog(submitter, closer) { new DialogRadioOptions({ "Same tab or window": "", "New tab or window": "_blank", - },{ + }, { label: 'Behaviour', id: 'target', value: getMarkAttribute(schema.marks.link, 'target'), @@ -69,17 +70,29 @@ function getLinkDialog(submitter, closer) { */ function applyLink(formData, state, dispatch) { const selection = state.selection; - const attrs = Object.fromEntries(formData); - if (dispatch) { - const tr = state.tr; + const attrs = nullifyEmptyValues(Object.fromEntries(formData)); + if (!dispatch) return true; - if (attrs.href) { - tr.addMark(selection.from, selection.to, schema.marks.link.create(attrs)); - } else { - tr.removeMark(selection.from, selection.to, schema.marks.link); + const tr = state.tr; + const noRange = (selection.from - selection.to === 0); + let from = selection.from; + let to = selection.to; + + if (noRange) { + const linkRange = markRangeAtPosition(state, schema.marks.link, selection.from); + if (linkRange.from !== -1) { + from = linkRange.from; + to = linkRange.to; } - dispatch(tr); } + + if (attrs.href) { + tr.addMark(from, to, schema.marks.link.create(attrs)); + } else { + tr.removeMark(from, to, schema.marks.link); + } + + dispatch(tr); return true; } diff --git a/resources/js/editor/util.js b/resources/js/editor/util.js index 6d3fb7417..e1746a725 100644 --- a/resources/js/editor/util.js +++ b/resources/js/editor/util.js @@ -33,6 +33,49 @@ export function stateToHtml(state) { return renderDoc.body.innerHTML; } +/** + * @param {Object} object + * @return {{}} + */ +export function nullifyEmptyValues(object) { + const clean = {}; + for (const [key, value] of Object.entries(object)) { + clean[key] = (value === "") ? null : value; + } + return clean; +} + +/** + * @param {PmEditorState} state + * @param {PmMarkType} markType + * @param {Number} pos + * @return {{from: Number, to: Number}} + */ +export function markRangeAtPosition(state, markType, pos) { + const $pos = state.doc.resolve(pos); + + const { parent, parentOffset } = $pos; + const start = parent.childAfter(parentOffset); + if (!start.node) return {from: -1, to: -1}; + + const mark = start.node.marks.find((mark) => mark.type === markType); + if (!mark) return {from: -1, to: -1}; + + let startIndex = $pos.index(); + let startPos = $pos.start() + start.offset; + let endIndex = startIndex + 1; + let endPos = startPos + start.node.nodeSize; + while (startIndex > 0 && mark.isInSet(parent.child(startIndex - 1).marks)) { + startIndex -= 1; + startPos -= parent.child(startIndex).nodeSize; + } + while (endIndex < parent.childCount && mark.isInSet(parent.child(endIndex).marks)) { + endPos += parent.child(endIndex).nodeSize; + endIndex += 1; + } + return { from: startPos, to: endPos }; +} + /** * @class KeyedMultiStack * Holds many stacks, seperated via a key, with a simple