@atlaskit/editor-plugin-placeholder
Version:
Placeholder plugin for @atlaskit/editor-core.
243 lines • 8.49 kB
JavaScript
import { placeholderTextMessages as messages } from '@atlaskit/editor-common/messages';
import { bracketTyped, hasDocAsParent, isEmptyDocument, isEmptyParagraph } from '@atlaskit/editor-common/utils';
import { findParentNode } from '@atlaskit/editor-prosemirror/utils';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { pluginKey } from '../placeholderPlugin';
import { createLongEmptyNodePlaceholderADF, createShortEmptyNodePlaceholderADF } from './adf-builders';
import { nodeTypesWithLongPlaceholderText, nodeTypesWithShortPlaceholderText, nodeTypesWithSyncBlockPlaceholderText } from './constants';
export function getPlaceholderState(editorState) {
return pluginKey.getState(editorState);
}
export function setPlaceHolderState({
placeholderText,
pos,
placeholderPrompts,
typedAndDeleted,
userHadTyped,
canShowOnEmptyParagraph,
showOnEmptyParagraph,
contextPlaceholderADF
}) {
return {
hasPlaceholder: true,
placeholderText,
placeholderPrompts,
contextPlaceholderADF,
pos: pos ? pos : 1,
typedAndDeleted,
userHadTyped,
canShowOnEmptyParagraph,
showOnEmptyParagraph
};
}
export const emptyPlaceholder = ({
placeholderText,
placeholderPrompts,
userHadTyped,
pos,
canShowOnEmptyParagraph,
showOnEmptyParagraph
}) => ({
hasPlaceholder: false,
placeholderText,
placeholderPrompts,
userHadTyped,
typedAndDeleted: false,
canShowOnEmptyParagraph,
showOnEmptyParagraph,
pos
});
export function createPlaceHolderStateFrom({
isInitial,
isEditorFocused,
editorState,
isTypeAheadOpen,
defaultPlaceholderText,
intl,
bracketPlaceholderText,
emptyLinePlaceholder,
placeholderADF,
placeholderPrompts,
typedAndDeleted,
userHadTyped,
isPlaceholderHidden,
withEmptyParagraph,
showOnEmptyParagraph
}) {
const shouldHidePlaceholder = isPlaceholderHidden;
if (shouldHidePlaceholder) {
return {
...emptyPlaceholder({
placeholderText: defaultPlaceholderText,
placeholderPrompts,
userHadTyped
}),
isPlaceholderHidden
};
}
if (isTypeAheadOpen !== null && isTypeAheadOpen !== void 0 && isTypeAheadOpen(editorState)) {
return emptyPlaceholder({
placeholderText: defaultPlaceholderText,
placeholderPrompts,
userHadTyped
});
}
if ((defaultPlaceholderText || placeholderPrompts || placeholderADF) && isEmptyDocument(editorState.doc)) {
return setPlaceHolderState({
placeholderText: defaultPlaceholderText,
pos: 1,
placeholderPrompts,
typedAndDeleted,
userHadTyped
});
}
if (withEmptyParagraph) {
const {
from,
to,
$to
} = editorState.selection;
const isOnEmptyParagraphInNonEmptyDoc = (defaultPlaceholderText || placeholderADF) && withEmptyParagraph && !isInitial && !isEmptyDocument(editorState.doc) && from === to && isEmptyParagraph($to.parent) && hasDocAsParent($to);
if (isOnEmptyParagraphInNonEmptyDoc) {
// If placeholder was already shown, keep it visible even without focus
// This prevents the placeholder from disappearing when switching browser tabs
if (showOnEmptyParagraph) {
return setPlaceHolderState({
placeholderText: defaultPlaceholderText,
pos: to,
placeholderPrompts,
typedAndDeleted,
userHadTyped,
canShowOnEmptyParagraph: true,
showOnEmptyParagraph: true
});
}
// Focus is required to start the timeout for showing placeholder
if (isEditorFocused) {
return emptyPlaceholder({
placeholderText: defaultPlaceholderText,
placeholderPrompts,
userHadTyped,
canShowOnEmptyParagraph: true,
showOnEmptyParagraph: false,
pos: to
});
}
}
}
if (isEditorFocused && editorExperiment('platform_editor_controls', 'variant1')) {
var _parentNode$firstChil, _parentNode$firstChil2;
const {
$from,
$to
} = editorState.selection;
if ($from.pos !== $to.pos) {
return emptyPlaceholder({
placeholderText: defaultPlaceholderText,
placeholderPrompts,
userHadTyped
});
}
const parentNode = $from.node($from.depth - 1);
const parentType = parentNode === null || parentNode === void 0 ? void 0 : parentNode.type.name;
if (emptyLinePlaceholder && parentType === 'doc') {
const isEmptyLine = isEmptyParagraph($from.parent);
if (isEmptyLine) {
return setPlaceHolderState({
placeholderText: emptyLinePlaceholder,
pos: $from.pos,
placeholderPrompts,
typedAndDeleted,
userHadTyped
});
}
}
const isEmptyNode = (parentNode === null || parentNode === void 0 ? void 0 : parentNode.childCount) === 1 && ((_parentNode$firstChil = parentNode.firstChild) === null || _parentNode$firstChil === void 0 ? void 0 : _parentNode$firstChil.content.size) === 0 && ((_parentNode$firstChil2 = parentNode.firstChild) === null || _parentNode$firstChil2 === void 0 ? void 0 : _parentNode$firstChil2.type.name) === 'paragraph';
if (nodeTypesWithShortPlaceholderText.includes(parentType) && isEmptyNode) {
var _table$node$firstChil;
const table = findParentNode(node => node.type === editorState.schema.nodes.table)(editorState.selection);
if (!table) {
return emptyPlaceholder({
placeholderText: defaultPlaceholderText,
placeholderPrompts,
userHadTyped
});
}
const isFirstCell = (table === null || table === void 0 ? void 0 : (_table$node$firstChil = table.node.firstChild) === null || _table$node$firstChil === void 0 ? void 0 : _table$node$firstChil.content.firstChild) === parentNode;
if (isFirstCell) {
return setPlaceHolderState({
placeholderText: undefined,
contextPlaceholderADF: createShortEmptyNodePlaceholderADF(intl),
pos: $from.pos,
placeholderPrompts,
typedAndDeleted,
userHadTyped
});
}
}
if (nodeTypesWithLongPlaceholderText.includes(parentType) && isEmptyNode) {
return setPlaceHolderState({
placeholderText: undefined,
contextPlaceholderADF: createLongEmptyNodePlaceholderADF(intl),
pos: $from.pos,
placeholderPrompts,
typedAndDeleted,
userHadTyped
});
}
if (nodeTypesWithSyncBlockPlaceholderText.includes(parentType) && isEmptyNode && editorExperiment('platform_synced_block', true)) {
return setPlaceHolderState({
placeholderText: intl.formatMessage(messages.sourceSyncBlockPlaceholderText),
pos: $from.pos,
placeholderPrompts,
typedAndDeleted,
userHadTyped
});
}
return emptyPlaceholder({
placeholderText: defaultPlaceholderText,
placeholderPrompts,
userHadTyped
});
}
if (bracketPlaceholderText && bracketTyped(editorState) && isEditorFocused) {
const {
$from
} = editorState.selection;
// Space is to account for positioning of the bracket
const bracketHint = ' ' + bracketPlaceholderText;
return setPlaceHolderState({
placeholderText: bracketHint,
pos: $from.pos - 1,
placeholderPrompts,
typedAndDeleted,
userHadTyped
});
}
return emptyPlaceholder({
placeholderText: defaultPlaceholderText,
placeholderPrompts,
userHadTyped
});
}
export function calculateUserInteractionState({
placeholderState,
oldEditorState,
newEditorState
}) {
const wasEmpty = oldEditorState ? isEmptyDocument(oldEditorState.doc) : true;
const isEmpty = isEmptyDocument(newEditorState.doc);
const hasEverTyped = Boolean(placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.userHadTyped) ||
// Previously typed
!wasEmpty ||
// Had content before
wasEmpty && !isEmpty; // Just added content
const justDeletedAll = hasEverTyped && isEmpty && !wasEmpty;
const isInTypedAndDeletedState = justDeletedAll || Boolean(placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.typedAndDeleted) && isEmpty;
// Only reset user interaction tracking when editor is cleanly empty
const shouldResetInteraction = isEmpty && !isInTypedAndDeletedState;
return {
userHadTyped: shouldResetInteraction ? false : hasEverTyped,
typedAndDeleted: isInTypedAndDeletedState
};
}