UNPKG

@atlaskit/editor-plugin-quick-insert

Version:

Quick insert plugin for @atlaskit/editor-core

191 lines (190 loc) • 7.92 kB
/* BlockControlQuickInsertButton.tsx generated by @compiled/babel-plugin v3.0.2 */ import "./BlockControlQuickInsertButton.compiled.css"; import { ax, ix } from "@compiled/react/runtime"; import React, { useCallback } from 'react'; import { useIntl } from 'react-intl'; import { ToolTipContent } from '@atlaskit/editor-common/keymaps'; import { messages } from '@atlaskit/editor-common/messages/block-controls'; import { isEmptyNode } from '@atlaskit/editor-common/utils'; import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state'; import { findParentNode, findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils'; import { CellSelection } from '@atlaskit/editor-tables/cell-selection'; import AddIcon from '@atlaskit/icon/core/add'; import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled'; import { Pressable } from '@atlaskit/primitives/compiled/pressable'; import Tooltip from '@atlaskit/tooltip/Tooltip'; const TEXT_PARENT_TYPES = ['paragraph', 'heading', 'blockquote', 'taskItem', 'decisionItem']; // @atlaskit/css intentionally restricts height and width values, so Compiled owns only the // dimensions that scale at runtime with the editor's dense-mode font-size custom property. const quickInsertButtonContainerStyles = { root: "_1e0c1txw _4t3i9xsi _1bsb9xsi", reservedDragHandleSpace: "_12l21xcw" }; const quickInsertButtonStyles = { root: "_19itglyw _2rko1rr0 _v564k6bl _1rjcze3t _18zrze3t _4cvr1h6o _bfhksm61 _vchhusvi _syazazsu _80omtlke _1e0c1txw _4t3i1osq _1bah1h6o _1bsb1osq _mizu1v1w _1ah3v77o _ra3xnqa1 _128mdkaa _irr31dpa _1ggl5xtf _1di6fcek" }; const createNewLine = start => ({ tr }) => { var _tr$doc$nodeAt; const nodeSize = (_tr$doc$nodeAt = tr.doc.nodeAt(start)) === null || _tr$doc$nodeAt === void 0 ? void 0 : _tr$doc$nodeAt.nodeSize; if (nodeSize === undefined) { return tr; } const position = start + nodeSize; tr.insert(position, tr.doc.type.schema.nodes.paragraph.create()); return tr.setSelection(TextSelection.near(tr.doc.resolve(position))); }; const isSelectionInNode = (start, view) => { const node = view.state.doc.nodeAt(start); if (!node) { return false; } const { $from, $to } = view.state.selection; return $from.pos >= start && $to.pos <= start + node.nodeSize; }; const isNonEditableBlock = (start, view) => { const node = view.state.doc.nodeAt(start); if (!node) { return false; } if (node.type.name === 'mediaGroup' || node.type.name === 'mediaSingle') { return true; } return node.isBlock && (node.isAtom || node.isLeaf); }; const getNextEmptyParagraphPosition = (start, view) => { const node = view.state.doc.nodeAt(start); if (!node) { return undefined; } const positionAfterTarget = start + node.nodeSize; const nextNode = view.state.doc.resolve(positionAfterTarget).nodeAfter; if ((nextNode === null || nextNode === void 0 ? void 0 : nextNode.type) !== view.state.schema.nodes.paragraph || nextNode.marks.length > 0) { return undefined; } if (!isEmptyNode(view.state.schema)(nextNode)) { return undefined; } return positionAfterTarget + 1; }; const openQuickInsert = (api, view, start, openTypeAhead) => { const isSelectionInsideNode = isSelectionInNode(start, view); const shouldCreateNewLine = !isSelectionInsideNode || isNonEditableBlock(start, view); const reusableParagraphPosition = shouldCreateNewLine && isExperimentEnabled('platform_editor_block_control_inp') ? getNextEmptyParagraphPosition(start, view) : undefined; if (shouldCreateNewLine && reusableParagraphPosition === undefined) { api.core.actions.execute(createNewLine(start)); } else if (reusableParagraphPosition !== undefined && // Only skip when the cursor is already collapsed in the reused paragraph. A range selection // that merely starts there must still be collapsed, otherwise opening the typeahead deletes // the selected content (openTypeAheadAtCursor calls deleteSelection for non-cursor ranges). !(view.state.selection.empty && view.state.selection.from === reusableParagraphPosition)) { api.core.actions.execute(({ tr }) => { tr.setSelection(TextSelection.create(tr.doc, reusableParagraphPosition)); return tr; }); } const { codeBlock } = view.state.schema.nodes; const { selection } = view.state; const codeBlockParentNode = findParentNodeOfType(codeBlock)(selection); if (codeBlockParentNode) { api.core.actions.execute(createNewLine(codeBlockParentNode.pos)); } else if (isSelectionInsideNode && reusableParagraphPosition === undefined) { const currentSelection = view.state.selection; if ((currentSelection instanceof TextSelection || currentSelection instanceof NodeSelection && currentSelection.node.isInline) && currentSelection.from !== currentSelection.to) { const currentParagraphNode = findParentNode(node => TEXT_PARENT_TYPES.includes(node.type.name))(currentSelection); if (currentParagraphNode) { const newPos = currentSelection.anchor === currentSelection.to ? currentParagraphNode.pos : currentParagraphNode.pos + currentParagraphNode.node.nodeSize - 1; api.core.actions.execute(({ tr }) => { tr.setSelection(TextSelection.create(view.state.selection.$from.doc, newPos)); return tr; }); } } if (currentSelection instanceof NodeSelection && currentSelection.$from.depth > 0) { if (isNonEditableBlock(view.state.selection.from, view)) { api.core.actions.execute(createNewLine(view.state.selection.from)); } else { api.core.actions.execute(({ tr }) => { createNewLine(view.state.selection.from)({ tr }); tr.setSelection(TextSelection.create(tr.doc, view.state.selection.from)); return tr; }); } } if (currentSelection instanceof CellSelection) { const lastInlinePosition = TextSelection.near(view.state.selection.$to, -1); if (lastInlinePosition) { api.core.actions.execute(({ tr }) => { if (!(lastInlinePosition instanceof TextSelection)) { createNewLine(lastInlinePosition.from)({ tr }); tr.setSelection(TextSelection.create(tr.doc, lastInlinePosition.to)); } else { tr.setSelection(lastInlinePosition); } return tr; }); } } } openTypeAhead(); }; export const BlockControlQuickInsertButton = ({ api, openTypeAhead, reserveDragHandleSpace = false, start, view }) => { const { formatMessage } = useIntl(); const label = formatMessage(messages.insert); const handleMouseDown = useCallback(() => { var _api$typeAhead; if ((_api$typeAhead = api.typeAhead) !== null && _api$typeAhead !== void 0 && _api$typeAhead.actions.isOpen(view.state)) { api.typeAhead.actions.close({ insertCurrentQueryAsRawText: false }); } }, [api, view]); const handleClick = useCallback(() => openQuickInsert(api, view, start, openTypeAhead), [api, openTypeAhead, start, view]); return /*#__PURE__*/React.createElement(Tooltip, { position: "top", content: /*#__PURE__*/React.createElement(ToolTipContent, { description: label }) }, /*#__PURE__*/React.createElement("span", { className: ax([quickInsertButtonContainerStyles.root, reserveDragHandleSpace && quickInsertButtonContainerStyles.reservedDragHandleSpace]) }, /*#__PURE__*/React.createElement(Pressable, { "aria-label": label, onClick: handleClick, onMouseDown: handleMouseDown, testId: "editor-quick-insert-button", type: "button", xcss: quickInsertButtonStyles.root }, /*#__PURE__*/React.createElement(AddIcon, { label: "", color: "var(--ds-icon-subtle, #505258)", size: "small" })))); };