@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.
Files changed (47) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/placeholderPlugin.js +19 -568
  3. package/dist/cjs/pm-plugins/adf-builders.js +30 -0
  4. package/dist/cjs/pm-plugins/animation.js +58 -0
  5. package/dist/cjs/pm-plugins/constants.js +18 -0
  6. package/dist/cjs/pm-plugins/decorations.js +96 -0
  7. package/dist/cjs/pm-plugins/main.js +168 -0
  8. package/dist/cjs/pm-plugins/placeholderPluginLegacy.js +617 -0
  9. package/dist/cjs/pm-plugins/types.js +5 -0
  10. package/dist/cjs/pm-plugins/utils.js +243 -0
  11. package/dist/es2019/placeholderPlugin.js +8 -548
  12. package/dist/es2019/pm-plugins/adf-builders.js +22 -0
  13. package/dist/es2019/pm-plugins/animation.js +49 -0
  14. package/dist/es2019/pm-plugins/constants.js +12 -0
  15. package/dist/es2019/pm-plugins/decorations.js +87 -0
  16. package/dist/es2019/pm-plugins/main.js +162 -0
  17. package/dist/es2019/pm-plugins/placeholderPluginLegacy.js +598 -0
  18. package/dist/es2019/pm-plugins/types.js +1 -0
  19. package/dist/es2019/pm-plugins/utils.js +234 -0
  20. package/dist/esm/placeholderPlugin.js +17 -563
  21. package/dist/esm/pm-plugins/adf-builders.js +24 -0
  22. package/dist/esm/pm-plugins/animation.js +52 -0
  23. package/dist/esm/pm-plugins/constants.js +12 -0
  24. package/dist/esm/pm-plugins/decorations.js +90 -0
  25. package/dist/esm/pm-plugins/main.js +162 -0
  26. package/dist/esm/pm-plugins/placeholderPluginLegacy.js +607 -0
  27. package/dist/esm/pm-plugins/types.js +1 -0
  28. package/dist/esm/pm-plugins/utils.js +232 -0
  29. package/dist/types/placeholderPlugin.d.ts +0 -8
  30. package/dist/types/pm-plugins/adf-builders.d.ts +4 -0
  31. package/dist/types/pm-plugins/animation.d.ts +1 -0
  32. package/dist/types/pm-plugins/constants.d.ts +9 -0
  33. package/dist/types/pm-plugins/decorations.d.ts +4 -0
  34. package/dist/types/pm-plugins/main.d.ts +6 -0
  35. package/dist/types/pm-plugins/placeholderPluginLegacy.d.ts +13 -0
  36. package/dist/types/pm-plugins/types.d.ts +37 -0
  37. package/dist/types/pm-plugins/utils.d.ts +27 -0
  38. package/dist/types-ts4.5/placeholderPlugin.d.ts +0 -8
  39. package/dist/types-ts4.5/pm-plugins/adf-builders.d.ts +4 -0
  40. package/dist/types-ts4.5/pm-plugins/animation.d.ts +1 -0
  41. package/dist/types-ts4.5/pm-plugins/constants.d.ts +9 -0
  42. package/dist/types-ts4.5/pm-plugins/decorations.d.ts +4 -0
  43. package/dist/types-ts4.5/pm-plugins/main.d.ts +6 -0
  44. package/dist/types-ts4.5/pm-plugins/placeholderPluginLegacy.d.ts +13 -0
  45. package/dist/types-ts4.5/pm-plugins/types.d.ts +37 -0
  46. package/dist/types-ts4.5/pm-plugins/utils.d.ts +27 -0
  47. package/package.json +6 -9
@@ -1,560 +1,20 @@
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
1
  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
2
  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
-
3
+ import createPlugin from './pm-plugins/main';
4
+ import { placeholderPluginLegacy } from './pm-plugins/placeholderPluginLegacy';
19
5
  export const EMPTY_PARAGRAPH_TIMEOUT_DELAY = 2000; // Delay before showing placeholder on empty paragraph
20
6
 
21
7
  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 && fg('platform_editor_ai_aifc_patch_beta')) {
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 (fg('platform_editor_ai_aifc_patch_beta_2') || fg('platform_editor_ai_aifc_patch_ga')) {
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
- });
428
- },
429
- apply: (tr, placeholderState, _oldEditorState, newEditorState) => {
430
- var _api$focus2, _api$focus2$sharedSta, _placeholderState$isP, _api$typeAhead2, _ref, _meta$placeholderText, _ref2, _meta$placeholderProm, _meta$showOnEmptyPara;
431
- const meta = tr.getMeta(pluginKey);
432
- 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);
433
- const {
434
- userHadTyped,
435
- typedAndDeleted
436
- } = calculateUserInteractionState({
437
- placeholderState,
438
- oldEditorState: _oldEditorState,
439
- newEditorState
440
- });
441
- let isPlaceholderHidden = (_placeholderState$isP = placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.isPlaceholderHidden) !== null && _placeholderState$isP !== void 0 ? _placeholderState$isP : false;
442
- if ((meta === null || meta === void 0 ? void 0 : meta.isPlaceholderHidden) !== undefined && fg('platform_editor_ai_aifc_patch_beta')) {
443
- isPlaceholderHidden = meta.isPlaceholderHidden;
444
- }
445
- if ((meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== undefined && (fg('platform_editor_ai_aifc_patch_beta_2') || fg('platform_editor_ai_aifc_patch_ga'))) {
446
- // Only update defaultPlaceholderText from meta if we're not using ADF placeholder
447
- if (!(fg('platform_editor_ai_aifc_patch_ga') && placeholderADF)) {
448
- defaultPlaceholderText = meta.placeholderText;
449
- }
450
- }
451
- const newPlaceholderState = createPlaceHolderStateFrom({
452
- isEditorFocused,
453
- editorState: newEditorState,
454
- isTypeAheadOpen: api === null || api === void 0 ? void 0 : (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.actions.isOpen,
455
- defaultPlaceholderText: fg('platform_editor_ai_aifc_patch_beta_2') || fg('platform_editor_ai_aifc_patch_ga') ? 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,
456
- bracketPlaceholderText,
457
- emptyLinePlaceholder,
458
- placeholderADF,
459
- 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,
460
- typedAndDeleted,
461
- userHadTyped,
462
- intl,
463
- isPlaceholderHidden,
464
- withEmptyParagraph,
465
- 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
466
- });
467
-
468
- // Clear timeouts when hasPlaceholder becomes false
469
- if (!newPlaceholderState.hasPlaceholder) {
470
- clearAllTypewriterTimeouts();
471
- }
472
- return newPlaceholderState;
473
- }
474
- },
475
- props: {
476
- decorations(editorState) {
477
- var _api$composition, _api$showDiff, _api$showDiff$sharedS;
478
- const {
479
- hasPlaceholder,
480
- placeholderText,
481
- pos,
482
- typedAndDeleted,
483
- contextPlaceholderADF
484
- } = getPlaceholderState(editorState);
485
-
486
- // Decorations is still called after plugin is destroyed
487
- // So we need to make sure decorations is not called if plugin has been destroyed to prevent the placeholder animations' setTimeouts called infinitely
488
- if (isDestroyed) {
489
- return;
490
- }
491
- const compositionPluginState = api === null || api === void 0 ? void 0 : (_api$composition = api.composition) === null || _api$composition === void 0 ? void 0 : _api$composition.sharedState.currentState();
492
- 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);
493
- if (hasPlaceholder && ((placeholderText !== null && placeholderText !== void 0 ? placeholderText : '') || placeholderPrompts || placeholderADF || contextPlaceholderADF) && pos !== undefined && !(compositionPluginState !== null && compositionPluginState !== void 0 && compositionPluginState.isComposing) && !isShowingDiff) {
494
- const initialDelayWhenUserTypedAndDeleted = typedAndDeleted ? TYPEWRITER_TYPED_AND_DELETED_DELAY : 0;
495
- // contextPlaceholderADF takes precedence over the global placeholderADF
496
- const placeholderAdfToUse = contextPlaceholderADF || placeholderADF;
497
- return createPlaceholderDecoration(editorState, placeholderText !== null && placeholderText !== void 0 ? placeholderText : '', placeholderPrompts, activeTypewriterTimeouts, pos, initialDelayWhenUserTypedAndDeleted, placeholderAdfToUse);
498
- }
499
- return;
500
- }
501
- },
502
- view() {
503
- let timeoutId;
504
- function startEmptyParagraphTimeout(editorView) {
505
- if (timeoutId) {
506
- return;
507
- }
508
- timeoutId = setTimeout(() => {
509
- timeoutId = undefined;
510
- editorView.dispatch(editorView.state.tr.setMeta(pluginKey, {
511
- showOnEmptyParagraph: true
512
- }));
513
- }, EMPTY_PARAGRAPH_TIMEOUT_DELAY);
514
- }
515
- function destroyEmptyParagraphTimeout() {
516
- if (timeoutId) {
517
- clearTimeout(timeoutId);
518
- timeoutId = undefined;
519
- }
520
- }
521
- return {
522
- update(editorView, prevState) {
523
- if (fg('platform_editor_ai_aifc_patch_beta_2') || fg('platform_editor_ai_aifc_patch_ga')) {
524
- const prevPluginState = getPlaceholderState(prevState);
525
- const newPluginState = getPlaceholderState(editorView.state);
526
-
527
- // user start typing after move to an empty paragraph, clear timeout
528
- if (!newPluginState.canShowOnEmptyParagraph && timeoutId) {
529
- destroyEmptyParagraphTimeout();
530
- }
531
- // user move to an empty paragraph again, reset state to hide placeholder, and restart timeout
532
- else if (prevPluginState.canShowOnEmptyParagraph && newPluginState.canShowOnEmptyParagraph && newPluginState.pos !== prevPluginState.pos) {
533
- editorView.dispatch(editorView.state.tr.setMeta(pluginKey, {
534
- showOnEmptyParagraph: false
535
- }));
536
- destroyEmptyParagraphTimeout();
537
- startEmptyParagraphTimeout(editorView);
538
- }
539
- // user move to an empty paragraph (by click enter or move to an empty paragraph), start timeout
540
- else if (!prevPluginState.canShowOnEmptyParagraph && newPluginState.canShowOnEmptyParagraph && !newPluginState.showOnEmptyParagraph && !timeoutId) {
541
- startEmptyParagraphTimeout(editorView);
542
- }
543
- }
544
- },
545
- destroy() {
546
- clearAllTypewriterTimeouts();
547
- destroyEmptyParagraphTimeout();
548
- isDestroyed = true;
549
- }
550
- };
551
- }
552
- });
553
- }
554
8
  export const placeholderPlugin = ({
555
9
  config: options,
556
10
  api
557
11
  }) => {
12
+ if (!fg('platform_editor_placeholder_plugin_tidying')) {
13
+ return placeholderPluginLegacy({
14
+ config: options,
15
+ api
16
+ });
17
+ }
558
18
  let currentPlaceholder = options === null || options === void 0 ? void 0 : options.placeholder;
559
19
  return {
560
20
  name: 'placeholder',
@@ -0,0 +1,22 @@
1
+ import { code, text } from '@atlaskit/adf-utils/builders';
2
+ import { placeholderTextMessages as messages } from '@atlaskit/editor-common/messages';
3
+ export const createShortEmptyNodePlaceholderADF = ({
4
+ formatMessage
5
+ }) => ({
6
+ version: 1,
7
+ type: 'doc',
8
+ content: [{
9
+ type: 'paragraph',
10
+ content: [code(formatMessage(messages.shortEmptyNodePlaceholderADFSlashShortcut)), text(' '), text(formatMessage(messages.shortEmptyNodePlaceholderADFSuffix))]
11
+ }]
12
+ });
13
+ export const createLongEmptyNodePlaceholderADF = ({
14
+ formatMessage
15
+ }) => ({
16
+ version: 1,
17
+ type: 'doc',
18
+ content: [{
19
+ type: 'paragraph',
20
+ content: [text(formatMessage(messages.longEmptyNodePlaceholderADFPrefix)), text(' '), code(formatMessage(messages.longEmptyNodePlaceholderADFSlashShortcut)), text(' '), text(formatMessage(messages.longEmptyNodePlaceholderADFSuffix))]
21
+ }]
22
+ });
@@ -0,0 +1,49 @@
1
+ import { TYPEWRITER_CYCLE_DELAY, TYPEWRITER_ERASE_DELAY, TYPEWRITER_PAUSE_BEFORE_ERASE, TYPEWRITER_TYPE_DELAY } from './constants';
2
+ export const cycleThroughPlaceholderPrompts = (placeholderPrompts, activeTypewriterTimeouts, placeholderNodeWithText, initialDelayWhenUserTypedAndDeleted = 0) => {
3
+ let currentPromptIndex = 0;
4
+ let displayedText = '';
5
+ let animationTimeouts = [];
6
+ const clearAllTimeouts = () => {
7
+ animationTimeouts.forEach(timeoutId => clearTimeout(timeoutId));
8
+ animationTimeouts = [];
9
+ };
10
+ const scheduleTimeout = (callback, delay) => {
11
+ const timeoutId = setTimeout(callback, delay);
12
+ animationTimeouts.push(timeoutId);
13
+ return timeoutId;
14
+ };
15
+ const startAnimationCycle = () => {
16
+ const currentPrompt = placeholderPrompts[currentPromptIndex];
17
+ let characterIndex = 0;
18
+ const typeNextCharacter = () => {
19
+ if (characterIndex < currentPrompt.length) {
20
+ displayedText = currentPrompt.substring(0, characterIndex + 1);
21
+ placeholderNodeWithText.textContent = displayedText;
22
+ characterIndex++;
23
+ scheduleTimeout(typeNextCharacter, TYPEWRITER_TYPE_DELAY);
24
+ } else {
25
+ scheduleTimeout(eraseLastCharacter, TYPEWRITER_PAUSE_BEFORE_ERASE);
26
+ }
27
+ };
28
+ const eraseLastCharacter = () => {
29
+ if (displayedText.length > 1) {
30
+ displayedText = displayedText.substring(0, displayedText.length - 1);
31
+ placeholderNodeWithText.textContent = displayedText;
32
+ scheduleTimeout(eraseLastCharacter, TYPEWRITER_ERASE_DELAY);
33
+ } else {
34
+ displayedText = ' ';
35
+ placeholderNodeWithText.textContent = displayedText;
36
+ currentPromptIndex = (currentPromptIndex + 1) % placeholderPrompts.length;
37
+ scheduleTimeout(startAnimationCycle, TYPEWRITER_CYCLE_DELAY);
38
+ }
39
+ };
40
+ typeNextCharacter();
41
+ };
42
+ activeTypewriterTimeouts === null || activeTypewriterTimeouts === void 0 ? void 0 : activeTypewriterTimeouts.push(clearAllTimeouts);
43
+ if (initialDelayWhenUserTypedAndDeleted > 0) {
44
+ placeholderNodeWithText.textContent = ' ';
45
+ scheduleTimeout(startAnimationCycle, initialDelayWhenUserTypedAndDeleted);
46
+ } else {
47
+ startAnimationCycle();
48
+ }
49
+ };
@@ -0,0 +1,12 @@
1
+ export const placeholderTestId = 'placeholder-test-id';
2
+
3
+ // Typewriter animation timing constants
4
+ export const TYPEWRITER_TYPE_DELAY = 50; // Delay between typing each character
5
+ export const TYPEWRITER_PAUSE_BEFORE_ERASE = 2000; // Pause before starting to erase text
6
+ export const TYPEWRITER_ERASE_DELAY = 40; // Delay between erasing each character
7
+ export const TYPEWRITER_CYCLE_DELAY = 500; // Delay before starting next cycle
8
+ export const TYPEWRITER_TYPED_AND_DELETED_DELAY = 1500; // Delay before starting animation after user typed and deleted
9
+
10
+ export const nodeTypesWithLongPlaceholderText = ['expand', 'panel'];
11
+ export const nodeTypesWithShortPlaceholderText = ['tableCell', 'tableHeader'];
12
+ export const nodeTypesWithSyncBlockPlaceholderText = ['bodiedSyncBlock'];