@atlaskit/editor-plugin-placeholder 6.5.4 → 6.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/cjs/placeholderPlugin.js +19 -568
- package/dist/cjs/pm-plugins/adf-builders.js +30 -0
- package/dist/cjs/pm-plugins/animation.js +58 -0
- package/dist/cjs/pm-plugins/constants.js +18 -0
- package/dist/cjs/pm-plugins/decorations.js +96 -0
- package/dist/cjs/pm-plugins/main.js +168 -0
- package/dist/cjs/pm-plugins/placeholderPluginLegacy.js +617 -0
- package/dist/cjs/pm-plugins/types.js +5 -0
- package/dist/cjs/pm-plugins/utils.js +243 -0
- package/dist/es2019/placeholderPlugin.js +8 -548
- package/dist/es2019/pm-plugins/adf-builders.js +22 -0
- package/dist/es2019/pm-plugins/animation.js +49 -0
- package/dist/es2019/pm-plugins/constants.js +12 -0
- package/dist/es2019/pm-plugins/decorations.js +87 -0
- package/dist/es2019/pm-plugins/main.js +162 -0
- package/dist/es2019/pm-plugins/placeholderPluginLegacy.js +598 -0
- package/dist/es2019/pm-plugins/types.js +1 -0
- package/dist/es2019/pm-plugins/utils.js +234 -0
- package/dist/esm/placeholderPlugin.js +17 -563
- package/dist/esm/pm-plugins/adf-builders.js +24 -0
- package/dist/esm/pm-plugins/animation.js +52 -0
- package/dist/esm/pm-plugins/constants.js +12 -0
- package/dist/esm/pm-plugins/decorations.js +90 -0
- package/dist/esm/pm-plugins/main.js +162 -0
- package/dist/esm/pm-plugins/placeholderPluginLegacy.js +607 -0
- package/dist/esm/pm-plugins/types.js +1 -0
- package/dist/esm/pm-plugins/utils.js +232 -0
- package/dist/types/placeholderPlugin.d.ts +0 -8
- package/dist/types/pm-plugins/adf-builders.d.ts +4 -0
- package/dist/types/pm-plugins/animation.d.ts +1 -0
- package/dist/types/pm-plugins/constants.d.ts +9 -0
- package/dist/types/pm-plugins/decorations.d.ts +4 -0
- package/dist/types/pm-plugins/main.d.ts +6 -0
- package/dist/types/pm-plugins/placeholderPluginLegacy.d.ts +13 -0
- package/dist/types/pm-plugins/types.d.ts +37 -0
- package/dist/types/pm-plugins/utils.d.ts +27 -0
- package/dist/types-ts4.5/placeholderPlugin.d.ts +0 -8
- package/dist/types-ts4.5/pm-plugins/adf-builders.d.ts +4 -0
- package/dist/types-ts4.5/pm-plugins/animation.d.ts +1 -0
- package/dist/types-ts4.5/pm-plugins/constants.d.ts +9 -0
- package/dist/types-ts4.5/pm-plugins/decorations.d.ts +4 -0
- package/dist/types-ts4.5/pm-plugins/main.d.ts +6 -0
- package/dist/types-ts4.5/pm-plugins/placeholderPluginLegacy.d.ts +13 -0
- package/dist/types-ts4.5/pm-plugins/types.d.ts +37 -0
- package/dist/types-ts4.5/pm-plugins/utils.d.ts +27 -0
- package/package.json +6 -9
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import { code, text } from '@atlaskit/adf-utils/builders';
|
|
2
|
+
import { placeholderTextMessages as messages } from '@atlaskit/editor-common/messages';
|
|
3
|
+
import { processRawValue } from '@atlaskit/editor-common/process-raw-value';
|
|
4
|
+
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
5
|
+
import { bracketTyped, browser, hasDocAsParent, isEmptyDocument, isEmptyParagraph } from '@atlaskit/editor-common/utils';
|
|
6
|
+
import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
|
|
7
|
+
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
8
|
+
import { findParentNode } from '@atlaskit/editor-prosemirror/utils';
|
|
9
|
+
import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
10
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
11
|
+
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
12
|
+
// Typewriter animation timing constants
|
|
13
|
+
const TYPEWRITER_TYPE_DELAY = 50; // Delay between typing each character
|
|
14
|
+
const TYPEWRITER_PAUSE_BEFORE_ERASE = 2000; // Pause before starting to erase text
|
|
15
|
+
const TYPEWRITER_ERASE_DELAY = 40; // Delay between erasing each character
|
|
16
|
+
const TYPEWRITER_CYCLE_DELAY = 500; // Delay before starting next cycle
|
|
17
|
+
const TYPEWRITER_TYPED_AND_DELETED_DELAY = 1500; // Delay before starting animation after user typed and deleted
|
|
18
|
+
|
|
19
|
+
export const EMPTY_PARAGRAPH_TIMEOUT_DELAY = 2000; // Delay before showing placeholder on empty paragraph
|
|
20
|
+
|
|
21
|
+
export const pluginKey = new PluginKey('placeholderPlugin');
|
|
22
|
+
const placeholderTestId = 'placeholder-test-id';
|
|
23
|
+
function getPlaceholderState(editorState) {
|
|
24
|
+
return pluginKey.getState(editorState);
|
|
25
|
+
}
|
|
26
|
+
const nodeTypesWithLongPlaceholderText = ['expand', 'panel'];
|
|
27
|
+
const nodeTypesWithShortPlaceholderText = ['tableCell', 'tableHeader'];
|
|
28
|
+
const nodeTypesWithSyncBlockPlaceholderText = ['bodiedSyncBlock'];
|
|
29
|
+
const createShortEmptyNodePlaceholderADF = ({
|
|
30
|
+
formatMessage
|
|
31
|
+
}) => ({
|
|
32
|
+
version: 1,
|
|
33
|
+
type: 'doc',
|
|
34
|
+
content: [{
|
|
35
|
+
type: 'paragraph',
|
|
36
|
+
content: [code(formatMessage(messages.shortEmptyNodePlaceholderADFSlashShortcut)), text(' '), text(formatMessage(messages.shortEmptyNodePlaceholderADFSuffix))]
|
|
37
|
+
}]
|
|
38
|
+
});
|
|
39
|
+
const createLongEmptyNodePlaceholderADF = ({
|
|
40
|
+
formatMessage
|
|
41
|
+
}) => ({
|
|
42
|
+
version: 1,
|
|
43
|
+
type: 'doc',
|
|
44
|
+
content: [{
|
|
45
|
+
type: 'paragraph',
|
|
46
|
+
content: [text(formatMessage(messages.longEmptyNodePlaceholderADFPrefix)), text(' '), code(formatMessage(messages.longEmptyNodePlaceholderADFSlashShortcut)), text(' '), text(formatMessage(messages.longEmptyNodePlaceholderADFSuffix))]
|
|
47
|
+
}]
|
|
48
|
+
});
|
|
49
|
+
const cycleThroughPlaceholderPrompts = (placeholderPrompts, activeTypewriterTimeouts, placeholderNodeWithText, initialDelayWhenUserTypedAndDeleted = 0) => {
|
|
50
|
+
let currentPromptIndex = 0;
|
|
51
|
+
let displayedText = '';
|
|
52
|
+
let animationTimeouts = [];
|
|
53
|
+
const clearAllTimeouts = () => {
|
|
54
|
+
animationTimeouts.forEach(timeoutId => clearTimeout(timeoutId));
|
|
55
|
+
animationTimeouts = [];
|
|
56
|
+
};
|
|
57
|
+
const scheduleTimeout = (callback, delay) => {
|
|
58
|
+
const timeoutId = setTimeout(callback, delay);
|
|
59
|
+
animationTimeouts.push(timeoutId);
|
|
60
|
+
return timeoutId;
|
|
61
|
+
};
|
|
62
|
+
const startAnimationCycle = () => {
|
|
63
|
+
const currentPrompt = placeholderPrompts[currentPromptIndex];
|
|
64
|
+
let characterIndex = 0;
|
|
65
|
+
const typeNextCharacter = () => {
|
|
66
|
+
if (characterIndex < currentPrompt.length) {
|
|
67
|
+
displayedText = currentPrompt.substring(0, characterIndex + 1);
|
|
68
|
+
placeholderNodeWithText.textContent = displayedText;
|
|
69
|
+
characterIndex++;
|
|
70
|
+
scheduleTimeout(typeNextCharacter, TYPEWRITER_TYPE_DELAY);
|
|
71
|
+
} else {
|
|
72
|
+
scheduleTimeout(eraseLastCharacter, TYPEWRITER_PAUSE_BEFORE_ERASE);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const eraseLastCharacter = () => {
|
|
76
|
+
if (displayedText.length > 1) {
|
|
77
|
+
displayedText = displayedText.substring(0, displayedText.length - 1);
|
|
78
|
+
placeholderNodeWithText.textContent = displayedText;
|
|
79
|
+
scheduleTimeout(eraseLastCharacter, TYPEWRITER_ERASE_DELAY);
|
|
80
|
+
} else {
|
|
81
|
+
displayedText = ' ';
|
|
82
|
+
placeholderNodeWithText.textContent = displayedText;
|
|
83
|
+
currentPromptIndex = (currentPromptIndex + 1) % placeholderPrompts.length;
|
|
84
|
+
scheduleTimeout(startAnimationCycle, TYPEWRITER_CYCLE_DELAY);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
typeNextCharacter();
|
|
88
|
+
};
|
|
89
|
+
activeTypewriterTimeouts === null || activeTypewriterTimeouts === void 0 ? void 0 : activeTypewriterTimeouts.push(clearAllTimeouts);
|
|
90
|
+
if (initialDelayWhenUserTypedAndDeleted > 0) {
|
|
91
|
+
placeholderNodeWithText.textContent = ' ';
|
|
92
|
+
scheduleTimeout(startAnimationCycle, initialDelayWhenUserTypedAndDeleted);
|
|
93
|
+
} else {
|
|
94
|
+
startAnimationCycle();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
export function createPlaceholderDecoration(editorState, placeholderText, placeholderPrompts, activeTypewriterTimeouts, pos = 1, initialDelayWhenUserTypedAndDeleted = 0, placeholderADF) {
|
|
98
|
+
const placeholderDecoration = document.createElement('span');
|
|
99
|
+
let placeholderNodeWithText = placeholderDecoration;
|
|
100
|
+
placeholderDecoration.setAttribute('data-testid', placeholderTestId);
|
|
101
|
+
placeholderDecoration.className = 'placeholder-decoration';
|
|
102
|
+
placeholderDecoration.setAttribute('aria-hidden', 'true');
|
|
103
|
+
|
|
104
|
+
// PM sets contenteditable to false on Decorations so Firefox doesn't display the flashing cursor
|
|
105
|
+
// So adding an extra span which will contain the placeholder text
|
|
106
|
+
if (browser.gecko) {
|
|
107
|
+
const placeholderNode = document.createElement('span');
|
|
108
|
+
placeholderNode.setAttribute('contenteditable', 'true'); // explicitly overriding the default Decoration behaviour
|
|
109
|
+
placeholderDecoration.appendChild(placeholderNode);
|
|
110
|
+
placeholderNodeWithText = placeholderNode;
|
|
111
|
+
}
|
|
112
|
+
if (placeholderText) {
|
|
113
|
+
placeholderNodeWithText.textContent = placeholderText || ' ';
|
|
114
|
+
} else if (placeholderADF) {
|
|
115
|
+
const serializer = DOMSerializer.fromSchema(editorState.schema);
|
|
116
|
+
// Get a PMNode from docnode
|
|
117
|
+
const docNode = processRawValue(editorState.schema, placeholderADF);
|
|
118
|
+
if (docNode) {
|
|
119
|
+
// Extract only the inline content from paragraphs, avoiding block-level elements
|
|
120
|
+
// that can interfere with cursor rendering
|
|
121
|
+
docNode.children.forEach(node => {
|
|
122
|
+
// For paragraph nodes, serialize their content (inline elements) directly
|
|
123
|
+
// without the wrapping <p> tag
|
|
124
|
+
if (node.type.name === 'paragraph') {
|
|
125
|
+
node.content.forEach(inlineNode => {
|
|
126
|
+
const inlineDOM = serializer.serializeNode(inlineNode);
|
|
127
|
+
placeholderNodeWithText.append(inlineDOM);
|
|
128
|
+
});
|
|
129
|
+
} else {
|
|
130
|
+
// For non-paragraph nodes, serialize normally
|
|
131
|
+
const nodeDOM = serializer.serializeNode(node);
|
|
132
|
+
placeholderNodeWithText.append(nodeDOM);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
const markElements = placeholderNodeWithText.querySelectorAll('[data-prosemirror-content-type="mark"]');
|
|
136
|
+
markElements.forEach(markEl => {
|
|
137
|
+
if (markEl instanceof HTMLElement) {
|
|
138
|
+
markEl.style.setProperty('color', "var(--ds-text-subtlest, #6B6E76)");
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
// Ensure all child elements don't block pointer events or cursor
|
|
142
|
+
const allElements = placeholderNodeWithText.querySelectorAll('*');
|
|
143
|
+
allElements.forEach(el => {
|
|
144
|
+
if (el instanceof HTMLElement) {
|
|
145
|
+
el.style.pointerEvents = 'none';
|
|
146
|
+
el.style.userSelect = 'none';
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} else if (placeholderPrompts) {
|
|
151
|
+
cycleThroughPlaceholderPrompts(placeholderPrompts, activeTypewriterTimeouts, placeholderNodeWithText, initialDelayWhenUserTypedAndDeleted);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ME-2289 Tapping on backspace in empty editor hides and displays the keyboard
|
|
155
|
+
// Add a editable buff node as the cursor moving forward is inevitable
|
|
156
|
+
// when backspace in GBoard composition
|
|
157
|
+
if (browser.android && browser.chrome) {
|
|
158
|
+
const buffNode = document.createElement('span');
|
|
159
|
+
buffNode.setAttribute('class', 'placeholder-android');
|
|
160
|
+
buffNode.setAttribute('contenteditable', 'true');
|
|
161
|
+
buffNode.textContent = ' ';
|
|
162
|
+
placeholderDecoration.appendChild(buffNode);
|
|
163
|
+
}
|
|
164
|
+
const isTargetNested = editorState.doc.resolve(pos).depth > 1;
|
|
165
|
+
|
|
166
|
+
// only truncate text for nested nodes, otherwise applying 'overflow: hidden;' to top level nodes
|
|
167
|
+
// creates issues with quick insert button
|
|
168
|
+
if (isTargetNested && editorExperiment('platform_editor_controls', 'variant1')) {
|
|
169
|
+
placeholderDecoration.classList.add('placeholder-decoration-hide-overflow');
|
|
170
|
+
}
|
|
171
|
+
return DecorationSet.create(editorState.doc, [Decoration.widget(pos, placeholderDecoration, {
|
|
172
|
+
side: 0,
|
|
173
|
+
key: `placeholder ${placeholderText}`
|
|
174
|
+
})]);
|
|
175
|
+
}
|
|
176
|
+
function setPlaceHolderState({
|
|
177
|
+
placeholderText,
|
|
178
|
+
pos,
|
|
179
|
+
placeholderPrompts,
|
|
180
|
+
typedAndDeleted,
|
|
181
|
+
userHadTyped,
|
|
182
|
+
canShowOnEmptyParagraph,
|
|
183
|
+
showOnEmptyParagraph,
|
|
184
|
+
contextPlaceholderADF
|
|
185
|
+
}) {
|
|
186
|
+
return {
|
|
187
|
+
hasPlaceholder: true,
|
|
188
|
+
placeholderText,
|
|
189
|
+
placeholderPrompts,
|
|
190
|
+
contextPlaceholderADF,
|
|
191
|
+
pos: pos ? pos : 1,
|
|
192
|
+
typedAndDeleted,
|
|
193
|
+
userHadTyped,
|
|
194
|
+
canShowOnEmptyParagraph,
|
|
195
|
+
showOnEmptyParagraph
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const emptyPlaceholder = ({
|
|
199
|
+
placeholderText,
|
|
200
|
+
placeholderPrompts,
|
|
201
|
+
userHadTyped,
|
|
202
|
+
pos,
|
|
203
|
+
canShowOnEmptyParagraph,
|
|
204
|
+
showOnEmptyParagraph
|
|
205
|
+
}) => ({
|
|
206
|
+
hasPlaceholder: false,
|
|
207
|
+
placeholderText,
|
|
208
|
+
placeholderPrompts,
|
|
209
|
+
userHadTyped,
|
|
210
|
+
typedAndDeleted: false,
|
|
211
|
+
canShowOnEmptyParagraph,
|
|
212
|
+
showOnEmptyParagraph,
|
|
213
|
+
pos
|
|
214
|
+
});
|
|
215
|
+
function createPlaceHolderStateFrom({
|
|
216
|
+
isInitial,
|
|
217
|
+
isEditorFocused,
|
|
218
|
+
editorState,
|
|
219
|
+
isTypeAheadOpen,
|
|
220
|
+
defaultPlaceholderText,
|
|
221
|
+
intl,
|
|
222
|
+
bracketPlaceholderText,
|
|
223
|
+
emptyLinePlaceholder,
|
|
224
|
+
placeholderADF,
|
|
225
|
+
placeholderPrompts,
|
|
226
|
+
typedAndDeleted,
|
|
227
|
+
userHadTyped,
|
|
228
|
+
isPlaceholderHidden,
|
|
229
|
+
withEmptyParagraph,
|
|
230
|
+
showOnEmptyParagraph
|
|
231
|
+
}) {
|
|
232
|
+
if (isPlaceholderHidden && withEmptyParagraph) {
|
|
233
|
+
return {
|
|
234
|
+
...emptyPlaceholder({
|
|
235
|
+
placeholderText: defaultPlaceholderText,
|
|
236
|
+
placeholderPrompts,
|
|
237
|
+
userHadTyped
|
|
238
|
+
}),
|
|
239
|
+
isPlaceholderHidden
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (isTypeAheadOpen !== null && isTypeAheadOpen !== void 0 && isTypeAheadOpen(editorState)) {
|
|
243
|
+
return emptyPlaceholder({
|
|
244
|
+
placeholderText: defaultPlaceholderText,
|
|
245
|
+
placeholderPrompts,
|
|
246
|
+
userHadTyped
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
if ((defaultPlaceholderText || placeholderPrompts || placeholderADF) && isEmptyDocument(editorState.doc)) {
|
|
250
|
+
return setPlaceHolderState({
|
|
251
|
+
placeholderText: defaultPlaceholderText,
|
|
252
|
+
pos: 1,
|
|
253
|
+
placeholderPrompts,
|
|
254
|
+
typedAndDeleted,
|
|
255
|
+
userHadTyped
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
if (withEmptyParagraph) {
|
|
259
|
+
const {
|
|
260
|
+
from,
|
|
261
|
+
to,
|
|
262
|
+
$to
|
|
263
|
+
} = editorState.selection;
|
|
264
|
+
if ((defaultPlaceholderText || placeholderADF) && withEmptyParagraph && isEditorFocused && !isInitial && !isEmptyDocument(editorState.doc) && from === to && isEmptyParagraph($to.parent) && hasDocAsParent($to)) {
|
|
265
|
+
return showOnEmptyParagraph ? setPlaceHolderState({
|
|
266
|
+
placeholderText: defaultPlaceholderText,
|
|
267
|
+
pos: to,
|
|
268
|
+
placeholderPrompts,
|
|
269
|
+
typedAndDeleted,
|
|
270
|
+
userHadTyped,
|
|
271
|
+
canShowOnEmptyParagraph: true,
|
|
272
|
+
showOnEmptyParagraph: true
|
|
273
|
+
}) : emptyPlaceholder({
|
|
274
|
+
placeholderText: defaultPlaceholderText,
|
|
275
|
+
placeholderPrompts,
|
|
276
|
+
userHadTyped,
|
|
277
|
+
canShowOnEmptyParagraph: true,
|
|
278
|
+
showOnEmptyParagraph: false,
|
|
279
|
+
pos: to
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (isEditorFocused && editorExperiment('platform_editor_controls', 'variant1')) {
|
|
284
|
+
var _parentNode$firstChil, _parentNode$firstChil2;
|
|
285
|
+
const {
|
|
286
|
+
$from,
|
|
287
|
+
$to
|
|
288
|
+
} = editorState.selection;
|
|
289
|
+
if ($from.pos !== $to.pos) {
|
|
290
|
+
return emptyPlaceholder({
|
|
291
|
+
placeholderText: defaultPlaceholderText,
|
|
292
|
+
placeholderPrompts,
|
|
293
|
+
userHadTyped
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
const parentNode = $from.node($from.depth - 1);
|
|
297
|
+
const parentType = parentNode === null || parentNode === void 0 ? void 0 : parentNode.type.name;
|
|
298
|
+
if (emptyLinePlaceholder && parentType === 'doc') {
|
|
299
|
+
const isEmptyLine = isEmptyParagraph($from.parent);
|
|
300
|
+
if (isEmptyLine) {
|
|
301
|
+
return setPlaceHolderState({
|
|
302
|
+
placeholderText: emptyLinePlaceholder,
|
|
303
|
+
pos: $from.pos,
|
|
304
|
+
placeholderPrompts,
|
|
305
|
+
typedAndDeleted,
|
|
306
|
+
userHadTyped
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
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';
|
|
311
|
+
if (nodeTypesWithShortPlaceholderText.includes(parentType) && isEmptyNode) {
|
|
312
|
+
var _table$node$firstChil;
|
|
313
|
+
const table = findParentNode(node => node.type === editorState.schema.nodes.table)(editorState.selection);
|
|
314
|
+
if (!table) {
|
|
315
|
+
return emptyPlaceholder({
|
|
316
|
+
placeholderText: defaultPlaceholderText,
|
|
317
|
+
placeholderPrompts,
|
|
318
|
+
userHadTyped
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
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;
|
|
322
|
+
if (isFirstCell) {
|
|
323
|
+
return setPlaceHolderState({
|
|
324
|
+
placeholderText: !fg('platform_editor_ai_aifc_patch_ga') ? intl.formatMessage(messages.shortEmptyNodePlaceholderText) : undefined,
|
|
325
|
+
contextPlaceholderADF: fg('platform_editor_ai_aifc_patch_ga') ? createShortEmptyNodePlaceholderADF(intl) : undefined,
|
|
326
|
+
pos: $from.pos,
|
|
327
|
+
placeholderPrompts,
|
|
328
|
+
typedAndDeleted,
|
|
329
|
+
userHadTyped
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (nodeTypesWithLongPlaceholderText.includes(parentType) && isEmptyNode) {
|
|
334
|
+
return setPlaceHolderState({
|
|
335
|
+
placeholderText: !fg('platform_editor_ai_aifc_patch_ga') ? intl.formatMessage(messages.longEmptyNodePlaceholderText) : undefined,
|
|
336
|
+
contextPlaceholderADF: fg('platform_editor_ai_aifc_patch_ga') ? createLongEmptyNodePlaceholderADF(intl) : undefined,
|
|
337
|
+
pos: $from.pos,
|
|
338
|
+
placeholderPrompts,
|
|
339
|
+
typedAndDeleted,
|
|
340
|
+
userHadTyped
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
if (nodeTypesWithSyncBlockPlaceholderText.includes(parentType) && isEmptyNode && editorExperiment('platform_synced_block', true)) {
|
|
344
|
+
return setPlaceHolderState({
|
|
345
|
+
placeholderText: intl.formatMessage(messages.syncBlockPlaceholderText),
|
|
346
|
+
pos: $from.pos,
|
|
347
|
+
placeholderPrompts,
|
|
348
|
+
typedAndDeleted,
|
|
349
|
+
userHadTyped
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return emptyPlaceholder({
|
|
353
|
+
placeholderText: defaultPlaceholderText,
|
|
354
|
+
placeholderPrompts,
|
|
355
|
+
userHadTyped
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
if (bracketPlaceholderText && bracketTyped(editorState) && isEditorFocused) {
|
|
359
|
+
const {
|
|
360
|
+
$from
|
|
361
|
+
} = editorState.selection;
|
|
362
|
+
// Space is to account for positioning of the bracket
|
|
363
|
+
const bracketHint = ' ' + bracketPlaceholderText;
|
|
364
|
+
return setPlaceHolderState({
|
|
365
|
+
placeholderText: bracketHint,
|
|
366
|
+
pos: $from.pos - 1,
|
|
367
|
+
placeholderPrompts,
|
|
368
|
+
typedAndDeleted,
|
|
369
|
+
userHadTyped
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
return emptyPlaceholder({
|
|
373
|
+
placeholderText: defaultPlaceholderText,
|
|
374
|
+
placeholderPrompts,
|
|
375
|
+
userHadTyped
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function calculateUserInteractionState({
|
|
379
|
+
placeholderState,
|
|
380
|
+
oldEditorState,
|
|
381
|
+
newEditorState
|
|
382
|
+
}) {
|
|
383
|
+
const wasEmpty = oldEditorState ? isEmptyDocument(oldEditorState.doc) : true;
|
|
384
|
+
const isEmpty = isEmptyDocument(newEditorState.doc);
|
|
385
|
+
const hasEverTyped = Boolean(placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.userHadTyped) ||
|
|
386
|
+
// Previously typed
|
|
387
|
+
!wasEmpty ||
|
|
388
|
+
// Had content before
|
|
389
|
+
wasEmpty && !isEmpty; // Just added content
|
|
390
|
+
const justDeletedAll = hasEverTyped && isEmpty && !wasEmpty;
|
|
391
|
+
const isInTypedAndDeletedState = justDeletedAll || Boolean(placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.typedAndDeleted) && isEmpty;
|
|
392
|
+
// Only reset user interaction tracking when editor is cleanly empty
|
|
393
|
+
const shouldResetInteraction = isEmpty && !isInTypedAndDeletedState;
|
|
394
|
+
return {
|
|
395
|
+
userHadTyped: shouldResetInteraction ? false : hasEverTyped,
|
|
396
|
+
typedAndDeleted: isInTypedAndDeletedState
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, emptyLinePlaceholder, placeholderPrompts, withEmptyParagraph, placeholderADF, api) {
|
|
400
|
+
if (!defaultPlaceholderText && !placeholderPrompts && !bracketPlaceholderText && !placeholderADF) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
let isDestroyed = false;
|
|
404
|
+
let activeTypewriterTimeouts = [];
|
|
405
|
+
const clearAllTypewriterTimeouts = () => {
|
|
406
|
+
activeTypewriterTimeouts.forEach(clearFn => clearFn());
|
|
407
|
+
activeTypewriterTimeouts = [];
|
|
408
|
+
};
|
|
409
|
+
return new SafePlugin({
|
|
410
|
+
key: pluginKey,
|
|
411
|
+
state: {
|
|
412
|
+
init: (_, state) => {
|
|
413
|
+
var _api$focus, _api$focus$sharedStat, _api$typeAhead;
|
|
414
|
+
return createPlaceHolderStateFrom({
|
|
415
|
+
isInitial: true,
|
|
416
|
+
isEditorFocused: Boolean(api === null || api === void 0 ? void 0 : (_api$focus = api.focus) === null || _api$focus === void 0 ? void 0 : (_api$focus$sharedStat = _api$focus.sharedState.currentState()) === null || _api$focus$sharedStat === void 0 ? void 0 : _api$focus$sharedStat.hasFocus),
|
|
417
|
+
editorState: state,
|
|
418
|
+
isTypeAheadOpen: api === null || api === void 0 ? void 0 : (_api$typeAhead = api.typeAhead) === null || _api$typeAhead === void 0 ? void 0 : _api$typeAhead.actions.isOpen,
|
|
419
|
+
defaultPlaceholderText,
|
|
420
|
+
bracketPlaceholderText,
|
|
421
|
+
emptyLinePlaceholder,
|
|
422
|
+
placeholderADF,
|
|
423
|
+
placeholderPrompts,
|
|
424
|
+
typedAndDeleted: false,
|
|
425
|
+
userHadTyped: false,
|
|
426
|
+
intl,
|
|
427
|
+
withEmptyParagraph
|
|
428
|
+
});
|
|
429
|
+
},
|
|
430
|
+
apply: (tr, placeholderState, _oldEditorState, newEditorState) => {
|
|
431
|
+
var _api$focus2, _api$focus2$sharedSta, _placeholderState$isP, _api$typeAhead2, _ref, _meta$placeholderText, _ref2, _meta$placeholderProm, _meta$showOnEmptyPara;
|
|
432
|
+
const meta = tr.getMeta(pluginKey);
|
|
433
|
+
const isEditorFocused = Boolean(api === null || api === void 0 ? void 0 : (_api$focus2 = api.focus) === null || _api$focus2 === void 0 ? void 0 : (_api$focus2$sharedSta = _api$focus2.sharedState.currentState()) === null || _api$focus2$sharedSta === void 0 ? void 0 : _api$focus2$sharedSta.hasFocus);
|
|
434
|
+
const {
|
|
435
|
+
userHadTyped,
|
|
436
|
+
typedAndDeleted
|
|
437
|
+
} = calculateUserInteractionState({
|
|
438
|
+
placeholderState,
|
|
439
|
+
oldEditorState: _oldEditorState,
|
|
440
|
+
newEditorState
|
|
441
|
+
});
|
|
442
|
+
let isPlaceholderHidden = (_placeholderState$isP = placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.isPlaceholderHidden) !== null && _placeholderState$isP !== void 0 ? _placeholderState$isP : false;
|
|
443
|
+
if ((meta === null || meta === void 0 ? void 0 : meta.isPlaceholderHidden) !== undefined && withEmptyParagraph) {
|
|
444
|
+
isPlaceholderHidden = meta.isPlaceholderHidden;
|
|
445
|
+
}
|
|
446
|
+
if ((meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== undefined && withEmptyParagraph) {
|
|
447
|
+
// Only update defaultPlaceholderText from meta if we're not using ADF placeholder
|
|
448
|
+
if (!(fg('platform_editor_ai_aifc_patch_ga') && placeholderADF)) {
|
|
449
|
+
defaultPlaceholderText = meta.placeholderText;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const newPlaceholderState = createPlaceHolderStateFrom({
|
|
453
|
+
isEditorFocused,
|
|
454
|
+
editorState: newEditorState,
|
|
455
|
+
isTypeAheadOpen: api === null || api === void 0 ? void 0 : (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.actions.isOpen,
|
|
456
|
+
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,
|
|
457
|
+
bracketPlaceholderText,
|
|
458
|
+
emptyLinePlaceholder,
|
|
459
|
+
placeholderADF,
|
|
460
|
+
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,
|
|
461
|
+
typedAndDeleted,
|
|
462
|
+
userHadTyped,
|
|
463
|
+
intl,
|
|
464
|
+
isPlaceholderHidden,
|
|
465
|
+
withEmptyParagraph,
|
|
466
|
+
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
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Clear timeouts when hasPlaceholder becomes false
|
|
470
|
+
if (!newPlaceholderState.hasPlaceholder) {
|
|
471
|
+
clearAllTypewriterTimeouts();
|
|
472
|
+
}
|
|
473
|
+
return newPlaceholderState;
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
props: {
|
|
477
|
+
decorations(editorState) {
|
|
478
|
+
var _api$composition, _api$showDiff, _api$showDiff$sharedS;
|
|
479
|
+
const {
|
|
480
|
+
hasPlaceholder,
|
|
481
|
+
placeholderText,
|
|
482
|
+
pos,
|
|
483
|
+
typedAndDeleted,
|
|
484
|
+
contextPlaceholderADF
|
|
485
|
+
} = getPlaceholderState(editorState);
|
|
486
|
+
|
|
487
|
+
// Decorations is still called after plugin is destroyed
|
|
488
|
+
// So we need to make sure decorations is not called if plugin has been destroyed to prevent the placeholder animations' setTimeouts called infinitely
|
|
489
|
+
if (isDestroyed) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const compositionPluginState = api === null || api === void 0 ? void 0 : (_api$composition = api.composition) === null || _api$composition === void 0 ? void 0 : _api$composition.sharedState.currentState();
|
|
493
|
+
const isShowingDiff = Boolean(api === null || api === void 0 ? void 0 : (_api$showDiff = api.showDiff) === null || _api$showDiff === void 0 ? void 0 : (_api$showDiff$sharedS = _api$showDiff.sharedState.currentState()) === null || _api$showDiff$sharedS === void 0 ? void 0 : _api$showDiff$sharedS.isDisplayingChanges);
|
|
494
|
+
if (hasPlaceholder && ((placeholderText !== null && placeholderText !== void 0 ? placeholderText : '') || placeholderPrompts || placeholderADF || contextPlaceholderADF) && pos !== undefined && !(compositionPluginState !== null && compositionPluginState !== void 0 && compositionPluginState.isComposing) && !isShowingDiff) {
|
|
495
|
+
const initialDelayWhenUserTypedAndDeleted = typedAndDeleted ? TYPEWRITER_TYPED_AND_DELETED_DELAY : 0;
|
|
496
|
+
// contextPlaceholderADF takes precedence over the global placeholderADF
|
|
497
|
+
const placeholderAdfToUse = contextPlaceholderADF || placeholderADF;
|
|
498
|
+
return createPlaceholderDecoration(editorState, placeholderText !== null && placeholderText !== void 0 ? placeholderText : '', placeholderPrompts, activeTypewriterTimeouts, pos, initialDelayWhenUserTypedAndDeleted, placeholderAdfToUse);
|
|
499
|
+
}
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
view() {
|
|
504
|
+
let timeoutId;
|
|
505
|
+
function startEmptyParagraphTimeout(editorView) {
|
|
506
|
+
if (timeoutId) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
timeoutId = setTimeout(() => {
|
|
510
|
+
timeoutId = undefined;
|
|
511
|
+
editorView.dispatch(editorView.state.tr.setMeta(pluginKey, {
|
|
512
|
+
showOnEmptyParagraph: true
|
|
513
|
+
}));
|
|
514
|
+
}, EMPTY_PARAGRAPH_TIMEOUT_DELAY);
|
|
515
|
+
}
|
|
516
|
+
function destroyEmptyParagraphTimeout() {
|
|
517
|
+
if (timeoutId) {
|
|
518
|
+
clearTimeout(timeoutId);
|
|
519
|
+
timeoutId = undefined;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
update(editorView, prevState) {
|
|
524
|
+
if (withEmptyParagraph) {
|
|
525
|
+
const prevPluginState = getPlaceholderState(prevState);
|
|
526
|
+
const newPluginState = getPlaceholderState(editorView.state);
|
|
527
|
+
|
|
528
|
+
// user start typing after move to an empty paragraph, clear timeout
|
|
529
|
+
if (!newPluginState.canShowOnEmptyParagraph && timeoutId) {
|
|
530
|
+
destroyEmptyParagraphTimeout();
|
|
531
|
+
}
|
|
532
|
+
// user move to an empty paragraph again, reset state to hide placeholder, and restart timeout
|
|
533
|
+
else if (prevPluginState.canShowOnEmptyParagraph && newPluginState.canShowOnEmptyParagraph && newPluginState.pos !== prevPluginState.pos) {
|
|
534
|
+
editorView.dispatch(editorView.state.tr.setMeta(pluginKey, {
|
|
535
|
+
showOnEmptyParagraph: false
|
|
536
|
+
}));
|
|
537
|
+
destroyEmptyParagraphTimeout();
|
|
538
|
+
startEmptyParagraphTimeout(editorView);
|
|
539
|
+
}
|
|
540
|
+
// user move to an empty paragraph (by click enter or move to an empty paragraph), start timeout
|
|
541
|
+
else if (!prevPluginState.canShowOnEmptyParagraph && newPluginState.canShowOnEmptyParagraph && !newPluginState.showOnEmptyParagraph && !timeoutId) {
|
|
542
|
+
startEmptyParagraphTimeout(editorView);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
destroy() {
|
|
547
|
+
clearAllTypewriterTimeouts();
|
|
548
|
+
destroyEmptyParagraphTimeout();
|
|
549
|
+
isDestroyed = true;
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
export const placeholderPluginLegacy = ({
|
|
556
|
+
config: options,
|
|
557
|
+
api
|
|
558
|
+
}) => {
|
|
559
|
+
let currentPlaceholder = options === null || options === void 0 ? void 0 : options.placeholder;
|
|
560
|
+
return {
|
|
561
|
+
name: 'placeholder',
|
|
562
|
+
commands: {
|
|
563
|
+
setPlaceholder: placeholderText => ({
|
|
564
|
+
tr
|
|
565
|
+
}) => {
|
|
566
|
+
if (currentPlaceholder !== placeholderText) {
|
|
567
|
+
currentPlaceholder = placeholderText;
|
|
568
|
+
return tr.setMeta(pluginKey, {
|
|
569
|
+
placeholderText: placeholderText
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
return null;
|
|
573
|
+
},
|
|
574
|
+
setAnimatingPlaceholderPrompts: placeholderPrompts => ({
|
|
575
|
+
tr
|
|
576
|
+
}) => {
|
|
577
|
+
return tr.setMeta(pluginKey, {
|
|
578
|
+
placeholderPrompts: placeholderPrompts
|
|
579
|
+
});
|
|
580
|
+
},
|
|
581
|
+
setPlaceholderHidden: isPlaceholderHidden => ({
|
|
582
|
+
tr
|
|
583
|
+
}) => {
|
|
584
|
+
return tr.setMeta(pluginKey, {
|
|
585
|
+
isPlaceholderHidden
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
pmPlugins() {
|
|
590
|
+
return [{
|
|
591
|
+
name: 'placeholder',
|
|
592
|
+
plugin: ({
|
|
593
|
+
getIntl
|
|
594
|
+
}) => createPlugin(getIntl(), options && options.placeholder, options && options.placeholderBracketHint, options && options.emptyLinePlaceholder, options && options.placeholderPrompts, options === null || options === void 0 ? void 0 : options.withEmptyParagraph, options && options.placeholderADF, api)
|
|
595
|
+
}];
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|