From f8bc8dfc0f1fabc69185ede9643505f8dbca4f03 Mon Sep 17 00:00:00 2001 From: BrianHung Date: Mon, 16 Aug 2021 13:02:44 -0700 Subject: [PATCH 1/6] support marks on element nodes --- src/plugins/sync-plugin.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/plugins/sync-plugin.js b/src/plugins/sync-plugin.js index 7051bbd..0930980 100644 --- a/src/plugins/sync-plugin.js +++ b/src/plugins/sync-plugin.js @@ -442,7 +442,15 @@ const createNodeFromYElement = (el, schema, mapping, snapshot, prevSnapshot, com attrs.ychange = computeYChange ? computeYChange('added', /** @type {Y.Item} */ (el._item).id) : { type: 'added' } } } - const node = schema.node(el.nodeName, attrs, children) + const marks = [] + for (const key in attrs) { + let markName = schema.marks[key] && key + if (markName) { + marks.push(schema.mark(markName, attrs[markName])) + delete attrs[markName] + } + } + const node = schema.node(el.nodeName, attrs, children, marks) mapping.set(el, node) return node } catch (e) { @@ -520,6 +528,13 @@ const createTypeFromElementNode = (node, mapping) => { type.setAttribute(key, val) } } + const markAttrs = marksToAttributes(node.marks) + for (const key in markAttrs) { + const val = markAttrs[key] + if (val !== null) { + type.setAttribute(key, val) + } + } type.insert(0, normalizePNodeContent(node).map(n => createTypeFromTextOrElementNode(n, mapping))) mapping.set(type, node) return type From 311a356115c0602d4b1415638d34c4c5ccbfb355 Mon Sep 17 00:00:00 2001 From: BrianHung Date: Mon, 16 Aug 2021 13:11:19 -0700 Subject: [PATCH 2/6] iterate over node.marks directly instead of marksToAttributes --- src/plugins/sync-plugin.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/plugins/sync-plugin.js b/src/plugins/sync-plugin.js index 0930980..5732739 100644 --- a/src/plugins/sync-plugin.js +++ b/src/plugins/sync-plugin.js @@ -528,11 +528,9 @@ const createTypeFromElementNode = (node, mapping) => { type.setAttribute(key, val) } } - const markAttrs = marksToAttributes(node.marks) - for (const key in markAttrs) { - const val = markAttrs[key] - if (val !== null) { - type.setAttribute(key, val) + for (const mark in node.marks) { + if (mark.type.name !== 'ychange') { + type.setAttribute(mark.type.name, mark.attrs) } } type.insert(0, normalizePNodeContent(node).map(n => createTypeFromTextOrElementNode(n, mapping))) From 3391f7a0f54fec9db3c94aae3e3ab847886dea59 Mon Sep 17 00:00:00 2001 From: BrianHung Date: Mon, 16 Aug 2021 18:01:09 -0700 Subject: [PATCH 3/6] separate 'marks' from other attributes and serialize as array --- src/plugins/sync-plugin.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/plugins/sync-plugin.js b/src/plugins/sync-plugin.js index 5732739..7a2e245 100644 --- a/src/plugins/sync-plugin.js +++ b/src/plugins/sync-plugin.js @@ -442,14 +442,8 @@ const createNodeFromYElement = (el, schema, mapping, snapshot, prevSnapshot, com attrs.ychange = computeYChange ? computeYChange('added', /** @type {Y.Item} */ (el._item).id) : { type: 'added' } } } - const marks = [] - for (const key in attrs) { - let markName = schema.marks[key] && key - if (markName) { - marks.push(schema.mark(markName, attrs[markName])) - delete attrs[markName] - } - } + const marks = attrs.marks && attrs.marks.map(mark => schema.markFromJSON(mark)) + delete attrs.marks const node = schema.node(el.nodeName, attrs, children, marks) mapping.set(el, node) return node @@ -528,10 +522,8 @@ const createTypeFromElementNode = (node, mapping) => { type.setAttribute(key, val) } } - for (const mark in node.marks) { - if (mark.type.name !== 'ychange') { - type.setAttribute(mark.type.name, mark.attrs) - } + if (node.marks.length) { + type.setAttribute('marks', node.marks.map(mark => mark.toJSON())) } type.insert(0, normalizePNodeContent(node).map(n => createTypeFromTextOrElementNode(n, mapping))) mapping.set(type, node) From 4edca477e1586d71d6bc4b61a93314f51a91dd2a Mon Sep 17 00:00:00 2001 From: BrianHung Date: Mon, 16 Aug 2021 18:10:08 -0700 Subject: [PATCH 4/6] update `equalYTypePNode` to check marks --- src/plugins/sync-plugin.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/plugins/sync-plugin.js b/src/plugins/sync-plugin.js index 7a2e245..a2e5717 100644 --- a/src/plugins/sync-plugin.js +++ b/src/plugins/sync-plugin.js @@ -550,6 +550,17 @@ const equalAttrs = (pattrs, yattrs) => { return eq } +/** + * Compares Prosemirror marks using their serialized `toJSON()` forms. + * https://github.com/ProseMirror/prosemirror-model/blob/master/src/mark.js + */ +const equalMarks = (pmarks, ymarks) => { + if (pmarks.length != ymarks.length) return false + for (let i = 0; i < pmarks.length; i++) + if (pmarks[i].type != y.marks[i].type || !equalAttrs(pmarks[i].attrs, ymarks[i].attrs)) return false + return true +} + /** * @typedef {Array|PModel.Node>} NormalizedPNodeContent */ @@ -587,13 +598,15 @@ const equalYTextPText = (ytext, ptexts) => { } /** + * Compares ytype with pnode (Prosemirror node) by looking at normalized content, attrs, marks, and children. + * * @param {Y.XmlElement|Y.XmlText|Y.XmlHook} ytype * @param {any|Array} pnode */ const equalYTypePNode = (ytype, pnode) => { if (ytype instanceof Y.XmlElement && !(pnode instanceof Array) && matchNodeName(ytype, pnode)) { const normalizedContent = normalizePNodeContent(pnode) - return ytype._length === normalizedContent.length && equalAttrs(ytype.getAttributes(), pnode.attrs) && ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, normalizedContent[i])) + return ytype._length === normalizedContent.length && equalAttrs(ytype.getAttributes(), pnode.attrs) && equalMarks(ytype.getAttributes().marks || [], pnode.marks) && ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, normalizedContent[i])) } return ytype instanceof Y.XmlText && pnode instanceof Array && equalYTextPText(ytype, pnode) } @@ -705,7 +718,7 @@ export const updateYFragment = (y, yDomFragment, pNode, mapping) => { throw new Error('node name mismatch!') } mapping.set(yDomFragment, pNode) - // update attributes + // update attributes and marks if (yDomFragment instanceof Y.XmlElement) { const yDomAttrs = yDomFragment.getAttributes() const pAttrs = pNode.attrs @@ -724,6 +737,9 @@ export const updateYFragment = (y, yDomFragment, pNode, mapping) => { yDomFragment.removeAttribute(key) } } + if (pNode.marks.length) { + yDomFragment.setAttribute('marks', pNode.marks.map(mark => mark.toJSON())) + } } // update children const pChildren = normalizePNodeContent(pNode) From b8c5a951d7ba07cc620119cfa365f56a065afb8b Mon Sep 17 00:00:00 2001 From: BrianHung Date: Mon, 16 Aug 2021 18:40:31 -0700 Subject: [PATCH 5/6] fix calls in `equalTypePNode` to `equalAttrs` and `equalMarks` --- src/plugins/sync-plugin.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/plugins/sync-plugin.js b/src/plugins/sync-plugin.js index a2e5717..339ba9c 100644 --- a/src/plugins/sync-plugin.js +++ b/src/plugins/sync-plugin.js @@ -554,10 +554,10 @@ const equalAttrs = (pattrs, yattrs) => { * Compares Prosemirror marks using their serialized `toJSON()` forms. * https://github.com/ProseMirror/prosemirror-model/blob/master/src/mark.js */ -const equalMarks = (pmarks, ymarks) => { + const equalMarks = (pmarks, ymarks) => { if (pmarks.length != ymarks.length) return false for (let i = 0; i < pmarks.length; i++) - if (pmarks[i].type != y.marks[i].type || !equalAttrs(pmarks[i].attrs, ymarks[i].attrs)) return false + if (pmarks[i].type != ymarks[i].type || !equalAttrs(pmarks[i].attrs, ymarks[i].attrs)) return false return true } @@ -603,10 +603,20 @@ const equalYTextPText = (ytext, ptexts) => { * @param {Y.XmlElement|Y.XmlText|Y.XmlHook} ytype * @param {any|Array} pnode */ -const equalYTypePNode = (ytype, pnode) => { + const equalYTypePNode = (ytype, pnode) => { if (ytype instanceof Y.XmlElement && !(pnode instanceof Array) && matchNodeName(ytype, pnode)) { - const normalizedContent = normalizePNodeContent(pnode) - return ytype._length === normalizedContent.length && equalAttrs(ytype.getAttributes(), pnode.attrs) && equalMarks(ytype.getAttributes().marks || [], pnode.marks) && ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, normalizedContent[i])) + let normalizedContent = normalizePNodeContent(pnode) + if (normalizedContent.length !== ytype._length) return false + // Exclude `marks` attribute from comparison with `pnode.attrs` if it exists as attribute on ytype. + let yattrs = ytype.getAttributes() + let pattrs = pnode.attrs + delete yattrs.marks + if (!equalAttrs(yattrs, pattrs)) return false + // Serialize `pnode.marks` so it is in the same form as `marks` in ytype. + let ymarks = ytype.getAttribute('marks') || [] + let pmarks = pnode.marks.map(mark => mark.toJSON()) + if (!equalMarks(ymarks, pmarks)) return false + return ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, normalizedContent[i])) } return ytype instanceof Y.XmlText && pnode instanceof Array && equalYTextPText(ytype, pnode) } From d563e3c8b35426295f3dbed278046fca004e9857 Mon Sep 17 00:00:00 2001 From: Brian Hung Date: Tue, 17 Aug 2021 12:45:47 -0700 Subject: [PATCH 6/6] lint --- src/plugins/sync-plugin.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/plugins/sync-plugin.js b/src/plugins/sync-plugin.js index 339ba9c..6658a19 100644 --- a/src/plugins/sync-plugin.js +++ b/src/plugins/sync-plugin.js @@ -554,10 +554,11 @@ const equalAttrs = (pattrs, yattrs) => { * Compares Prosemirror marks using their serialized `toJSON()` forms. * https://github.com/ProseMirror/prosemirror-model/blob/master/src/mark.js */ - const equalMarks = (pmarks, ymarks) => { - if (pmarks.length != ymarks.length) return false - for (let i = 0; i < pmarks.length; i++) - if (pmarks[i].type != ymarks[i].type || !equalAttrs(pmarks[i].attrs, ymarks[i].attrs)) return false +const equalMarks = (pmarks, ymarks) => { + if (pmarks.length !== ymarks.length) return false + for (let i = 0; i < pmarks.length; i++) { + if (pmarks[i].type !== ymarks[i].type || !equalAttrs(pmarks[i].attrs, ymarks[i].attrs)) return false + } return true } @@ -598,12 +599,12 @@ const equalYTextPText = (ytext, ptexts) => { } /** - * Compares ytype with pnode (Prosemirror node) by looking at normalized content, attrs, marks, and children. - * + * Compares ytype with pnode (Prosemirror node) by looking at normalized content, attrs, marks, and children + * * @param {Y.XmlElement|Y.XmlText|Y.XmlHook} ytype * @param {any|Array} pnode */ - const equalYTypePNode = (ytype, pnode) => { +const equalYTypePNode = (ytype, pnode) => { if (ytype instanceof Y.XmlElement && !(pnode instanceof Array) && matchNodeName(ytype, pnode)) { let normalizedContent = normalizePNodeContent(pnode) if (normalizedContent.length !== ytype._length) return false