UNPKG

@atlaskit/editor-plugin-tasks-and-decisions

Version:

Tasks and decisions plugin for @atlaskit/editor-core

349 lines (338 loc) 17.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.createPlugin = createPlugin; var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _adfSchema = require("@atlaskit/adf-schema"); var _steps = require("@atlaskit/adf-schema/steps"); var _safePlugin = require("@atlaskit/editor-common/safe-plugin"); var _selection = require("@atlaskit/editor-common/selection"); var _utils = require("@atlaskit/editor-common/utils"); var _state = require("@atlaskit/editor-prosemirror/state"); var _view = require("@atlaskit/editor-prosemirror/view"); var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals"); var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure"); var _DecisionItemNodeView = require("../nodeviews/DecisionItemNodeView"); var _taskNodeView = require("../nodeviews/task-node-view"); var _actions = require("./actions"); var _helpers = require("./helpers"); var _pluginKey = require("./plugin-key"); var _taskItemOnChange = require("./taskItemOnChange"); var _transforms = require("./transforms"); var _types = require("./types"); var _paste = require("./utils/paste"); var _excluded = ["localId"]; 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) { (0, _defineProperty2.default)(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; } function nodesBetweenChanged(tr, f, startPos) { var stepRange = (0, _utils.getStepRange)(tr); if (!stepRange) { return; } tr.doc.nodesBetween(stepRange.from, stepRange.to, f, startPos); } // eslint-disable-next-line jsdoc/require-jsdoc function createPlugin(portalProviderAPI, eventDispatcher, dispatch, api, getIntl) { var useLongPressSelection = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; var hasEditPermission = arguments.length > 6 ? arguments[6] : undefined; var hasRequestedEditPermission = arguments.length > 7 ? arguments[7] : undefined; var requestToEditContent = arguments.length > 8 ? arguments[8] : undefined; var taskPlaceholder = arguments.length > 9 ? arguments[9] : undefined; return new _safePlugin.SafePlugin({ props: { nodeViews: _objectSpread({ taskItem: (0, _taskNodeView.taskView)(api, getIntl(), taskPlaceholder), decisionItem: function (node) { return new _DecisionItemNodeView.DecisionItemNodeView(node, getIntl()); } }, (0, _expValEquals.expValEquals)('platform_editor_blocktaskitem_node_tenantid', 'isEnabled', true) ? { blockTaskItem: (0, _taskNodeView.taskView)(api, getIntl(), taskPlaceholder) } : {}), decorations: function decorations(state) { var pluginState = _pluginKey.stateKey.getState(state); if (pluginState !== null && pluginState !== void 0 && pluginState.decorations) { return pluginState.decorations; } return _view.DecorationSet.empty; }, handleTextInput: function handleTextInput(view, from, to, text) { // When a decision item is selected and the user starts typing, the entire node // should be replaced with what was just typed. This custom text input handler // is needed to implement that behaviour. var state = view.state, dispatch = view.dispatch; var tr = state.tr; if (state.selection instanceof _state.NodeSelection && state.selection.node.type === view.state.schema.nodes.decisionItem) { state.selection.replace(tr); tr.insertText(text); if (dispatch) { dispatch(tr); } return true; } return false; }, handleClickOn: (0, _selection.createSelectionClickHandler)(['decisionItem', 'taskItem'], function (target) { return target.hasAttribute('data-decision-wrapper') || target.getAttribute('aria-label') === 'Decision'; }, { useLongPressSelection: useLongPressSelection }), handleDOMEvents: { // When the page is lazy loaded and task item is not yet available this allows // our toDOM implementation to toggle the node state change: _taskItemOnChange.taskItemOnChange }, handleKeyDown: function handleKeyDown(view, event) { var state = view.state; var selection = state.selection, schema = state.schema; var $from = selection.$from, $to = selection.$to; var parentOffset = $from.parentOffset; var isInTaskItem = (0, _expValEquals.expValEquals)('platform_editor_blocktaskitem_patch_1', 'isEnabled', true) ? (0, _helpers.isInsideTask)(state) : $from.node().type === schema.nodes.taskItem; var focusedTaskItemLocalId = _pluginKey.stateKey.getState(state).focusedTaskItemLocalId; var currentTaskItemData = (0, _helpers.getTaskItemDataAtPos)(view); var currentTaskItemFocused = focusedTaskItemLocalId === (currentTaskItemData === null || currentTaskItemData === void 0 ? void 0 : currentTaskItemData.localId); // if task item checkbox not focused and arrow key is not pressed // then we don't want to handle event. if (!['ArrowUp', 'ArrowDown', 'ArrowRight', 'ArrowLeft'].includes(event.key)) { return false; } // We want to handle arrow up, down and left key only // when selection is inside task item and no text is selected. if (['ArrowUp', 'ArrowDown', 'ArrowLeft'].includes(event.key) && (!isInTaskItem || $from.pos !== $to.pos)) { return false; } // Arrow keys are pressed and shift, ctrl or meta is pressed as well. // along with arrow keys and task item checkbox is focused // then first move focus to view and proceed with default event handling. if (event.shiftKey || event.ctrlKey || event.metaKey) { currentTaskItemFocused && (0, _helpers.removeCheckboxFocus)(view); return false; } // task item checkbox is already focused if (focusedTaskItemLocalId) { if (event.key === 'ArrowLeft') { // Move focus to view and proceed with default keyboard handler. // Which will move cursor to previous position. (0, _helpers.removeCheckboxFocus)(view); return false; } if (event.key === 'ArrowRight') { // Move focus to view and DON'T proceed with default handler. // We have assumed that selection is already before first character of task item. (0, _helpers.removeCheckboxFocus)(view); return true; } if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { var taskItemData = (0, _helpers.getTaskItemDataToFocus)(view, event.key === 'ArrowUp' ? 'previous' : 'next'); if (taskItemData) { (0, _helpers.focusCheckboxAndUpdateSelection)(view, taskItemData); return true; } else { // If any how checkbox input not found, then move focus to view // and proceed with default keyboard handler. (0, _helpers.removeCheckboxFocus)(view); return false; } } } // If left arrow key is pressed and cursor is at first position in task-item // then focus checkbox and DON'T proceed with default keyboard handler if (event.key === 'ArrowLeft' && parentOffset === 0) { // here we are not using focusCheckboxAndUpdateSelection() method // because it is working incorretly when we are placing is inside the nested items (0, _helpers.focusCheckbox)(view, currentTaskItemData); return true; } if (event.key === 'ArrowRight') { var _$from$nodeAfter; // If gap cursor is just before task list then focus first task item in list. if (selection instanceof _selection.GapCursorSelection && selection.side === 'left' && ((_$from$nodeAfter = $from.nodeAfter) === null || _$from$nodeAfter === void 0 ? void 0 : _$from$nodeAfter.type) === schema.nodes.taskList) { var taskList = $from.nodeAfter; var firstTaskItemNode = taskList.child(0); var taskItemPos = $from.pos + 1; (0, _helpers.focusCheckboxAndUpdateSelection)(view, { pos: taskItemPos, localId: firstTaskItemNode.attrs.localId }); return true; } // if cursor is at then end of task item text then focus next task item checkbox else if (isInTaskItem && $from.node().content.size === parentOffset) { var nextTaskItemData = (0, _helpers.getTaskItemDataToFocus)(view, 'next'); if (nextTaskItemData) { (0, _helpers.focusCheckboxAndUpdateSelection)(view, nextTaskItemData); return true; } } else { return false; } } }, transformPasted: function transformPasted(slice, view) { var _view$state; if (Boolean(view === null || view === void 0 || (_view$state = view.state) === null || _view$state === void 0 || (_view$state = _view$state.schema) === null || _view$state === void 0 || (_view$state = _view$state.nodes) === null || _view$state === void 0 ? void 0 : _view$state.blockTaskItem)) { slice = (0, _paste.tempTransformSliceToRemoveBlockTaskItem)(slice, view); } return slice; } }, state: { init: function init() { return { insideTaskDecisionItem: false, hasEditPermission: hasEditPermission, hasRequestedEditPermission: hasRequestedEditPermission, requestToEditContent: requestToEditContent, focusedTaskItemLocalId: null, taskDecisionProvider: undefined, decorations: _view.DecorationSet.empty }; }, apply: function apply(tr, pluginState) { var metaData = tr.getMeta(_pluginKey.stateKey); var _ref = metaData !== null && metaData !== void 0 ? metaData : { action: null, data: null }, action = _ref.action, data = _ref.data; var newPluginState = pluginState; // Actions switch (action) { case _types.ACTIONS.FOCUS_BY_LOCALID: newPluginState = (0, _actions.focusTaskDecision)(newPluginState, { action: _types.ACTIONS.FOCUS_BY_LOCALID, data: data }); break; case _types.ACTIONS.SET_PROVIDER: newPluginState = (0, _actions.setProvider)(newPluginState, { action: _types.ACTIONS.SET_PROVIDER, data: data }); break; case _types.ACTIONS.OPEN_REQUEST_TO_EDIT_POPUP: newPluginState = (0, _actions.openRequestEditPopup)(newPluginState, { action: _types.ACTIONS.OPEN_REQUEST_TO_EDIT_POPUP, data: data }); break; } // Commands if (metaData && 'hasEditPermission' in metaData) { newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, { hasEditPermission: metaData.hasEditPermission }); } if (metaData && 'hasRequestedEditPermission' in metaData) { newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, { hasRequestedEditPermission: metaData.hasRequestedEditPermission }); } var taskItemInfo = tr.getMeta('taskItemInfo'); var decorations = _view.DecorationSet.empty; if (taskItemInfo) { decorations = _view.DecorationSet.create(tr.doc, [_view.Decoration.inline(taskItemInfo.from, taskItemInfo.to, {}, { dataTaskNodeCheckState: taskItemInfo.checkState })]); } else { var _newPluginState$decor; decorations = (_newPluginState$decor = newPluginState.decorations) === null || _newPluginState$decor === void 0 ? void 0 : _newPluginState$decor.map(tr.mapping, tr.doc); } var newState = _objectSpread(_objectSpread({}, newPluginState), {}, { decorations: decorations }); dispatch(_pluginKey.stateKey, newState); return newState; } }, key: _pluginKey.stateKey, /* * After each transaction, we search through the document for any decisionList/Item & taskList/Item nodes * that do not have the localId attribute set and generate a random UUID to use. This is to replace a previous * Prosemirror capability where node attributes could be generated dynamically. * See https://discuss.prosemirror.net/t/release-0-23-0-possibly-to-be-1-0-0/959/17 for a discussion of this approach. * * Note: we currently do not handle the edge case where two nodes may have the same localId */ appendTransaction: function appendTransaction(transactions, _oldState, newState) { // Normalise taskList structure first — runs on its own transaction (without addToHistory: false) // so it is included in history and correctly undone with the triggering operation. if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_flexible_list_schema', 'isEnabled', true) && transactions.some(function (t) { return t.docChanged; })) { var normTr = (0, _transforms.applyTaskListNormalisationFixes)({ tr: newState.tr, transactions: transactions, doc: newState.doc, schema: newState.schema }); if (normTr.docChanged) { // Return the normalisation transaction — the next appendTransaction call will // assign any missing localIds to newly inserted nodes via the localId path below. return normTr; } } // Assign unique localIds to any new nodes that don't have one. // Runs with addToHistory: false so localId assignment is not part of the undo history. var tr = newState.tr; var modified = false; transactions.forEach(function (transaction) { if (!transaction.docChanged) { return; } // Adds a unique id to a node nodesBetweenChanged(transaction, function (node, pos) { var _newState$schema$node = newState.schema.nodes, decisionList = _newState$schema$node.decisionList, decisionItem = _newState$schema$node.decisionItem, taskList = _newState$schema$node.taskList, taskItem = _newState$schema$node.taskItem, blockTaskItem = _newState$schema$node.blockTaskItem; if (!!node.type && (node.type === decisionList || node.type === decisionItem || node.type === taskList || node.type === taskItem || blockTaskItem && node.type === blockTaskItem)) { var _node$attrs = node.attrs, localId = _node$attrs.localId, rest = (0, _objectWithoutProperties2.default)(_node$attrs, _excluded); if (localId === undefined || localId === null || localId === '') { tr.step(new _steps.SetAttrsStep(pos, _objectSpread({ localId: _adfSchema.uuid.generate() }, rest))); modified = true; } } }); }); if (modified) { return tr.setMeta('addToHistory', false); } return; }, view: function view() { return { update: function update(view, prevState) { var pluginState = _pluginKey.stateKey.getState(view.state); var prevPluginState = _pluginKey.stateKey.getState(prevState); if (pluginState.focusedTaskItemLocalId === prevPluginState.focusedTaskItemLocalId) { return; } var taskItem = (0, _helpers.getTaskItemDataAtPos)(view); if (!taskItem) { return; } if (pluginState.focusedTaskItemLocalId === taskItem.localId) { var taskElement = view.nodeDOM(taskItem.pos); if (taskElement instanceof HTMLElement) { var _taskElement$querySel; taskElement === null || taskElement === void 0 || (_taskElement$querySel = taskElement.querySelector('input')) === null || _taskElement$querySel === void 0 || _taskElement$querySel.focus(); } } } }; } }); }