UNPKG

@atlaskit/editor-plugin-placeholder

Version:

Placeholder plugin for @atlaskit/editor-core.

174 lines (171 loc) 10.9 kB
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { expVal } from '@atlaskit/tmp-editor-statsig/expVal'; import { EMPTY_PARAGRAPH_TIMEOUT_DELAY, pluginKey } from '../placeholderPlugin'; import { TYPEWRITER_TYPED_AND_DELETED_DELAY } from './constants'; import { createPlaceholderDecoration } from './decorations'; import { calculateUserInteractionState, createPlaceHolderStateFrom, getPlaceholderState } from './utils'; export default function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, emptyLinePlaceholder, placeholderPrompts, withEmptyParagraph, initialIsPlaceholderHidden, placeholderADF, isRovoLLMEnabled, api) { if (!defaultPlaceholderText && !placeholderPrompts && !bracketPlaceholderText && !placeholderADF) { return; } var isDestroyed = false; var activeTypewriterTimeouts = []; var clearAllTypewriterTimeouts = function clearAllTypewriterTimeouts() { activeTypewriterTimeouts.forEach(function (clearFn) { return clearFn(); }); activeTypewriterTimeouts = []; }; return new SafePlugin({ key: pluginKey, state: { init: function init(_, state) { var _api$focus, _api$typeAhead; return createPlaceHolderStateFrom({ isInitial: true, isEditorFocused: Boolean(api === null || api === void 0 || (_api$focus = api.focus) === null || _api$focus === void 0 || (_api$focus = _api$focus.sharedState.currentState()) === null || _api$focus === void 0 ? void 0 : _api$focus.hasFocus), editorState: state, isTypeAheadOpen: api === null || api === void 0 || (_api$typeAhead = api.typeAhead) === null || _api$typeAhead === void 0 ? void 0 : _api$typeAhead.actions.isOpen, defaultPlaceholderText: defaultPlaceholderText, bracketPlaceholderText: bracketPlaceholderText, emptyLinePlaceholder: emptyLinePlaceholder, placeholderADF: placeholderADF, placeholderPrompts: placeholderPrompts, typedAndDeleted: false, userHadTyped: false, intl: intl, withEmptyParagraph: withEmptyParagraph, isPlaceholderHidden: initialIsPlaceholderHidden }); }, apply: function apply(tr, placeholderState, _oldEditorState, newEditorState) { var _api$focus2, _placeholderState$isP, _api$typeAhead2, _ref, _meta$placeholderText, _ref2, _meta$placeholderProm, _meta$showOnEmptyPara; var meta = tr.getMeta(pluginKey); var isEditorFocused = Boolean(api === null || api === void 0 || (_api$focus2 = api.focus) === null || _api$focus2 === void 0 || (_api$focus2 = _api$focus2.sharedState.currentState()) === null || _api$focus2 === void 0 ? void 0 : _api$focus2.hasFocus); var _calculateUserInterac = calculateUserInteractionState({ placeholderState: placeholderState, oldEditorState: _oldEditorState, newEditorState: newEditorState }), userHadTyped = _calculateUserInterac.userHadTyped, typedAndDeleted = _calculateUserInterac.typedAndDeleted; var isPlaceholderHidden = (_placeholderState$isP = placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.isPlaceholderHidden) !== null && _placeholderState$isP !== void 0 ? _placeholderState$isP : false; var shouldUpdatePlaceholderHidden = (meta === null || meta === void 0 ? void 0 : meta.isPlaceholderHidden) !== undefined; if (shouldUpdatePlaceholderHidden) { isPlaceholderHidden = meta.isPlaceholderHidden; } if ((meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== undefined && withEmptyParagraph) { var isCreateWithRovoOverride = !!meta.placeholderText && isRovoLLMEnabled && expVal('cwr_blank_object_experiment', 'isEnabled', false); // Only update defaultPlaceholderText from meta if we're not using ADF placeholder // OR when the create-with-rovo experiment is active to allow intentional non-empty placeholder overrides if (!placeholderADF || isCreateWithRovoOverride) { defaultPlaceholderText = meta.placeholderText; } } var newPlaceholderState = createPlaceHolderStateFrom({ isEditorFocused: isEditorFocused, editorState: newEditorState, isTypeAheadOpen: api === null || api === void 0 || (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.actions.isOpen, defaultPlaceholderText: withEmptyParagraph ? defaultPlaceholderText : (_ref = (_meta$placeholderText = meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== null && _meta$placeholderText !== void 0 ? _meta$placeholderText : placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderText) !== null && _ref !== void 0 ? _ref : defaultPlaceholderText, bracketPlaceholderText: bracketPlaceholderText, emptyLinePlaceholder: emptyLinePlaceholder, placeholderADF: placeholderADF, placeholderPrompts: (_ref2 = (_meta$placeholderProm = meta === null || meta === void 0 ? void 0 : meta.placeholderPrompts) !== null && _meta$placeholderProm !== void 0 ? _meta$placeholderProm : placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderPrompts) !== null && _ref2 !== void 0 ? _ref2 : placeholderPrompts, typedAndDeleted: typedAndDeleted, userHadTyped: userHadTyped, intl: intl, isPlaceholderHidden: isPlaceholderHidden, withEmptyParagraph: withEmptyParagraph, showOnEmptyParagraph: (_meta$showOnEmptyPara = meta === null || meta === void 0 ? void 0 : meta.showOnEmptyParagraph) !== null && _meta$showOnEmptyPara !== void 0 ? _meta$showOnEmptyPara : placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.showOnEmptyParagraph }); // Clear timeouts when hasPlaceholder becomes false if (!newPlaceholderState.hasPlaceholder) { clearAllTypewriterTimeouts(); } return newPlaceholderState; } }, props: { decorations: function decorations(editorState) { var _api$composition, _api$showDiff; if (expValEquals('confluence_load_editor_title_on_transition', 'contentPlaceholder', true)) { // @ts-ignore quick fix which needs follow up to use standard apis if (editorState.collabEditPlugin$ && editorState.collabEditPlugin$.isReady !== true) { return; } } var _getPlaceholderState = getPlaceholderState(editorState), hasPlaceholder = _getPlaceholderState.hasPlaceholder, placeholderText = _getPlaceholderState.placeholderText, pos = _getPlaceholderState.pos, typedAndDeleted = _getPlaceholderState.typedAndDeleted, contextPlaceholderADF = _getPlaceholderState.contextPlaceholderADF, showOnEmptyParagraph = _getPlaceholderState.showOnEmptyParagraph; // Decorations is still called after plugin is destroyed // So we need to make sure decorations is not called if plugin has been destroyed to prevent the placeholder animations' setTimeouts called infinitely if (isDestroyed) { return; } var compositionPluginState = api === null || api === void 0 || (_api$composition = api.composition) === null || _api$composition === void 0 ? void 0 : _api$composition.sharedState.currentState(); var isShowingDiff = Boolean(api === null || api === void 0 || (_api$showDiff = api.showDiff) === null || _api$showDiff === void 0 || (_api$showDiff = _api$showDiff.sharedState.currentState()) === null || _api$showDiff === void 0 ? void 0 : _api$showDiff.isDisplayingChanges); if (hasPlaceholder && ((placeholderText !== null && placeholderText !== void 0 ? placeholderText : '') || placeholderPrompts || placeholderADF || contextPlaceholderADF) && pos !== undefined && !(compositionPluginState !== null && compositionPluginState !== void 0 && compositionPluginState.isComposing) && !isShowingDiff) { var initialDelayWhenUserTypedAndDeleted = typedAndDeleted ? TYPEWRITER_TYPED_AND_DELETED_DELAY : 0; // contextPlaceholderADF takes precedence over the global placeholderADF var placeholderAdfToUse = contextPlaceholderADF || placeholderADF; return createPlaceholderDecoration(editorState, placeholderText !== null && placeholderText !== void 0 ? placeholderText : '', placeholderPrompts, activeTypewriterTimeouts, pos, initialDelayWhenUserTypedAndDeleted, placeholderAdfToUse, showOnEmptyParagraph); } return; } }, view: function view() { var timeoutId; function startEmptyParagraphTimeout(editorView) { if (timeoutId) { return; } timeoutId = setTimeout(function () { timeoutId = undefined; editorView.dispatch(editorView.state.tr.setMeta(pluginKey, { showOnEmptyParagraph: true })); }, EMPTY_PARAGRAPH_TIMEOUT_DELAY); } function destroyEmptyParagraphTimeout() { if (timeoutId) { clearTimeout(timeoutId); timeoutId = undefined; } } return { update: function update(editorView, prevState) { if (withEmptyParagraph) { var prevPluginState = getPlaceholderState(prevState); var newPluginState = getPlaceholderState(editorView.state); // user start typing after move to an empty paragraph, clear timeout if (!newPluginState.canShowOnEmptyParagraph && timeoutId) { destroyEmptyParagraphTimeout(); } // user move to an empty paragraph again, reset state to hide placeholder, and restart timeout else if (prevPluginState.canShowOnEmptyParagraph && newPluginState.canShowOnEmptyParagraph && newPluginState.pos !== prevPluginState.pos) { editorView.dispatch(editorView.state.tr.setMeta(pluginKey, { showOnEmptyParagraph: false })); destroyEmptyParagraphTimeout(); startEmptyParagraphTimeout(editorView); } // user move to an empty paragraph (by click enter or move to an empty paragraph), start timeout else if (!prevPluginState.canShowOnEmptyParagraph && newPluginState.canShowOnEmptyParagraph && !newPluginState.showOnEmptyParagraph && !timeoutId) { startEmptyParagraphTimeout(editorView); } } }, destroy: function destroy() { clearAllTypewriterTimeouts(); destroyEmptyParagraphTimeout(); isDestroyed = true; } }; } }); }