@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
336 lines (325 loc) • 19.3 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createPlugin = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _analytics = require("@atlaskit/editor-common/analytics");
var _browser = require("@atlaskit/editor-common/browser");
var _coreUtils = require("@atlaskit/editor-common/core-utils");
var _nesting = require("@atlaskit/editor-common/nesting");
var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
var _transforms = require("@atlaskit/editor-common/transforms");
var _utils = require("@atlaskit/editor-common/utils");
var _utils2 = require("@atlaskit/editor-prosemirror/utils");
var _editorTables = require("@atlaskit/editor-tables");
var _utils3 = require("@atlaskit/editor-tables/utils");
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
var _tableNodeViews = require("../nodeviews/table-node-views");
var _plugin = require("../pm-plugins/decorations/plugin");
var _types = require("../types");
var _eventHandlers = require("../ui/event-handlers");
var _commands = require("./commands");
var _columnResize = require("./commands/column-resize");
var _misc = require("./commands/misc");
var _defaultTableSelection = require("./default-table-selection");
var _pluginFactory = require("./plugin-factory");
var _pluginKey = require("./plugin-key");
var _fixTables = require("./transforms/fix-tables");
var _replaceTable = require("./transforms/replace-table");
var _decoration = require("./utils/decoration");
var _paste = require("./utils/paste");
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; }
var createPlugin = exports.createPlugin = function createPlugin(dispatchAnalyticsEvent, dispatch, portalProviderAPI, nodeViewPortalProviderAPI, eventDispatcher, pluginConfig, getEditorContainerWidth, getEditorFeatureFlags, getIntl, fullWidthModeEnabled, previousFullWidthModeEnabled, dragAndDropEnabled, editorAnalyticsAPI, pluginInjectionApi, isTableScalingEnabled, shouldUseIncreasedScalingPercent, isCommentEditor, isChromelessEditor, allowFixedColumnWidthOption) {
var _accessibilityUtils;
var state = (0, _pluginFactory.createPluginState)(dispatch, _objectSpread(_objectSpread(_objectSpread({
pluginConfig: pluginConfig,
isTableHovered: false,
insertColumnButtonIndex: undefined,
insertRowButtonIndex: undefined,
isFullWidthModeEnabled: fullWidthModeEnabled,
wasFullWidthModeEnabled: previousFullWidthModeEnabled,
isHeaderRowEnabled: !!pluginConfig.allowHeaderRow,
isHeaderColumnEnabled: false,
isDragAndDropEnabled: dragAndDropEnabled,
isTableScalingEnabled: isTableScalingEnabled
}, _defaultTableSelection.defaultHoveredCell), _defaultTableSelection.defaultTableSelection), {}, {
getIntl: getIntl
}));
// Used to prevent invalid table cell spans being reported more than once per editor/document
var invalidTableIds = [];
var editorViewRef = null;
var ariaNotifyPlugin = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_accessibilityUtils = pluginInjectionApi.accessibilityUtils) === null || _accessibilityUtils === void 0 ? void 0 : _accessibilityUtils.actions.ariaNotify;
var getCurrentEditorState = function getCurrentEditorState() {
var editorView = editorViewRef;
if (!editorView) {
return null;
}
return editorView.state;
};
var getNodeView = function getNodeView() {
return {
table: (0, _tableNodeViews.tableView)({
portalProviderAPI: portalProviderAPI,
eventDispatcher: eventDispatcher,
getEditorContainerWidth: getEditorContainerWidth,
getEditorFeatureFlags: getEditorFeatureFlags,
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
pluginInjectionApi: pluginInjectionApi,
isCommentEditor: isCommentEditor,
isChromelessEditor: isChromelessEditor,
allowFixedColumnWidthOption: allowFixedColumnWidthOption
}),
tableRow: (0, _tableNodeViews.tableRowView)({
eventDispatcher: eventDispatcher,
pluginInjectionApi: pluginInjectionApi,
isDragAndDropEnabled: dragAndDropEnabled
}),
tableCell: (0, _tableNodeViews.tableCellView)({
eventDispatcher: eventDispatcher,
pluginInjectionApi: pluginInjectionApi
}),
tableHeader: (0, _tableNodeViews.tableHeaderView)({
eventDispatcher: eventDispatcher,
pluginInjectionApi: pluginInjectionApi
})
};
};
var nodeViews = getNodeView();
return new _safePlugin.SafePlugin({
state: state,
key: _pluginKey.pluginKey,
appendTransaction: function appendTransaction(transactions, oldState, newState) {
var tr = transactions.find(function (tr) {
return tr.getMeta('uiEvent') === 'cut';
});
function reportInvalidTableCellSpanAttrs(invalidNodeAttr) {
if (invalidTableIds.find(function (id) {
return id === invalidNodeAttr.tableLocalId;
})) {
return;
}
invalidTableIds.push(invalidNodeAttr.tableLocalId);
dispatchAnalyticsEvent({
action: _analytics.ACTION.INVALID_DOCUMENT_ENCOUNTERED,
actionSubject: _analytics.ACTION_SUBJECT.EDITOR,
eventType: _analytics.EVENT_TYPE.OPERATIONAL,
attributes: {
nodeType: invalidNodeAttr.nodeType,
reason: "".concat(invalidNodeAttr.attribute, ": ").concat(invalidNodeAttr.reason),
tableLocalId: invalidNodeAttr.tableLocalId,
spanValue: invalidNodeAttr.spanValue
}
});
}
if (tr) {
var _getEditorFeatureFlag = getEditorFeatureFlags(),
_getEditorFeatureFlag2 = _getEditorFeatureFlag.tableWithFixedColumnWidthsOption,
tableWithFixedColumnWidthsOption = _getEditorFeatureFlag2 === void 0 ? false : _getEditorFeatureFlag2;
// "fixTables" removes empty rows as we don't allow that in schema
var updatedTr = (0, _eventHandlers.handleCut)(tr, oldState, newState, pluginInjectionApi, editorAnalyticsAPI, editorViewRef || undefined, isTableScalingEnabled, tableWithFixedColumnWidthsOption, shouldUseIncreasedScalingPercent);
return (0, _fixTables.fixTables)(updatedTr) || updatedTr;
}
if (transactions.find(function (tr) {
return tr.docChanged;
})) {
return (0, _fixTables.fixTables)(newState.tr, reportInvalidTableCellSpanAttrs);
}
},
view: function view(editorView) {
var domAtPos = editorView.domAtPos.bind(editorView);
editorViewRef = editorView;
return {
update: function update(view, prevState) {
var state = view.state,
dispatch = view.dispatch;
var selection = state.selection;
var pluginState = (0, _pluginFactory.getPluginState)(state);
var tableRef;
if ((0, _platformFeatureFlags.fg)('platform_editor_enable_table_dnd')) {
var _pluginInjectionApi$e;
var parent = (0, _utils2.findParentDomRefOfType)(state.schema.nodes.table, domAtPos)(selection);
var shouldSetTableRef = (0, _platformFeatureFlags.fg)('platform_editor_enable_table_dnd_patch_1') ? parent && (pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e = pluginInjectionApi.editorViewMode) === null || _pluginInjectionApi$e === void 0 || (_pluginInjectionApi$e = _pluginInjectionApi$e.sharedState.currentState()) === null || _pluginInjectionApi$e === void 0 ? void 0 : _pluginInjectionApi$e.mode) !== 'view' : parent;
if ((0, _expValEquals.expValEquals)('platform_editor_table_update_table_ref', 'isEnabled', true) && (0, _platformFeatureFlags.fg)('platform_editor_update_table_ref_fix')) {
var _pluginInjectionApi$e2, _pluginInjectionApi$i;
shouldSetTableRef = parent && (pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e2 = pluginInjectionApi.editorViewMode) === null || _pluginInjectionApi$e2 === void 0 || (_pluginInjectionApi$e2 = _pluginInjectionApi$e2.sharedState.currentState()) === null || _pluginInjectionApi$e2 === void 0 ? void 0 : _pluginInjectionApi$e2.mode) !== 'view' && (pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$i = pluginInjectionApi.interaction) === null || _pluginInjectionApi$i === void 0 || (_pluginInjectionApi$i = _pluginInjectionApi$i.sharedState.currentState()) === null || _pluginInjectionApi$i === void 0 ? void 0 : _pluginInjectionApi$i.interactionState) !== 'hasNotHadInteraction';
}
if (shouldSetTableRef) {
tableRef =
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
parent.querySelector('table') || undefined;
}
}
if (pluginState.editorHasFocus) {
if (!(0, _platformFeatureFlags.fg)('platform_editor_enable_table_dnd')) {
var _parent = (0, _utils2.findParentDomRefOfType)(state.schema.nodes.table, domAtPos)(selection);
if (_parent) {
tableRef =
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
_parent.querySelector('table') || undefined;
}
}
var tableNode = (0, _utils3.findTable)(state.selection);
// when keyboard cursor leaves the table we need to stop column resizing
var pluginPrevState = (0, _pluginFactory.getPluginState)(prevState);
var isStopKeyboardColumResizing = pluginPrevState.isResizeHandleWidgetAdded && pluginPrevState.isKeyboardResize;
if (isStopKeyboardColumResizing) {
var isTableNodesDifferent = (pluginPrevState === null || pluginPrevState === void 0 ? void 0 : pluginPrevState.tableNode) !== (tableNode === null || tableNode === void 0 ? void 0 : tableNode.node);
if (pluginPrevState !== null && pluginPrevState !== void 0 && pluginPrevState.tableNode && tableNode && isTableNodesDifferent) {
var oldRowsNumber = _editorTables.TableMap.get(pluginPrevState.tableNode).height;
var newRowsNumber = _editorTables.TableMap.get(tableNode.node).height;
if (oldRowsNumber !== newRowsNumber ||
// Add/delete row
tableNode.node.attrs.localId !== pluginPrevState.tableNode.attrs.localId) {
// Jump to another table
(0, _columnResize.stopKeyboardColumnResizing)({
ariaNotify: ariaNotifyPlugin,
getIntl: getIntl
})(state, dispatch);
}
} else if (!tableNode) {
// selection outside of table
(0, _columnResize.stopKeyboardColumnResizing)({
ariaNotify: ariaNotifyPlugin,
getIntl: getIntl
})(state, dispatch);
}
}
}
if (pluginState.tableRef !== tableRef) {
(0, _commands.setTableRef)(tableRef)(state, dispatch);
}
if (pluginState.editorHasFocus && pluginState.tableRef) {
var _ref = state.selection,
$cursor = _ref.$cursor;
if ($cursor) {
// Only update bold when it's a cursor
var tableCellHeader = (0, _utils2.findParentNodeOfType)(state.schema.nodes.tableHeader)(state.selection);
if (tableCellHeader) {
(0, _commands.addBoldInEmptyHeaderCells)(tableCellHeader)(state, dispatch);
}
}
} else if (pluginState.isResizeHandleWidgetAdded) {
(0, _misc.removeResizeHandleDecorations)()(state, dispatch);
}
}
};
},
props: {
transformPasted: function transformPasted(slice) {
var editorState = getCurrentEditorState();
if (!editorState) {
return slice;
}
var schema = editorState.schema;
// if we're pasting to outside a table or outside a table
// header, ensure that we apply any table headers to the first
// row of content we see, if required
if (!(0, _coreUtils.insideTable)(editorState) && (0, _paste.isHeaderRowRequired)(editorState)) {
slice = (0, _misc.transformSliceToAddTableHeaders)(slice, schema);
}
// This fixes pasting a table with default layout into comment editor
// table lose width and expand to full width
if (!(0, _coreUtils.insideTable)(editorState) && isCommentEditor && pluginConfig.allowTableAlignment && isTableScalingEnabled) {
slice = (0, _paste.transformSliceTableLayoutDefaultToCenter)(slice, schema);
}
slice = (0, _paste.transformSliceToFixHardBreakProblemOnCopyFromCell)(slice, schema);
// We do this separately, so it also applies to drag/drop events
// This needs to go before `transformSliceToRemoveOpenExpand`
slice = (0, _transforms.transformSliceToRemoveOpenLayoutNodes)(slice, schema);
// If a partial paste of expand, paste only the content
// This needs to go before `transformSliceToRemoveOpenTable`
slice = (0, _transforms.transformSliceToRemoveOpenExpand)(slice, schema);
// transformSliceToRemoveOpenTable() transforms based on the depth of the root node, assuming that the tables will be at the root
// Bodied extensions will contribute to the depth of the table selection so we need to remove them first
/** If a partial paste of bodied extension, paste only text */
slice = (0, _transforms.transformSliceToRemoveOpenBodiedExtension)(slice, schema);
/** If a partial paste of table, paste only table's content */
slice = (0, _paste.transformSliceToRemoveOpenTable)(slice, schema);
/** If a partial paste of multi bodied extension, paste only children */
slice = (0, _transforms.transformSliceToRemoveOpenMultiBodiedExtension)(slice, schema);
slice = (0, _paste.transformSliceToCorrectEmptyTableCells)(slice, schema);
if (!pluginConfig.allowColumnResizing) {
slice = (0, _misc.transformSliceToRemoveColumnsWidths)(slice, schema);
}
// If we don't allow background on cells, we need to remove it
// from the paste slice
if (!pluginConfig.allowBackgroundColor) {
slice = (0, _misc.transformSliceRemoveCellBackgroundColor)(slice, schema);
} else {
slice = (0, _misc.transformSliceToFixDarkModeDefaultBackgroundColor)(slice, schema);
}
slice = (0, _transforms.transformSliceToRemoveOpenNestedExpand)(slice, schema);
if ((0, _nesting.isNestedTablesSupported)(schema)) {
slice = (0, _paste.transformSliceToRemoveNestedTables)(slice, schema, editorState.selection);
}
return slice;
},
handleClick: function handleClick(_ref2, _pos, event) {
var state = _ref2.state,
dispatch = _ref2.dispatch;
var decorationSet = _plugin.pluginKey.getState(state);
var browser = (0, _expValEquals.expValEquals)('platform_editor_hydratable_ui', 'isEnabled', true) ? (0, _browser.getBrowserInfo)() : _browser.browser;
if ((0, _decoration.findControlsHoverDecoration)(decorationSet).length) {
(0, _commands.clearHoverSelection)()(state, dispatch);
}
// ED-6069: workaround for Chrome given a regression introduced in prosemirror-view@1.6.8
// Returning true prevents that updateSelection() is getting called in the commit below:
// @see https://github.com/ProseMirror/prosemirror-view/commit/33fe4a8b01584f6b4103c279033dcd33e8047b95
if (browser.chrome && event.target) {
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
var targetClassList = event.target.classList;
if (targetClassList.contains(_types.TableCssClassName.CONTROLS_BUTTON) || targetClassList.contains(_types.TableCssClassName.CONTEXTUAL_MENU_BUTTON) || targetClassList.contains(_types.TableCssClassName.DRAG_HANDLE_BUTTON_CLICKABLE_ZONE) || targetClassList.contains(_types.TableCssClassName.DRAG_HANDLE_BUTTON_CONTAINER)) {
return true;
}
}
return false;
},
handleScrollToSelection: function handleScrollToSelection(view) {
// when typing into a sticky header cell, we don't want to scroll
// back to the top of the table if the user has already scrolled down
var tableHeader = view.state.schema.nodes.tableHeader;
var domRef = (0, _utils2.findParentDomRefOfType)(tableHeader, view.domAtPos.bind(view))(view.state.selection);
var maybeTr = (0, _utils.closestElement)(domRef, 'tr');
return maybeTr ? maybeTr.classList.contains('sticky') : false;
},
handleTextInput: function handleTextInput(view, _from, _to, text) {
var state = view.state,
dispatch = view.dispatch;
var _getPluginState = (0, _pluginFactory.getPluginState)(state),
isKeyboardResize = _getPluginState.isKeyboardResize;
if (isKeyboardResize) {
(0, _columnResize.stopKeyboardColumnResizing)({
ariaNotify: ariaNotifyPlugin,
getIntl: getIntl
})(state, dispatch);
return false;
}
var tr = (0, _replaceTable.replaceSelectedTable)(state, text, _analytics.INPUT_METHOD.KEYBOARD, editorAnalyticsAPI);
if (tr.selectionSet) {
dispatch(tr);
return true;
}
return false;
},
nodeViews: nodeViews,
handleDOMEvents: {
focus: _eventHandlers.handleFocus,
blur: _eventHandlers.handleBlur,
mousedown: (0, _eventHandlers.withCellTracking)(_eventHandlers.handleMouseDown),
mouseleave: _eventHandlers.handleMouseLeave,
mousemove: (0, _eventHandlers.whenTableInFocus)((0, _eventHandlers.handleMouseMove)(nodeViewPortalProviderAPI), pluginInjectionApi),
mouseenter: _eventHandlers.handleMouseEnter,
mouseup: (0, _eventHandlers.whenTableInFocus)(_eventHandlers.handleMouseUp),
click: (0, _eventHandlers.withCellTracking)((0, _eventHandlers.whenTableInFocus)(_eventHandlers.handleClick))
},
handleTripleClick: _eventHandlers.handleTripleClick
}
});
};