Skip to content

Commit

Permalink
Add support for converting directly between prosemirror and Y.XmlFrag…
Browse files Browse the repository at this point in the history
…ment nodes.
  • Loading branch information
nkeynes committed Jan 24, 2022
1 parent 039be96 commit d90aa69
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 5 deletions.
66 changes: 64 additions & 2 deletions src/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,32 @@ export function prosemirrorToYDoc (doc, xmlFragment = 'prosemirror') {
return ydoc
}

updateYFragment(type.doc, type, doc, new Map())
prosemirrorToYXmlFragment(doc, type)
return type.doc
}

/**
* Utility method to update an empty Y.XmlFragment with content from a Prosemirror Doc Node.
*
* This can be used when importing existing content to Y.Doc for the first time,
* note that this should not be used to rehydrate a Y.Doc from a database once
* collaboration has begun as all history will be lost
*
* Note: The Y.XmlFragment does not need to be part of a Y.Doc document at the time that this
* method is called, but it must be added before any other operations are performed on it.
*
* @param {Node} doc prosemirror document.
* @param {Y.XmlFragment} [xmlFragment] If supplied, an xml fragment to be
* populated from the prosemirror state; otherwise a new XmlFragment will be created.
* @return {Y.XmlFragment}
*/
export function prosemirrorToYXmlFragment (doc, xmlFragment) {
const type = xmlFragment || new Y.XmlFragment()
const ydoc = type.doc ? type.doc : { transact: (transaction) => transaction(undefined) }
updateYFragment(ydoc, type, doc, new Map())
return type
}

/**
* Utility method to convert Prosemirror compatible JSON into a Y.Doc.
*
Expand All @@ -226,6 +248,24 @@ export function prosemirrorJSONToYDoc (schema, state, xmlFragment = 'prosemirror
return prosemirrorToYDoc(doc, xmlFragment)
}

/**
* Utility method to convert Prosemirror compatible JSON to a Y.XmlFragment
*
* This can be used when importing existing content to Y.Doc for the first time,
* note that this should not be used to rehydrate a Y.Doc from a database once
* collaboration has begun as all history will be lost
*
* @param {Schema} schema
* @param {any} state
* @param {Y.XmlFragment} [xmlFragment] If supplied, an xml fragment to be
* populated from the prosemirror state; otherwise a new XmlFragment will be created.
* @return {Y.XmlFragment}
*/
export function prosemirrorJSONToYXmlFragment (schema, state, xmlFragment) {
const doc = Node.fromJSON(schema, state)
return prosemirrorToYXmlFragment(doc, xmlFragment)
}

/**
* Utility method to convert a Y.Doc to a Prosemirror Doc node.
*
Expand All @@ -238,6 +278,18 @@ export function yDocToProsemirror (schema, ydoc) {
return Node.fromJSON(schema, state)
}

/**
* Utility method to convert a Y.XmlFragment to a Prosemirror Doc node.
*
* @param {Schema} schema
* @param {Y.XmlFragment} xmlFragment
* @return {Node}
*/
export function yXmlFragmentToProsemirror (schema, xmlFragment) {
const state = yXmlFragmentToProsemirrorJSON(xmlFragment)
return Node.fromJSON(schema, state)
}

/**
* Utility method to convert a Y.Doc to Prosemirror compatible JSON.
*
Expand All @@ -249,7 +301,17 @@ export function yDocToProsemirrorJSON (
ydoc,
xmlFragment = 'prosemirror'
) {
const items = ydoc.getXmlFragment(xmlFragment).toArray()
return yXmlFragmentToProsemirrorJSON(ydoc.getXmlFragment(xmlFragment))
}

/**
* Utility method to convert a Y.Doc to Prosemirror compatible JSON.
*
* @param {Y.XmlFragment} xmlFragment The fragment, which must be part of a Y.Doc.
* @return {Record<string, any>}
*/
export function yXmlFragmentToProsemirrorJSON (xmlFragment) {
const items = xmlFragment.toArray()

function serialize (item) {
/**
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/sync-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ const marksToAttributes = marks => {

/**
* @private
* @param {Y.Doc} y
* @param {{transact: Function}} y
* @param {Y.XmlFragment} yDomFragment
* @param {any} pNode
* @param {ProsemirrorMapping} mapping
Expand Down
3 changes: 2 additions & 1 deletion src/y-prosemirror.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from './plugins/cursor-plugin.js'
export { ySyncPlugin, isVisible, getRelativeSelection, ProsemirrorBinding } from './plugins/sync-plugin.js'
export * from './plugins/undo-plugin.js'
export * from './plugins/keys.js'
export { absolutePositionToRelativePosition, relativePositionToAbsolutePosition, setMeta, prosemirrorJSONToYDoc, yDocToProsemirrorJSON, yDocToProsemirror, prosemirrorToYDoc } from './lib.js'
export { absolutePositionToRelativePosition, relativePositionToAbsolutePosition, setMeta, prosemirrorJSONToYDoc, yDocToProsemirrorJSON, yDocToProsemirror, prosemirrorToYDoc,
prosemirrorJSONToYXmlFragment, yXmlFragmentToProsemirrorJSON, yXmlFragmentToProsemirror, prosemirrorToYXmlFragment } from './lib.js'
17 changes: 16 additions & 1 deletion test/y-prosemirror.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as Y from 'yjs'
// @ts-ignore
import { applyRandomTests } from 'yjs/testHelper'

import { ySyncPlugin, prosemirrorJSONToYDoc, yDocToProsemirrorJSON } from '../src/y-prosemirror.js'
import { ySyncPlugin, prosemirrorJSONToYDoc, yDocToProsemirrorJSON, prosemirrorJSONToYXmlFragment, yXmlFragmentToProsemirrorJSON } from '../src/y-prosemirror.js'
import { EditorState, TextSelection } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import * as basicSchema from 'prosemirror-schema-basic'
Expand All @@ -27,6 +27,21 @@ export const testDocTransformation = tc => {
t.compare(stateJSON, backandforth)
}

export const testXmlFragmentTransformation = tc => {
const view = createNewProsemirrorView(new Y.Doc())
view.dispatch(view.state.tr.insert(0, /** @type {any} */ (schema.node('paragraph', undefined, schema.text('hello world')))))
const stateJSON = view.state.doc.toJSON()
console.log(JSON.stringify(stateJSON))
// test if transforming back and forth from yXmlFragment works
const xml = new Y.XmlFragment()
prosemirrorJSONToYXmlFragment(/** @type {any} */ (schema), stateJSON, xml)
const doc = new Y.Doc()
doc.getMap('root').set('firstDoc', xml)
const backandforth = yXmlFragmentToProsemirrorJSON(xml)
console.log(JSON.stringify(backandforth))
t.compare(stateJSON, backandforth)
}

/**
* @param {t.TestCase} tc
*/
Expand Down

0 comments on commit d90aa69

Please sign in to comment.