UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

207 lines (201 loc) 8.88 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /** * A plugin for ensuring tables always have unique local IDs, and to * preserve/not regenerate them when they are being cut and paste around the * document. * * More broadly, this plugin should be generalised so it can solve this ‘unique * id’ problem across the codebase for any node, but for now this will live on * its own solving only for tables. * * TODO: https://product-fabric.atlassian.net/browse/ED-12714 * */ import rafSchedule from 'raf-schd'; import { uuid } from '@atlaskit/adf-schema'; import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { stepHasSlice } from '@atlaskit/editor-common/utils'; import { PluginKey } from '@atlaskit/editor-prosemirror/state'; var pluginKey = new PluginKey('tableLocalIdPlugin'); var getPluginState = function getPluginState(state) { return state && pluginKey.getState(state); }; /** * Ensures uniqueness of `localId`s on tables being created or edited */ var createPlugin = function createPlugin(dispatch) { return new SafePlugin({ key: pluginKey, state: { init: function init() { return { parsedForLocalIds: false }; }, apply: function apply(tr, pluginState) { var meta = tr.getMeta(pluginKey); if (meta) { var keys = Object.keys(meta); var changed = keys.some(function (key) { return pluginState[key] !== meta[key]; }); if (changed) { var newState = _objectSpread(_objectSpread({}, pluginState), meta); dispatch(pluginKey, newState); return newState; } } return pluginState; } }, view: function view() { return { /** * Utilise the update cycle for _one_ scan through an initial doc * to ensure existing tables without IDs get them when this plugin is * enabled. * * This entire block can be skipped if we simply remove the `checkIsAddingTable` * check in appendTransaction, but that comes with 2 cons: * * 1. require a transaction before we actually add the local IDs * 2. ever slightly more unncessary checks * * Finally, this never happens in practice when utilising this in * confluence, as the collab/synchrony initialisation process will * trigger a transaction which adds tables, and thus this plugin will * add/dedupe the necessary IDs. But general usage of the editor * without collab should still solve for IDs. */ update: function update(editorView) { var state = editorView.state; var pluginState = getPluginState(state); if (!pluginState) { return; } var parsed = pluginState.parsedForLocalIds; if (parsed) { return; } var table = state.schema.nodes.table; rafSchedule(function () { var tr = editorView.state.tr; var tableIdWasAdded = false; editorView.state.doc.descendants(function (node, pos) { var isTable = node.type === table; var localId = node.attrs.localId; if (isTable && !localId) { tableIdWasAdded = true; tr.setNodeMarkup(pos, undefined, _objectSpread(_objectSpread({}, node.attrs), {}, { localId: uuid.generate() })); return false; } /** * Otherwise continue traversing, we can encounter tables nested in * expands/bodiedExtensions */ return true; }); if (tableIdWasAdded) { tr.setMeta('addToHistory', false); editorView.dispatch(tr); } })(); editorView.dispatch(state.tr.setMeta(pluginKey, { parsedForLocalIds: true })); } }; }, appendTransaction: function appendTransaction(transactions, _oldState, newState) { var modified = false; var tr = newState.tr; var table = newState.schema.nodes.table; var addedTableNodes = new Set(); var addedTableNodePos = new Map(); var localIds = new Set(); transactions.forEach(function (transaction) { if (!transaction.docChanged) { return; } // Don't interfere with cut as it clashes with fixTables & we don't need // to handle any extra cut cases in this plugin var uiEvent = transaction.getMeta('uiEvent'); if (uiEvent === 'cut') { return; } /** Get the tables we are adding and their position */ var _iterator = _createForOfIteratorHelper(transaction.steps), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var step = _step.value; if (!stepHasSlice(step)) { continue; } step.slice.content.descendants(function (node) { if (node.type === table) { addedTableNodes.add(node); } return true; }); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } }); if (!addedTableNodes.size) { return; } // Get the existing localIds on the page newState.doc.descendants(function (node, pos) { // Skip if this is position of added table if (addedTableNodes.has(node)) { addedTableNodePos.set(node, pos); return false; } if (node.type !== table) { return true; } localIds.add(node.attrs.localId); // can't have table within a table return false; }); // If the added nodes have duplicate id, generate a new one var _iterator2 = _createForOfIteratorHelper(addedTableNodes), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var node = _step2.value; if (!node.attrs.localId || localIds.has(node.attrs.localId)) { var pos = addedTableNodePos.get(node); if (pos === undefined) { continue; } tr.setNodeMarkup(pos, undefined, _objectSpread(_objectSpread({}, node.attrs), {}, { localId: uuid.generate() })); modified = true; } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } if (modified) { return tr; } return; } }); }; export { createPlugin };