I wish to display the shared cursor information of other clients in the editor.
So far, I have editted: EditorConnection.js in the web example, and is able to get the information of client A’s cursor information to be sent to client B and client C…etc.
The information that client B and client C receives is here: {clientID: “1e8f4b00-0326-11eb-aa11-3d2f6cec7c6a” selection: {anchor: 226 head: 226 type: “text”}}
but I am not sure how to make this a display in the editor. I have created a CursorPlugin.js // @flow
import {EditorState, Plugin, PluginKey} from 'prosemirror-state';
import {TextSelection} from 'prosemirror-state';
import {EditorView} from 'prosemirror-view';
import {Decoration, DecorationSet} from 'prosemirror-view';
import {MARK_LINK} from './MarkNames';
import LinkTooltip from './ui/LinkTooltip';
import './ui/czi-pop-up.css';
const PLACE_HOLDER_ID = {name: 'CursorPlaceholderPlugin2'};
export function showCursor(
json: any,
coords: any
): boolean {
const {runtime, state, readOnly, disabled} = view;
const {schema, plugins} = state;
if (readOnly || disabled || !runtime || !runtime.canUploadImage) {
return false;
}
}
export function showCursorPlaceholder(
state: any,
view: any,
json: any
): boolean {
// console.log('in');
console.log(state.edit.plugins);
console.log(state.edit.plugins[2]);
state.edit.plugins[2].update_cursor(state.edit, json);
// console.log(view);
// console.log(json);
}
function defaultCursorBuilder() {
const cursor = document.createElement('span')
cursor.classList.add('ProseMirror-yjs-cursor')
cursor.setAttribute('style', `border-color: #000000`)
const userDiv = document.createElement('div')
userDiv.setAttribute('style', `background-color: #000000`)
userDiv.insertBefore(document.createTextNode('Ted'), null)
cursor.insertBefore(userDiv, null)
return cursor
}
class CursorPlaceholderPlugin2 extends Plugin {
_object = null;
_editor = null;
constructor() {
super({
// [FS] IRAD-1005 2020-07-07
// Upgrade outdated packages.
key: new PluginKey('CursorPlaceholderPlugin2'),
state: {
init() {
return DecorationSet.empty;
},
// apply(tr, prevState, oldState, newState) {
// console.log('# bookmark 1');
// console.log(tr);
// console.log(prevState);
// console.log(oldState);
// console.log(newState);
// prevState = prevState.map(tr.mapping, tr.doc);
// // const action = tr.getMeta(this);
// // if (!action) {
// // return set;
// // }
// // console.log('');
// // const widget = document.createElement('czi-cursor-placeholder2');
// // widget.className = 'czi-cursor-placeholder2';
// // const deco = Decoration.widget(action.add.pos, widget, {
// // id: PLACE_HOLDER_ID,
// // });
// // set = set.add(tr.doc, [deco]);
//
// console.log(prevState);
// return prevState;
// }
apply(tr: Transform, set: DecorationSet): DecorationSet {
console.log('123142414151');
// Adjust decoration positions to changes made by the transaction
set = set.map(tr.mapping, tr.doc);
// See if the transaction adds or removes any placeholders
const action = tr.getMeta(this);
console.log(tr);
if (action && action.add) {
const cursor = defaultCursorBuilder();
const deco = Decoration.widget(action.add.pos, cursor, {
id: action.add.id,
});
set = set.add(tr.doc, [deco]);
} else if (action && action.remove) {
const finder = spec => spec.id == action.remove.id;
set = set.remove(set.find(null, null, finder));
}
return set;
}
},
view(editorView: EditorView) {
console.log('# bookmark 2');
console.log(editorView);
this._object = new CursorView(editorView);
this._editor = editorView;
return this._object;
},
});
}
update_cursor(state, json) {
console.log(state);
this._object.update(this._editor, json);
}
}
class CursorView {
_editor = null;
_cursor = null;
_cursor_div = null;
constructor(editorView: EditorView) {
this._editor = editorView;
this._cursor = document.createElement('span');
this._cursor.className = 'czi-cursor-placeholder2';
this._cursor.contenteditable = 'false';
this._cursor_div = document.createElement('div');
this._cursor_div.textContent = 'User'
this._cursor.appendChild(this._cursor_div);
editorView.dom.parentNode.appendChild(this._cursor);
this.update(editorView, null);
}
update(view, editor): void {
//state
//view.state.doc
//editor.doc
// let json = editor;
if (view != null) {
console.log('#12314');
console.log(view);
this._cursor.style.display = '';
let box = this._cursor.offsetParent.getBoundingClientRect();
let start = view.coordsAtPos(view.state.selection.head), end = view.coordsAtPos(view.state.selection.anchor)
let left = Math.max((start.left + end.left) / 2, start.left + 3)
this._cursor.style.left = (left - box.left) + 'px'
this._cursor.style.bottom = (box.bottom - start.top - 15) + 'px'
let decorations = []
if (view.state.selection.anchor !== null && view.state.selection.head !== null) {
const maxsize = Math.max(view.state.doc.content.size - 1, 0)
const anchor = Math.min(view.state.selection.anchor, maxsize)
const head = Math.min(view.state.selection.head, maxsize)
decorations.push(Decoration.widget(head, defaultCursorBuilder(), { key: 12345 + '', side: 10 }))
const from = Math.min(anchor, head)
const to = Math.max(anchor, head)
decorations.push(Decoration.inline(from, to, { style: `background-color: #000000` }, { inclusiveEnd: true, inclusiveStart: false }))
}
return DecorationSet.create(view.state.doc, decorations)
} else {
this._cursor.style.display = 'none';
return DecorationSet.empty;
}
}
destroy() {
this._cursor.style.display = 'none';
return DecorationSet.empty;
}
}
export default CursorPlaceholderPlugin2;
I tried to export showCursorPlaceholder(), and call it directly from EditorConnection.js, but the editorview is empty, I guess it is necessary to be called only through transaction and dispatch?
How can I make a transaction to change state without changing the contents in the editor?
Sorry that my understanding is not as thorough. If there is something unclear, please do let me know.