UNPKG

@atlaskit/editor-plugin-collab-edit

Version:

Collab Edit plugin for @atlaskit/editor-core

256 lines (250 loc) 11.3 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.getValidPos = exports.PluginState = void 0; var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _browser = require("@atlaskit/editor-common/browser"); var _collab = require("@atlaskit/editor-common/collab"); var _state = require("@atlaskit/editor-prosemirror/state"); var _view = require("@atlaskit/editor-prosemirror/view"); var _participants = require("../participants"); var _utils = require("../utils"); /** * Returns position where it's possible to place a decoration. */ var getValidPos = exports.getValidPos = function getValidPos(tr, pos) { var endOfDocPos = tr.doc.nodeSize - 2; if (pos <= endOfDocPos) { var resolvedPos = tr.doc.resolve(pos); var backwardSelection = _state.Selection.findFrom(resolvedPos, -1, true); // if there's no correct cursor position before the `pos`, we try to find it after the `pos` var forwardSelection = _state.Selection.findFrom(resolvedPos, 1, true); return backwardSelection ? backwardSelection.from : forwardSelection ? forwardSelection.from : pos; } return endOfDocPos; }; var PluginState = exports.PluginState = /*#__PURE__*/function () { function PluginState(decorations, participants, sessionId) { var collabInitalised = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; var onError = arguments.length > 4 ? arguments[4] : undefined; var nudgeAnimations = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : new Map(); (0, _classCallCheck2.default)(this, PluginState); // eslint-disable-next-line no-console (0, _defineProperty2.default)(this, "onError", function (error) { return console.error(error); }); this.decorationSet = decorations; this.participants = participants; this.sid = sessionId; this.isReady = collabInitalised; this.onError = onError || this.onError; this.nudgeAnimations = nudgeAnimations; } return (0, _createClass2.default)(PluginState, [{ key: "decorations", get: function get() { return this.decorationSet; } }, { key: "activeParticipants", get: function get() { return this.participants; } }, { key: "sessionId", get: function get() { return this.sid; } }, { key: "getFullName", value: function getFullName(sessionId) { var participant = this.participants.get(sessionId); return participant ? participant.name : 'X'; } }, { key: "getInitial", value: function getInitial(sessionId) { return this.getFullName(sessionId).substring(0, 1).toUpperCase(); } }, { key: "getPresenceId", value: function getPresenceId(sessionId) { var _participant$presence; var participant = this.participants.get(sessionId); return (_participant$presence = participant === null || participant === void 0 ? void 0 : participant.presenceId) !== null && _participant$presence !== void 0 ? _participant$presence : sessionId; } }, { key: "apply", value: function apply(tr) { var _this = this; // Ignored via go/ees005 // eslint-disable-next-line prefer-const var participants = this.participants, sid = this.sid, isReady = this.isReady; var presenceData = tr.getMeta('presence'); var telepointerData = tr.getMeta('telepointer'); var nudgeTelepointerData = tr.getMeta('nudgeTelepointer'); var sessionIdData = tr.getMeta('sessionId'); var collabInitialised = tr.getMeta('collabInitialised'); if (typeof collabInitialised !== 'boolean') { collabInitialised = isReady; } if (sessionIdData) { sid = sessionIdData.sid; } var add = []; var remove = []; if (presenceData) { var _presenceData$joined = presenceData.joined, joined = _presenceData$joined === void 0 ? [] : _presenceData$joined, _presenceData$left = presenceData.left, left = _presenceData$left === void 0 ? [] : _presenceData$left; participants = participants.remove(left.map(function (i) { return i.sessionId; })); participants = participants.add(joined); // Remove telepointers for users that left left.forEach(function (i) { var pointers = (0, _utils.findPointers)(_this.getPresenceId(i.sessionId), _this.decorationSet); if (pointers) { remove = remove.concat(pointers); } }); } if (telepointerData) { var sessionId = telepointerData.sessionId; if (participants.get(sessionId) && sessionId !== sid) { var oldPointers = (0, _utils.findPointers)(this.getPresenceId(sessionId), this.decorationSet); if (oldPointers) { remove = remove.concat(oldPointers); } var endOfDocPos = tr.doc.nodeSize - 2; var anchor = telepointerData.selection.anchor; var head = telepointerData.selection.head; var rawFrom = anchor < head ? anchor : head; var rawTo = anchor >= head ? anchor : head; if (rawFrom > endOfDocPos) { rawFrom = endOfDocPos; } if (rawTo > endOfDocPos) { rawTo = endOfDocPos; } var isSelection = rawTo - rawFrom > 0; var from = 1; var to = 1; try { from = getValidPos(tr, isSelection ? Math.max(rawFrom, 0) : rawFrom); to = isSelection ? getValidPos(tr, rawTo) : from; } catch (err) { this.onError(err); } add = add.concat((0, _utils.createTelepointers)(from, to, sessionId, isSelection, this.getInitial(sessionId), this.getPresenceId(sessionId), this.getFullName(sessionId), (0, _utils.hasExistingNudge)(sessionId, this.nudgeAnimations))); } } if (tr.docChanged) { // Adjust decoration positions to changes made by the transaction try { this.decorationSet = this.decorationSet.map(tr.mapping, tr.doc, { // Reapplies decorators those got removed by the state change onRemove: function onRemove(spec) { if (spec.pointer && spec.pointer.sessionId && spec.key === "telepointer-".concat(spec.pointer.sessionId)) { var step = tr.steps.filter(_utils.isReplaceStep)[0]; if (step) { var _spec$pointer = spec.pointer, _sessionId = _spec$pointer.sessionId, presenceId = _spec$pointer.presenceId; var _ref = step, size = _ref.slice.content.size, _from = _ref.from; var pos = getValidPos(tr, size ? Math.min(_from + size, tr.doc.nodeSize - 3) : Math.max(_from, 1)); add = add.concat((0, _utils.createTelepointers)(pos, pos, _sessionId, false, _this.getInitial(_sessionId), presenceId, _this.getFullName(_sessionId), (0, _utils.hasExistingNudge)(_sessionId, _this.nudgeAnimations))); } } } }); } catch (err) { this.onError(err); } } var selection = tr.selection; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any this.decorationSet.find().forEach(function (deco) { if (deco.type.toDOM) { var hasTelepointerDimClass = deco.type.toDOM.classList.contains(_collab.TELEPOINTER_DIM_CLASS); var browser = (0, _browser.getBrowserInfo)(); if (deco.from === selection.from && deco.to === selection.to) { if (!hasTelepointerDimClass) { deco.type.toDOM.classList.add(_collab.TELEPOINTER_DIM_CLASS); } // Browser condition here to fix ED-14722 where telepointer // decorations with side -1 in Firefox causes backspace issues. // This is likely caused by contenteditable quirks in Firefox if (!browser.gecko) { deco.type.side = -1; } } else { if (hasTelepointerDimClass) { deco.type.toDOM.classList.remove(_collab.TELEPOINTER_DIM_CLASS); } deco.type.side = 0; } } }); if (nudgeTelepointerData) { var nudgeSessionId = nudgeTelepointerData === null || nudgeTelepointerData === void 0 ? void 0 : nudgeTelepointerData.sessionId; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any this.decorationSet.find().forEach(function (deco) { var _deco$spec, _deco$spec2; if (deco.type.toDOM && participants.get(nudgeSessionId) && ((_deco$spec = deco.spec) === null || _deco$spec === void 0 || (_deco$spec = _deco$spec.pointer) === null || _deco$spec === void 0 ? void 0 : _deco$spec.sessionId) === nudgeSessionId && ((_deco$spec2 = deco.spec) === null || _deco$spec2 === void 0 ? void 0 : _deco$spec2.key) === "telepointer-".concat(nudgeSessionId)) { // Restart animation by removing and re-adding the class deco.type.toDOM.classList.remove(_collab.TELEPOINTER_PULSE_DURING_TR_CLASS); deco.type.toDOM.classList.remove(_collab.TELEPOINTER_PULSE_CLASS); void deco.type.toDOM.offsetWidth; // Force reflow deco.type.toDOM.classList.add(_collab.TELEPOINTER_PULSE_CLASS); _this.nudgeAnimations.set(nudgeSessionId, Date.now()); } }); } if (remove.length) { this.decorationSet = this.decorationSet.remove(remove); } if (add.length) { this.decorationSet = this.decorationSet.add(tr.doc, add); } // This piece needs to be after the decorationSet adjustments, // otherwise it's always one step behind where the cursor is if (telepointerData) { var _sessionId2 = telepointerData.sessionId; if (participants.get(_sessionId2)) { var positionForScroll = (0, _utils.getPositionOfTelepointer)(_sessionId2, this.decorationSet); if (positionForScroll) { participants = participants.updateCursorPos(_sessionId2, positionForScroll); } } } var nextState = new PluginState(this.decorationSet, participants, sid, collabInitialised, this.onError, this.nudgeAnimations); return PluginState.eq(nextState, this) ? this : nextState; } }], [{ key: "eq", value: function eq(a, b) { return a.participants === b.participants && a.sessionId === b.sessionId && a.isReady === b.isReady; } // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any }, { key: "init", value: function init(config) { var doc = config.doc, onError = config.onError; return new PluginState(_view.DecorationSet.create(doc, []), new _participants.Participants(), undefined, undefined, onError); } }]); }();