@atlaskit/editor-plugin-placeholder 6.1.1 → 6.3.0

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.
@@ -1,9 +1,11 @@
1
1
  import { placeholderTextMessages as messages } from '@atlaskit/editor-common/messages';
2
2
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
- import { bracketTyped, browser, isEmptyDocument, isEmptyParagraph } from '@atlaskit/editor-common/utils';
3
+ import { bracketTyped, browser, hasDocAsParent, isEmptyDocument, isEmptyParagraph } from '@atlaskit/editor-common/utils';
4
4
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
5
5
  import { findParentNode } from '@atlaskit/editor-prosemirror/utils';
6
6
  import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
+ import { fg } from '@atlaskit/platform-feature-flags';
8
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
7
9
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
8
10
  // Typewriter animation timing constants
9
11
  const TYPEWRITER_TYPE_DELAY = 50; // Delay between typing each character
@@ -12,6 +14,8 @@ const TYPEWRITER_ERASE_DELAY = 40; // Delay between erasing each character
12
14
  const TYPEWRITER_CYCLE_DELAY = 500; // Delay before starting next cycle
13
15
  const TYPEWRITER_TYPED_AND_DELETED_DELAY = 1500; // Delay before starting animation after user typed and deleted
14
16
 
17
+ export const EMPTY_PARAGRAPH_TIMEOUT_DELAY = 2000; // Delay before showing placeholder on empty paragraph
18
+
15
19
  export const pluginKey = new PluginKey('placeholderPlugin');
16
20
  const placeholderTestId = 'placeholder-test-id';
17
21
  function getPlaceholderState(editorState) {
@@ -19,6 +23,7 @@ function getPlaceholderState(editorState) {
19
23
  }
20
24
  const nodeTypesWithLongPlaceholderText = ['expand', 'panel'];
21
25
  const nodeTypesWithShortPlaceholderText = ['tableCell', 'tableHeader'];
26
+ const nodeTypesWithSyncBlockPlaceholderText = ['bodiedSyncBlock'];
22
27
  const cycleThroughPlaceholderPrompts = (placeholderPrompts, activeTypewriterTimeouts, placeholderNodeWithText, initialDelayWhenUserTypedAndDeleted = 0) => {
23
28
  let currentPromptIndex = 0;
24
29
  let displayedText = '';
@@ -109,24 +114,45 @@ export function createPlaceholderDecoration(editorState, placeholderText, placeh
109
114
  key: `placeholder ${placeholderText}`
110
115
  })]);
111
116
  }
112
- function setPlaceHolderState(placeholderText, pos, placeholderPrompts, typedAndDeleted, userHadTyped) {
117
+ function setPlaceHolderState({
118
+ placeholderText,
119
+ pos,
120
+ placeholderPrompts,
121
+ typedAndDeleted,
122
+ userHadTyped,
123
+ canShowOnEmptyParagraph,
124
+ showOnEmptyParagraph
125
+ }) {
113
126
  return {
114
127
  hasPlaceholder: true,
115
128
  placeholderText,
116
129
  placeholderPrompts,
117
130
  pos: pos ? pos : 1,
118
131
  typedAndDeleted,
119
- userHadTyped
132
+ userHadTyped,
133
+ canShowOnEmptyParagraph,
134
+ showOnEmptyParagraph
120
135
  };
121
136
  }
122
- const emptyPlaceholder = (placeholderText, placeholderPrompts, userHadTyped) => ({
137
+ const emptyPlaceholder = ({
138
+ placeholderText,
139
+ placeholderPrompts,
140
+ userHadTyped,
141
+ pos,
142
+ canShowOnEmptyParagraph,
143
+ showOnEmptyParagraph
144
+ }) => ({
123
145
  hasPlaceholder: false,
124
146
  placeholderText,
125
147
  placeholderPrompts,
126
148
  userHadTyped,
127
- typedAndDeleted: false
149
+ typedAndDeleted: false,
150
+ canShowOnEmptyParagraph,
151
+ showOnEmptyParagraph,
152
+ pos
128
153
  });
129
154
  function createPlaceHolderStateFrom({
155
+ isInitial,
130
156
  isEditorFocused,
131
157
  editorState,
132
158
  isTypeAheadOpen,
@@ -136,13 +162,61 @@ function createPlaceHolderStateFrom({
136
162
  emptyLinePlaceholder,
137
163
  placeholderPrompts,
138
164
  typedAndDeleted,
139
- userHadTyped
165
+ userHadTyped,
166
+ isPlaceholderHidden,
167
+ withEmptyParagraph,
168
+ showOnEmptyParagraph
140
169
  }) {
170
+ if (isPlaceholderHidden && fg('platform_editor_ai_aifc_patch_beta')) {
171
+ return {
172
+ ...emptyPlaceholder({
173
+ placeholderText: defaultPlaceholderText,
174
+ placeholderPrompts,
175
+ userHadTyped
176
+ }),
177
+ isPlaceholderHidden
178
+ };
179
+ }
141
180
  if (isTypeAheadOpen !== null && isTypeAheadOpen !== void 0 && isTypeAheadOpen(editorState)) {
142
- return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts, userHadTyped);
181
+ return emptyPlaceholder({
182
+ placeholderText: defaultPlaceholderText,
183
+ placeholderPrompts,
184
+ userHadTyped
185
+ });
143
186
  }
144
187
  if ((defaultPlaceholderText || placeholderPrompts) && isEmptyDocument(editorState.doc)) {
145
- return setPlaceHolderState(defaultPlaceholderText, 1, placeholderPrompts, typedAndDeleted, userHadTyped);
188
+ return setPlaceHolderState({
189
+ placeholderText: defaultPlaceholderText,
190
+ pos: 1,
191
+ placeholderPrompts,
192
+ typedAndDeleted,
193
+ userHadTyped
194
+ });
195
+ }
196
+ if (fg('platform_editor_ai_aifc_patch_beta_2')) {
197
+ const {
198
+ from,
199
+ to,
200
+ $to
201
+ } = editorState.selection;
202
+ if (defaultPlaceholderText && withEmptyParagraph && isEditorFocused && !isInitial && !isEmptyDocument(editorState.doc) && from === to && isEmptyParagraph($to.parent) && hasDocAsParent($to)) {
203
+ return showOnEmptyParagraph ? setPlaceHolderState({
204
+ placeholderText: defaultPlaceholderText,
205
+ pos: to,
206
+ placeholderPrompts,
207
+ typedAndDeleted,
208
+ userHadTyped,
209
+ canShowOnEmptyParagraph: true,
210
+ showOnEmptyParagraph: true
211
+ }) : emptyPlaceholder({
212
+ placeholderText: defaultPlaceholderText,
213
+ placeholderPrompts,
214
+ userHadTyped,
215
+ canShowOnEmptyParagraph: true,
216
+ showOnEmptyParagraph: false,
217
+ pos: to
218
+ });
219
+ }
146
220
  }
147
221
  if (isEditorFocused && editorExperiment('platform_editor_controls', 'variant1')) {
148
222
  var _parentNode$firstChil, _parentNode$firstChil2;
@@ -151,14 +225,24 @@ function createPlaceHolderStateFrom({
151
225
  $to
152
226
  } = editorState.selection;
153
227
  if ($from.pos !== $to.pos) {
154
- return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts, userHadTyped);
228
+ return emptyPlaceholder({
229
+ placeholderText: defaultPlaceholderText,
230
+ placeholderPrompts,
231
+ userHadTyped
232
+ });
155
233
  }
156
234
  const parentNode = $from.node($from.depth - 1);
157
235
  const parentType = parentNode === null || parentNode === void 0 ? void 0 : parentNode.type.name;
158
236
  if (emptyLinePlaceholder && parentType === 'doc') {
159
237
  const isEmptyLine = isEmptyParagraph($from.parent);
160
238
  if (isEmptyLine) {
161
- return setPlaceHolderState(emptyLinePlaceholder, $from.pos, placeholderPrompts, typedAndDeleted, userHadTyped);
239
+ return setPlaceHolderState({
240
+ placeholderText: emptyLinePlaceholder,
241
+ pos: $from.pos,
242
+ placeholderPrompts,
243
+ typedAndDeleted,
244
+ userHadTyped
245
+ });
162
246
  }
163
247
  }
164
248
  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';
@@ -166,17 +250,46 @@ function createPlaceHolderStateFrom({
166
250
  var _table$node$firstChil;
167
251
  const table = findParentNode(node => node.type === editorState.schema.nodes.table)(editorState.selection);
168
252
  if (!table) {
169
- return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts, userHadTyped);
253
+ return emptyPlaceholder({
254
+ placeholderText: defaultPlaceholderText,
255
+ placeholderPrompts,
256
+ userHadTyped
257
+ });
170
258
  }
171
259
  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;
172
260
  if (isFirstCell) {
173
- return setPlaceHolderState(intl.formatMessage(messages.shortEmptyNodePlaceholderText), $from.pos, placeholderPrompts, typedAndDeleted, userHadTyped);
261
+ return setPlaceHolderState({
262
+ placeholderText: intl.formatMessage(messages.shortEmptyNodePlaceholderText),
263
+ pos: $from.pos,
264
+ placeholderPrompts,
265
+ typedAndDeleted,
266
+ userHadTyped
267
+ });
174
268
  }
175
269
  }
176
270
  if (nodeTypesWithLongPlaceholderText.includes(parentType) && isEmptyNode) {
177
- return setPlaceHolderState(intl.formatMessage(messages.longEmptyNodePlaceholderText), $from.pos, placeholderPrompts, typedAndDeleted, userHadTyped);
271
+ return setPlaceHolderState({
272
+ placeholderText: intl.formatMessage(messages.longEmptyNodePlaceholderText),
273
+ pos: $from.pos,
274
+ placeholderPrompts,
275
+ typedAndDeleted,
276
+ userHadTyped
277
+ });
278
+ }
279
+ if (nodeTypesWithSyncBlockPlaceholderText.includes(parentType) && isEmptyNode && expValEquals('platform_synced_block', 'isEnabled', true)) {
280
+ return setPlaceHolderState({
281
+ placeholderText: intl.formatMessage(messages.syncBlockPlaceholderText),
282
+ pos: $from.pos,
283
+ placeholderPrompts,
284
+ typedAndDeleted,
285
+ userHadTyped
286
+ });
178
287
  }
179
- return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts, userHadTyped);
288
+ return emptyPlaceholder({
289
+ placeholderText: defaultPlaceholderText,
290
+ placeholderPrompts,
291
+ userHadTyped
292
+ });
180
293
  }
181
294
  if (bracketPlaceholderText && bracketTyped(editorState) && isEditorFocused) {
182
295
  const {
@@ -184,9 +297,19 @@ function createPlaceHolderStateFrom({
184
297
  } = editorState.selection;
185
298
  // Space is to account for positioning of the bracket
186
299
  const bracketHint = ' ' + bracketPlaceholderText;
187
- return setPlaceHolderState(bracketHint, $from.pos - 1, placeholderPrompts, typedAndDeleted, userHadTyped);
300
+ return setPlaceHolderState({
301
+ placeholderText: bracketHint,
302
+ pos: $from.pos - 1,
303
+ placeholderPrompts,
304
+ typedAndDeleted,
305
+ userHadTyped
306
+ });
188
307
  }
189
- return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts, userHadTyped);
308
+ return emptyPlaceholder({
309
+ placeholderText: defaultPlaceholderText,
310
+ placeholderPrompts,
311
+ userHadTyped
312
+ });
190
313
  }
191
314
  function calculateUserInteractionState({
192
315
  placeholderState,
@@ -209,7 +332,7 @@ function calculateUserInteractionState({
209
332
  typedAndDeleted: isInTypedAndDeletedState
210
333
  };
211
334
  }
212
- export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, emptyLinePlaceholder, placeholderPrompts, api) {
335
+ export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, emptyLinePlaceholder, placeholderPrompts, withEmptyParagraph, api) {
213
336
  if (!defaultPlaceholderText && !placeholderPrompts && !bracketPlaceholderText) {
214
337
  return;
215
338
  }
@@ -225,6 +348,7 @@ export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderTex
225
348
  init: (_, state) => {
226
349
  var _api$focus, _api$focus$sharedStat, _api$typeAhead;
227
350
  return createPlaceHolderStateFrom({
351
+ isInitial: true,
228
352
  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),
229
353
  editorState: state,
230
354
  isTypeAheadOpen: api === null || api === void 0 ? void 0 : (_api$typeAhead = api.typeAhead) === null || _api$typeAhead === void 0 ? void 0 : _api$typeAhead.actions.isOpen,
@@ -238,7 +362,7 @@ export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderTex
238
362
  });
239
363
  },
240
364
  apply: (tr, placeholderState, _oldEditorState, newEditorState) => {
241
- var _api$focus2, _api$focus2$sharedSta, _api$typeAhead2, _ref, _meta$placeholderText, _ref2, _meta$placeholderProm;
365
+ var _api$focus2, _api$focus2$sharedSta, _placeholderState$isP, _api$typeAhead2, _ref, _meta$placeholderText, _ref2, _meta$placeholderProm, _meta$showOnEmptyPara;
242
366
  const meta = tr.getMeta(pluginKey);
243
367
  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);
244
368
  const {
@@ -249,17 +373,27 @@ export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderTex
249
373
  oldEditorState: _oldEditorState,
250
374
  newEditorState
251
375
  });
376
+ let isPlaceholderHidden = (_placeholderState$isP = placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.isPlaceholderHidden) !== null && _placeholderState$isP !== void 0 ? _placeholderState$isP : false;
377
+ if ((meta === null || meta === void 0 ? void 0 : meta.isPlaceholderHidden) !== undefined && fg('platform_editor_ai_aifc_patch_beta')) {
378
+ isPlaceholderHidden = meta.isPlaceholderHidden;
379
+ }
380
+ if ((meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== undefined && fg('platform_editor_ai_aifc_patch_beta_2')) {
381
+ defaultPlaceholderText = meta.placeholderText;
382
+ }
252
383
  const newPlaceholderState = createPlaceHolderStateFrom({
253
384
  isEditorFocused,
254
385
  editorState: newEditorState,
255
386
  isTypeAheadOpen: api === null || api === void 0 ? void 0 : (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.actions.isOpen,
256
- 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,
387
+ defaultPlaceholderText: fg('platform_editor_ai_aifc_patch_beta_2') ? 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,
257
388
  bracketPlaceholderText,
258
389
  emptyLinePlaceholder,
259
390
  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,
260
391
  typedAndDeleted,
261
392
  userHadTyped,
262
- intl
393
+ intl,
394
+ isPlaceholderHidden,
395
+ withEmptyParagraph,
396
+ 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
263
397
  });
264
398
 
265
399
  // Clear timeouts when hasPlaceholder becomes false
@@ -294,9 +428,51 @@ export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderTex
294
428
  }
295
429
  },
296
430
  view() {
431
+ let timeoutId;
432
+ function startEmptyParagraphTimeout(editorView) {
433
+ if (timeoutId) {
434
+ return;
435
+ }
436
+ timeoutId = setTimeout(() => {
437
+ timeoutId = undefined;
438
+ editorView.dispatch(editorView.state.tr.setMeta(pluginKey, {
439
+ showOnEmptyParagraph: true
440
+ }));
441
+ }, EMPTY_PARAGRAPH_TIMEOUT_DELAY);
442
+ }
443
+ function destroyEmptyParagraphTimeout() {
444
+ if (timeoutId) {
445
+ clearTimeout(timeoutId);
446
+ timeoutId = undefined;
447
+ }
448
+ }
297
449
  return {
450
+ update(editorView, prevState) {
451
+ if (fg('platform_editor_ai_aifc_patch_beta_2')) {
452
+ const prevPluginState = getPlaceholderState(prevState);
453
+ const newPluginState = getPlaceholderState(editorView.state);
454
+
455
+ // user start typing after move to an empty paragraph, clear timeout
456
+ if (!newPluginState.canShowOnEmptyParagraph && timeoutId) {
457
+ destroyEmptyParagraphTimeout();
458
+ }
459
+ // user move to an empty paragraph again, reset state to hide placeholder, and restart timeout
460
+ else if (prevPluginState.canShowOnEmptyParagraph && newPluginState.canShowOnEmptyParagraph && newPluginState.pos !== prevPluginState.pos) {
461
+ editorView.dispatch(editorView.state.tr.setMeta(pluginKey, {
462
+ showOnEmptyParagraph: false
463
+ }));
464
+ destroyEmptyParagraphTimeout();
465
+ startEmptyParagraphTimeout(editorView);
466
+ }
467
+ // user move to an empty paragraph (by click enter or move to an empty paragraph), start timeout
468
+ else if (!prevPluginState.canShowOnEmptyParagraph && newPluginState.canShowOnEmptyParagraph && !newPluginState.showOnEmptyParagraph && !timeoutId) {
469
+ startEmptyParagraphTimeout(editorView);
470
+ }
471
+ }
472
+ },
298
473
  destroy() {
299
474
  clearAllTypewriterTimeouts();
475
+ destroyEmptyParagraphTimeout();
300
476
  isDestroyed = true;
301
477
  }
302
478
  };
@@ -328,6 +504,13 @@ export const placeholderPlugin = ({
328
504
  return tr.setMeta(pluginKey, {
329
505
  placeholderPrompts: placeholderPrompts
330
506
  });
507
+ },
508
+ setPlaceholderHidden: isPlaceholderHidden => ({
509
+ tr
510
+ }) => {
511
+ return tr.setMeta(pluginKey, {
512
+ isPlaceholderHidden
513
+ });
331
514
  }
332
515
  },
333
516
  pmPlugins() {
@@ -335,7 +518,7 @@ export const placeholderPlugin = ({
335
518
  name: 'placeholder',
336
519
  plugin: ({
337
520
  getIntl
338
- }) => createPlugin(getIntl(), options && options.placeholder, options && options.placeholderBracketHint, options && options.emptyLinePlaceholder, options && options.placeholderPrompts, api)
521
+ }) => createPlugin(getIntl(), options && options.placeholder, options && options.placeholderBracketHint, options && options.emptyLinePlaceholder, options && options.placeholderPrompts, options === null || options === void 0 ? void 0 : options.withEmptyParagraph, api)
339
522
  }];
340
523
  }
341
524
  };