Skip to content

Commit

Permalink
feat: allow marks to exist on all nodes that support it
Browse files Browse the repository at this point in the history
  • Loading branch information
Pruxis authored and nperez0111 committed Nov 22, 2024
1 parent df4257b commit d6c99ab
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 11 deletions.
18 changes: 15 additions & 3 deletions src/lib.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { updateYFragment, createNodeFromYElement } from './plugins/sync-plugin.js' // eslint-disable-line
import { updateYFragment, createNodeFromYElement, MarkPrefix } from './plugins/sync-plugin.js' // eslint-disable-line
import { ySyncPluginKey } from './plugins/keys.js'
import * as Y from 'yjs'
import { EditorView } from 'prosemirror-view' // eslint-disable-line
Expand Down Expand Up @@ -416,8 +416,20 @@ export function yXmlFragmentToProsemirrorJSON (xmlFragment) {
}

const attrs = item.getAttributes()
if (Object.keys(attrs).length) {
response.attrs = attrs

// Check wether the attrs contains marks, if so, add them to the response
if (Object.keys(attrs).some((key) => key.startsWith(MarkPrefix))) {
response.marks = Object.keys(attrs)
.filter((key) => key.startsWith(MarkPrefix))
.map((key) => {
return attrs[key]
})
}

// Add all other non-mark attributes to the response
for (const key of Object.keys(attrs).filter((key) => !key.startsWith(MarkPrefix))) {
if (!response.attrs) response.attrs = {}
response.attrs[key] = attrs[key]
}

const children = item.toArray()
Expand Down
66 changes: 58 additions & 8 deletions src/plugins/sync-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import * as random from 'lib0/random'
import * as environment from 'lib0/environment'
import * as dom from 'lib0/dom'
import * as eventloop from 'lib0/eventloop'
import * as f from 'lib0/function'

export const MarkPrefix = '_mark_'

/**
* @param {Y.Item} item
Expand Down Expand Up @@ -723,7 +726,22 @@ export const createNodeFromYElement = (
: { type: 'added' }
}
}
const node = schema.node(el.nodeName, attrs, children)
const nodeAttrs = {}
const nodeMarks = []

for (const key in attrs) {
if (key.startsWith(MarkPrefix)) {
const markName = key.replace(MarkPrefix, '')
const markValue = attrs[key]
if (isObject(markValue)) {
nodeMarks.push(schema.mark(markName, /** @type {Object} */ (markValue).attrs))
}
} else {
nodeAttrs[key] = attrs[key]
}
}

const node = schema.node(el.nodeName, nodeAttrs, children, nodeMarks)
mapping.set(el, node)
return node
} catch (e) {
Expand Down Expand Up @@ -802,12 +820,16 @@ const createTypeFromTextNodes = (nodes, mapping) => {
*/
const createTypeFromElementNode = (node, mapping) => {
const type = new Y.XmlElement(node.type.name)
const nodeMarksAttr = nodeMarksToAttributes(node.marks)
for (const key in node.attrs) {
const val = node.attrs[key]
if (val !== null && key !== 'ychange') {
type.setAttribute(key, val)
}
}
for (const key in nodeMarksAttr) {
type.setAttribute(key, nodeMarksAttr[key])
}
type.insert(
0,
normalizePNodeContent(node).map((n) =>
Expand Down Expand Up @@ -835,7 +857,7 @@ const equalAttrs = (pattrs, yattrs) => {
const keys = Object.keys(pattrs).filter((key) => pattrs[key] !== null)
let eq =
keys.length ===
Object.keys(yattrs).filter((key) => yattrs[key] !== null).length
Object.keys(yattrs).filter((key) => yattrs[key] !== null && !key.startsWith(MarkPrefix)).length
for (let i = 0; i < keys.length && eq; i++) {
const key = keys[i]
const l = pattrs[key]
Expand All @@ -846,6 +868,20 @@ const equalAttrs = (pattrs, yattrs) => {
return eq
}

const equalMarks = (pmarks, yattrs) => {
const keys = Object.keys(yattrs).filter((key) => key.startsWith(MarkPrefix))
let eq =
keys.length === pmarks.length
const pMarkAttr = nodeMarksToAttributes(pmarks)
for (let i = 0; i < keys.length && eq; i++) {
const key = keys[i]
const l = pMarkAttr[key]
const r = yattrs[key]
eq = key === 'ychange' || f.equalityDeep(l, r)
}
return eq
}

/**
* @typedef {Array<Array<PModel.Node>|PModel.Node>} NormalizedPNodeContent
*/
Expand Down Expand Up @@ -900,7 +936,8 @@ const equalYTypePNode = (ytype, pnode) => {
) {
const normalizedContent = normalizePNodeContent(pnode)
return ytype._length === normalizedContent.length &&
equalAttrs(ytype.getAttributes(), pnode.attrs) &&
equalAttrs(pnode.attrs, ytype.getAttributes()) &&
equalMarks(pnode.marks, ytype.getAttributes()) &&
ytype.toArray().every((ychild, i) =>
equalYTypePNode(ychild, normalizedContent[i])
)
Expand Down Expand Up @@ -1017,6 +1054,16 @@ const marksToAttributes = (marks) => {
return pattrs
}

const nodeMarksToAttributes = (marks) => {
const pattrs = {}
marks.forEach((mark) => {
if (mark.type.name !== 'ychange') {
pattrs[`${MarkPrefix}${mark.type.name}`] = mark.toJSON()
}
})
return pattrs
}

/**
* Update a yDom node by syncing the current content of the prosemirror node.
*
Expand All @@ -1042,18 +1089,21 @@ export const updateYFragment = (y, yDomFragment, pNode, mapping) => {
if (yDomFragment instanceof Y.XmlElement) {
const yDomAttrs = yDomFragment.getAttributes()
const pAttrs = pNode.attrs
for (const key in pAttrs) {
if (pAttrs[key] !== null) {
if (yDomAttrs[key] !== pAttrs[key] && key !== 'ychange') {
yDomFragment.setAttribute(key, pAttrs[key])
const pNodeMarksAttr = nodeMarksToAttributes(pNode.marks)
const attrs = { ...pAttrs, ...pNodeMarksAttr }

for (const key in attrs) {
if (attrs[key] !== null) {
if (yDomAttrs[key] !== attrs[key] && key !== 'ychange') {
yDomFragment.setAttribute(key, attrs[key])
}
} else {
yDomFragment.removeAttribute(key)
}
}
// remove all keys that are no longer in pAttrs
for (const key in yDomAttrs) {
if (pAttrs[key] === undefined) {
if (attrs[key] === undefined) {
yDomFragment.removeAttribute(key)
}
}
Expand Down

0 comments on commit d6c99ab

Please sign in to comment.