UNPKG

@atlaskit/editor-plugin-placeholder-text

Version:

placeholder text plugin for @atlaskit/editor-core

276 lines (275 loc) 11.5 kB
import React from 'react'; import { placeholder } from '@atlaskit/adf-schema'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks'; import { toolbarInsertBlockMessages as messages } from '@atlaskit/editor-common/messages'; import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { isNodeEmpty } from '@atlaskit/editor-common/utils'; import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state'; import TextIcon from '@atlaskit/icon/core/text'; import { hidePlaceholderFloatingToolbar, insertPlaceholderTextAtSelection, showPlaceholderFloatingToolbar } from './editor-actions/actions'; import { drawFakeTextCursor, FakeTextCursorSelection } from './pm-plugins/fake-text-cursor/cursor'; import { PlaceholderTextNodeView } from './pm-plugins/placeholder-text-nodeview'; import { pluginKey } from './pm-plugins/plugin-key'; import { isSelectionAtPlaceholder } from './pm-plugins/utils/selection-utils'; import PlaceholderFloatingToolbar from './ui/PlaceholderFloatingToolbar'; var getOpenTypeAhead = function getOpenTypeAhead(trigger, api) { var _api$typeAhead, _api$typeAhead2; var typeAheadHandler = api === null || api === void 0 || (_api$typeAhead = api.typeAhead) === null || _api$typeAhead === void 0 || (_api$typeAhead = _api$typeAhead.actions) === null || _api$typeAhead === void 0 ? void 0 : _api$typeAhead.findHandlerByTrigger(trigger); if (!typeAheadHandler || !typeAheadHandler.id) { return null; } return api === null || api === void 0 || (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 || (_api$typeAhead2 = _api$typeAhead2.actions) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.openAtTransaction({ triggerHandler: typeAheadHandler, inputMethod: INPUT_METHOD.KEYBOARD }); }; export function createPlugin(dispatch, options, api) { var allowInserting = !!options.allowInserting; return new SafePlugin({ key: pluginKey, state: { init: function init() { return { showInsertPanelAt: null, allowInserting: allowInserting }; }, apply: function apply(tr, state) { var meta = tr.getMeta(pluginKey); if (meta && meta.showInsertPanelAt !== undefined) { var newState = { showInsertPanelAt: meta.showInsertPanelAt, allowInserting: allowInserting }; dispatch(pluginKey, newState); return newState; } else if (state.showInsertPanelAt) { var _newState = { showInsertPanelAt: tr.mapping.map(state.showInsertPanelAt), allowInserting: allowInserting }; dispatch(pluginKey, _newState); return _newState; } return state; } }, appendTransaction: function appendTransaction(transactions, oldState, newState) { if (transactions.some(function (txn) { return txn.docChanged; })) { var didPlaceholderExistBeforeTxn = oldState.selection.$head.nodeAfter === newState.selection.$head.nodeAfter; var adjacentNode = newState.selection.$head.nodeAfter; var adjacentNodePos = newState.selection.$head.pos; var placeholderNodeType = newState.schema.nodes.placeholder; if (adjacentNode && adjacentNode.type === placeholderNodeType && didPlaceholderExistBeforeTxn) { var _$newHead$nodeBefore; var $newHead = newState.selection.$head; var $oldHead = oldState.selection.$head; // Check that cursor has moved forward in the document **and** that there is content before the cursor var cursorMoved = $oldHead.pos < $newHead.pos; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion var nodeBeforeHasContent = !isNodeEmpty($newHead.nodeBefore); var nodeBeforeIsInline = (_$newHead$nodeBefore = $newHead.nodeBefore) === null || _$newHead$nodeBefore === void 0 ? void 0 : _$newHead$nodeBefore.type.isInline; if (cursorMoved && (nodeBeforeHasContent || nodeBeforeIsInline)) { var _NodeSelection$create = NodeSelection.create(newState.doc, adjacentNodePos), $from = _NodeSelection$create.$from, $to = _NodeSelection$create.$to; return newState.tr.deleteRange($from.pos, $to.pos); } } } // Handle Fake Text Cursor for Floating Toolbar if (!pluginKey.getState(oldState).showInsertPanelAt && pluginKey.getState(newState).showInsertPanelAt) { return newState.tr.setSelection(new FakeTextCursorSelection(newState.selection.$from)); } if (pluginKey.getState(oldState).showInsertPanelAt && !pluginKey.getState(newState).showInsertPanelAt) { if (newState.selection instanceof FakeTextCursorSelection) { return newState.tr.setSelection(new TextSelection(newState.selection.$from)); } } return; }, props: { decorations: drawFakeTextCursor, handleDOMEvents: { beforeinput: function beforeinput(view, event) { var state = view.state; if (event instanceof InputEvent && !event.isComposing && event.inputType === 'insertText' && isSelectionAtPlaceholder(view.state.selection)) { event.stopPropagation(); event.preventDefault(); var startNodePosition = state.selection.from; var content = event.data || ''; var tr = view.state.tr; tr.delete(startNodePosition, startNodePosition + 1); var openTypeAhead = getOpenTypeAhead(content, api); if (openTypeAhead) { openTypeAhead(tr); } else { tr.insertText(content); } view.dispatch(tr); return true; } return false; } }, nodeViews: { placeholder: function placeholder(node, view, getPos) { return new PlaceholderTextNodeView(node, view, getPos); } } } }); } function ContentComponent(_ref) { var editorView = _ref.editorView, dependencyApi = _ref.dependencyApi, popupsMountPoint = _ref.popupsMountPoint, popupsBoundariesElement = _ref.popupsBoundariesElement; var _useSharedPluginState = useSharedPluginStateWithSelector(dependencyApi, ['placeholderText'], function (states) { var _states$placeholderTe; return { showInsertPanelAt: (_states$placeholderTe = states.placeholderTextState) === null || _states$placeholderTe === void 0 ? void 0 : _states$placeholderTe.showInsertPanelAt }; }), showInsertPanelAt = _useSharedPluginState.showInsertPanelAt; var insertPlaceholderText = function insertPlaceholderText(value) { return insertPlaceholderTextAtSelection(value)(editorView.state, editorView.dispatch); }; var hidePlaceholderToolbar = function hidePlaceholderToolbar() { return hidePlaceholderFloatingToolbar(editorView.state, editorView.dispatch); }; var getNodeFromPos = function getNodeFromPos(pos) { return editorView.domAtPos(pos).node; }; var getFixedCoordinatesFromPos = function getFixedCoordinatesFromPos(pos) { return editorView.coordsAtPos(pos); }; var setFocusInEditor = function setFocusInEditor() { return editorView.focus(); }; if (showInsertPanelAt) { return /*#__PURE__*/React.createElement(PlaceholderFloatingToolbar // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting , { editorViewDOM: editorView.dom, popupsMountPoint: popupsMountPoint, popupsBoundariesElement: popupsBoundariesElement, getFixedCoordinatesFromPos: getFixedCoordinatesFromPos, getNodeFromPos: getNodeFromPos, hidePlaceholderFloatingToolbar: hidePlaceholderToolbar, showInsertPanelAt: showInsertPanelAt, insertPlaceholder: insertPlaceholderText, setFocusInEditor: setFocusInEditor }); } return null; } var basePlaceholderTextPlugin = function basePlaceholderTextPlugin(_ref2) { var api = _ref2.api, options = _ref2.config; return { name: 'placeholderText', nodes: function nodes() { return [{ name: 'placeholder', node: placeholder }]; }, pmPlugins: function pmPlugins() { return [{ name: 'placeholderText', plugin: function plugin(_ref3) { var dispatch = _ref3.dispatch; return createPlugin(dispatch, options, api); } }]; }, actions: { showPlaceholderFloatingToolbar: showPlaceholderFloatingToolbar }, getSharedState: function getSharedState(editorState) { if (!editorState) { return undefined; } var _ref4 = pluginKey.getState(editorState) || { showInsertPanelAt: null }, showInsertPanelAt = _ref4.showInsertPanelAt, allowInserting = _ref4.allowInserting; return { showInsertPanelAt: showInsertPanelAt, allowInserting: !!allowInserting }; }, contentComponent: function contentComponent(_ref5) { var editorView = _ref5.editorView, popupsMountPoint = _ref5.popupsMountPoint, popupsBoundariesElement = _ref5.popupsBoundariesElement; if (!editorView) { return null; } return /*#__PURE__*/React.createElement(ContentComponent, { editorView: editorView, popupsMountPoint: popupsMountPoint, popupsBoundariesElement: popupsBoundariesElement, dependencyApi: api }); } }; }; var decorateWithPluginOptions = function decorateWithPluginOptions(plugin, options, api) { if (!options.allowInserting) { return plugin; } plugin.pluginsOptions = { quickInsert: function quickInsert(_ref6) { var formatMessage = _ref6.formatMessage; return [{ id: 'placeholderText', title: formatMessage(messages.placeholderText), description: formatMessage(messages.placeholderTextDescription), priority: 1400, keywords: ['placeholder'], icon: function icon() { return /*#__PURE__*/React.createElement(TextIcon, { label: "" }); }, action: function action(insert, state) { var _api$analytics; var tr = state.tr; tr.setMeta(pluginKey, { showInsertPanelAt: tr.selection.anchor }); var resolvedInputMethod = INPUT_METHOD.QUICK_INSERT; api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.attachAnalyticsEvent({ action: ACTION.INSERTED, actionSubject: ACTION_SUBJECT.DOCUMENT, actionSubjectId: ACTION_SUBJECT_ID.PLACEHOLDER_TEXT, attributes: { inputMethod: resolvedInputMethod }, eventType: EVENT_TYPE.TRACK })(tr); return tr; } }]; } }; return plugin; }; var placeholderTextPlugin = function placeholderTextPlugin(_ref7) { var _ref7$config = _ref7.config, options = _ref7$config === void 0 ? {} : _ref7$config, api = _ref7.api; return decorateWithPluginOptions(basePlaceholderTextPlugin({ config: options, api: api }), options, api); }; export default placeholderTextPlugin;