@atlaskit/editor-plugin-placeholder
Version:
Placeholder plugin for @atlaskit/editor-core.
174 lines (171 loc) • 10.9 kB
JavaScript
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;
}
};
}
});
}