Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aem2doc parsing now done in da-collab #163

Merged
merged 4 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion blocks/edit/da-editor/da-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ export default class DaEditor extends LitElement {
initIms().then(() => { this._imsLoaded = true; });
}

disconnectWebsocket() {
if (this.wsProvider) {
this.wsProvider.disconnect({ data: 'Client navigation' });
this.wsProvider = undefined;
}
}

disconnectedCallback() {
super.disconnectedCallback();
this.disconnectWebsocket();
}

async fetchVersion() {
const resp = await daFetch(this.version);
if (!resp.ok) return;
Expand Down Expand Up @@ -73,9 +85,10 @@ export default class DaEditor extends LitElement {
updated(props) {
if (!this._imsLoaded) return;
if (!(props.has('version') || props.has('_versionDom'))) {
this.disconnectWebsocket();
const prose = this.shadowRoot.querySelector('.da-prose-mirror');
prose.innerHTML = '';
initProse({ editor: prose, path: this.path });
this.wsProvider = initProse({ editor: prose, path: this.path });
}
}
}
Expand Down
71 changes: 13 additions & 58 deletions blocks/edit/prose/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import {
EditorState,
EditorView,
DOMParser,
Schema,
baseSchema,
history,
Expand All @@ -25,15 +24,13 @@ import {
yUndoPlugin,
yUndo,
yRedo,
prosemirrorToYXmlFragment,
} from 'da-y-wrapper';

// DA
import prose2aem from '../../shared/prose2aem.js';
import menu from './plugins/menu.js';
import imageDrop from './plugins/imageDrop.js';
import linkConverter from './plugins/linkConverter.js';
import { aem2prose, parse } from '../utils/helpers.js';
import { COLLAB_ORIGIN, getDaAdmin } from '../../shared/constants.js';
import { addLocNodes, getLocClass } from './loc-utils.js';

Expand All @@ -58,6 +55,8 @@ function addCustomMarks(marks) {
.addToEnd('contextHighlightingMark', contextHighlight);
}

// Note: until getSchema() is separated in its own module, this function needs to be kept in-sync
// with the getSchema() function in da-collab src/collab.js
export function getSchema() {
const { marks, nodes: baseNodes } = baseSchema.spec;
const withLocNodes = addLocNodes(baseNodes);
Expand Down Expand Up @@ -101,18 +100,6 @@ function pollForUpdates() {
}, 500);
}

// Apply the document in AEM doc format to the editor.
// For this it's converted to Prose and then applied to the current ydoc as an XML fragment
function setAEMDocInEditor(aemDoc, yXmlFragment, schema) {
const doc = parse(aemDoc);
const pdoc = aem2prose(doc);
const docc = document.createElement('div');
docc.append(...pdoc);
const parser = DOMParser.fromSchema(schema);
const fin = parser.parse(docc);
prosemirrorToYXmlFragment(fin, yXmlFragment);
}

function handleAwarenessUpdates(wsProvider, daTitle, win) {
const users = new Set();

Expand Down Expand Up @@ -148,45 +135,13 @@ export function createAwarenessStatusWidget(wsProvider, win) {
return daTitle;
}

export function handleYDocUpdates({
daTitle, editor, ydoc, path, schema, wsProvider, yXmlFragment, fnInitProse,
}, win = window, fnSetAEMDocInEditor = setAEMDocInEditor) {
let firstUpdate = true;
ydoc.on('update', (_, originWS) => {
if (firstUpdate) {
firstUpdate = false;

// Do the following async to allow the ydoc to init itself with any
// changes coming from other editors
setTimeout(() => {
const aemMap = ydoc.getMap('aem');
const current = aemMap.get('content');
const inital = aemMap.get('initial');
if (!current && inital) {
fnSetAEMDocInEditor(inital, yXmlFragment, schema);
}
}, 1);
}

const serverInvKey = 'svrinv';
const svrUpdate = ydoc.getMap('aem').get(serverInvKey);
if (svrUpdate) {
// push update from the server: re-init document
delete daTitle.collabStatus;
delete daTitle.collabUsers;
ydoc.destroy();
wsProvider.destroy();
editor.innerHTML = '';
fnInitProse({ editor, path });
return;
}

if (originWS && originWS !== wsProvider) {
const proseEl = win.view.root.querySelector('.ProseMirror');
const clone = proseEl.cloneNode(true);
const aem = prose2aem(clone);
const aemMap = ydoc.getMap('aem');
aemMap.set('content', aem);
function registerErrorHandler(ydoc) {
ydoc.on('update', () => {
const errorMap = ydoc.getMap('error');
if (errorMap && errorMap.size > 0) {
// eslint-disable-next-line no-console
console.log('Error from server', JSON.stringify(errorMap));
errorMap.clear();
}
});
}
Expand Down Expand Up @@ -226,12 +181,10 @@ export default function initProse({ editor, path }) {
}

const wsProvider = new WebsocketProvider(server, roomName, ydoc, opts);
const daTitle = createAwarenessStatusWidget(wsProvider, window);
createAwarenessStatusWidget(wsProvider, window);
registerErrorHandler(ydoc);

const yXmlFragment = ydoc.getXmlFragment('prosemirror');
handleYDocUpdates({
daTitle, editor, ydoc, path, schema, wsProvider, yXmlFragment, fnInitProse: initProse,
});

if (window.adobeIMS?.isSignedInUser()) {
window.adobeIMS.getProfile().then(
Expand Down Expand Up @@ -310,4 +263,6 @@ export default function initProse({ editor, path }) {

document.execCommand('enableObjectResizing', false, 'false');
document.execCommand('enableInlineTableEditing', false, 'false');

return wsProvider;
}
1 change: 1 addition & 0 deletions blocks/shared/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const DA_ADMIN_ENVS = {

const DA_COLLAB_ENVS = {
local: 'ws://localhost:4711',
stage: 'wss://stage-collab.da.live',
prod: 'wss://collab.da.live',
};

Expand Down
22 changes: 22 additions & 0 deletions test/unit/blocks/edit/da-editor/da-editor.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { expect } from '@esm-bundle/chai';

// This is needed to make a dynamic import work that is indirectly referenced
// from da-editor.js
const { setNx } = await import('../../../../../scripts/utils.js');
setNx('/bheuaark/', { hostname: 'localhost' });

const { default: DaEditor } = await import('../../../../../blocks/edit/da-editor/da-editor.js');

describe('da-editor', () => {
it('Test wsprovider disconnectedcallback', async () => {
const ed = new DaEditor();

const called = [];
const mockWSProvider = { disconnect: () => called.push('disconnect') };

ed.wsProvider = mockWSProvider;
ed.disconnectWebsocket();
expect(ed.wsProvider).to.be.undefined;
expect(called).to.deep.equal(['disconnect']);
});
});
93 changes: 0 additions & 93 deletions test/unit/blocks/edit/proseCollab.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,97 +73,4 @@ describe('Prose collab', () => {
awarenessOnCalled[0].f(delta2); // Call the callback function
expect(daTitle.collabUsers).to.deep.equal(['Anonymous', 'Anonymous', 'Joe Bloggs']);
});

it('Test YDoc firstUpdate callback', (done) => {
const ydocMap = new Map();
ydocMap.set('initial', 'Some intial text');

const ydocOnCalls = [];
const ydoc = {
getMap: (n) => (n === 'aem' ? ydocMap : null),
on: (n, f) => ydocOnCalls.push({ n, f }),
};

const setAEMDocCalls = [];
const fnSetAEMDoc = () => setAEMDocCalls.push('called');

pi.handleYDocUpdates({
daTitle: {},
editor: {},
ydoc,
path: {},
schema: {},
wsProvider: {},
yXmlFragment: {},
fnInitProse: () => {},
}, {}, fnSetAEMDoc);
expect(ydocOnCalls.length).to.equal(1);
expect(ydocOnCalls[0].n).to.equal('update');

ydocOnCalls[0].f();
setTimeout(() => {
expect(setAEMDocCalls).to.deep.equal(['called']);

// the function call again, it should not perform any action this time
ydocOnCalls[0].f();
setTimeout(() => {
expect(setAEMDocCalls).to.deep.equal(
['called'],
'First update code should only be called once',
);
done();
}, 200);
}, 200);
});

it('Test YDoc server update callback', () => {
const daTitle = {
collabStatus: 'yeah',
collabUsers: 'some',
};
const editor = {};

const ydocMap = new Map();
ydocMap.set('svrinv', 'Some svrinv text');

const ydocCalls = [];
const ydocOnCalls = [];
const ydoc = {
getMap: (n) => (n === 'aem' ? ydocMap : null),
destroy: () => ydocCalls.push('destroy'),
on: (n, f) => ydocOnCalls.push({ n, f }),
};

const wspCalls = [];
const wsp = { destroy: () => wspCalls.push('destroy') };

const initProseCalls = [];
const mockInitProse = () => initProseCalls.push('init');

pi.handleYDocUpdates({
daTitle,
editor,
ydoc,
path: {},
schema: {},
wsProvider: wsp,
yXmlFragment: {},
fnInitProse: mockInitProse,
}, {}, () => {});
expect(ydocOnCalls.length).to.equal(1);
expect(ydocOnCalls[0].n).to.equal('update');

expect(daTitle.collabStatus).to.equal('yeah', 'Precondition');
expect(daTitle.collabUsers).to.equal('some', 'Precondition');

// Calls server invalidation
ydocOnCalls[0].f();

expect(daTitle.collabStatus).to.be.undefined;
expect(daTitle.collabUsers).to.be.undefined;
expect(ydocCalls).to.deep.equal(['destroy']);
expect(wspCalls).to.deep.equal(['destroy']);
expect(initProseCalls).to.deep.equal(['init']);
expect(editor.innerHTML).to.equal('');
});
});
Loading