@atlaskit/editor-core 179.0.4 → 179.1.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 (108) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/cjs/create-editor/ReactEditorView.js +1 -1
  3. package/dist/cjs/create-editor/create-plugins-list.js +6 -287
  4. package/dist/cjs/editor.js +5 -7
  5. package/dist/cjs/labs/next/presets/universal.js +313 -0
  6. package/dist/cjs/plugins/card/ui/EditLinkToolbar.js +24 -5
  7. package/dist/cjs/plugins/floating-toolbar/index.js +6 -0
  8. package/dist/cjs/plugins/floating-toolbar/pm-plugins/force-focus/index.js +72 -0
  9. package/dist/cjs/plugins/floating-toolbar/ui/Toolbar.js +12 -9
  10. package/dist/cjs/plugins/insert-block/ui/ToolbarInsertBlock/block-insert-element-browser.js +3 -1
  11. package/dist/cjs/plugins/insert-block/ui/ToolbarInsertBlock/index.js +3 -2
  12. package/dist/cjs/plugins/list/actions/outdent-list-items-selected.js +40 -1
  13. package/dist/cjs/plugins/list/commands/outdent-list.js +22 -9
  14. package/dist/cjs/plugins/list/pm-plugins/input-rules/create-list-input-rule.js +30 -12
  15. package/dist/cjs/plugins/list/utils/analytics.js +18 -2
  16. package/dist/cjs/plugins/media/commands/linking.js +2 -2
  17. package/dist/cjs/plugins/media/pm-plugins/alt-text/index.js +1 -12
  18. package/dist/cjs/plugins/media/toolbar/alt-text.js +14 -3
  19. package/dist/cjs/plugins/media/toolbar/linking-toolbar-appearance.js +6 -6
  20. package/dist/cjs/plugins/media/toolbar/linking.js +7 -1
  21. package/dist/cjs/plugins/paste/handlers.js +7 -5
  22. package/dist/cjs/plugins/placeholder/index.js +1 -0
  23. package/dist/cjs/plugins/placeholder/styles.js +1 -1
  24. package/dist/cjs/plugins/toolbar-lists-indentation/ui/Toolbar.js +17 -0
  25. package/dist/cjs/plugins/type-ahead/ui/InputQuery.js +13 -13
  26. package/dist/cjs/utils/document.js +3 -2
  27. package/dist/cjs/utils/input-rules.js +3 -2
  28. package/dist/cjs/utils/lists.js +31 -0
  29. package/dist/cjs/utils/prepare-quick-insert-provider.js +22 -0
  30. package/dist/cjs/version-wrapper.js +1 -1
  31. package/dist/cjs/version.json +1 -1
  32. package/dist/es2019/create-editor/ReactEditorView.js +1 -1
  33. package/dist/es2019/create-editor/create-plugins-list.js +5 -293
  34. package/dist/es2019/editor.js +5 -7
  35. package/dist/es2019/labs/next/presets/universal.js +311 -0
  36. package/dist/es2019/plugins/card/ui/EditLinkToolbar.js +19 -2
  37. package/dist/es2019/plugins/floating-toolbar/index.js +4 -0
  38. package/dist/es2019/plugins/floating-toolbar/pm-plugins/force-focus/index.js +59 -0
  39. package/dist/es2019/plugins/floating-toolbar/ui/Toolbar.js +6 -3
  40. package/dist/es2019/plugins/insert-block/ui/ToolbarInsertBlock/block-insert-element-browser.js +3 -1
  41. package/dist/es2019/plugins/insert-block/ui/ToolbarInsertBlock/index.js +3 -2
  42. package/dist/es2019/plugins/list/actions/outdent-list-items-selected.js +41 -1
  43. package/dist/es2019/plugins/list/commands/outdent-list.js +16 -1
  44. package/dist/es2019/plugins/list/pm-plugins/input-rules/create-list-input-rule.js +29 -11
  45. package/dist/es2019/plugins/list/utils/analytics.js +12 -0
  46. package/dist/es2019/plugins/media/commands/linking.js +2 -2
  47. package/dist/es2019/plugins/media/pm-plugins/alt-text/index.js +1 -12
  48. package/dist/es2019/plugins/media/toolbar/alt-text.js +14 -3
  49. package/dist/es2019/plugins/media/toolbar/linking-toolbar-appearance.js +6 -6
  50. package/dist/es2019/plugins/media/toolbar/linking.js +9 -1
  51. package/dist/es2019/plugins/paste/handlers.js +8 -6
  52. package/dist/es2019/plugins/placeholder/index.js +1 -0
  53. package/dist/es2019/plugins/placeholder/styles.js +7 -0
  54. package/dist/es2019/plugins/toolbar-lists-indentation/ui/Toolbar.js +17 -0
  55. package/dist/es2019/plugins/type-ahead/ui/InputQuery.js +13 -13
  56. package/dist/es2019/utils/document.js +3 -2
  57. package/dist/es2019/utils/input-rules.js +3 -2
  58. package/dist/es2019/utils/lists.js +26 -0
  59. package/dist/es2019/utils/prepare-quick-insert-provider.js +17 -0
  60. package/dist/es2019/version-wrapper.js +1 -1
  61. package/dist/es2019/version.json +1 -1
  62. package/dist/esm/create-editor/ReactEditorView.js +1 -1
  63. package/dist/esm/create-editor/create-plugins-list.js +6 -287
  64. package/dist/esm/editor.js +5 -7
  65. package/dist/esm/labs/next/presets/universal.js +306 -0
  66. package/dist/esm/plugins/card/ui/EditLinkToolbar.js +24 -5
  67. package/dist/esm/plugins/floating-toolbar/index.js +6 -0
  68. package/dist/esm/plugins/floating-toolbar/pm-plugins/force-focus/index.js +62 -0
  69. package/dist/esm/plugins/floating-toolbar/ui/Toolbar.js +12 -9
  70. package/dist/esm/plugins/insert-block/ui/ToolbarInsertBlock/block-insert-element-browser.js +3 -1
  71. package/dist/esm/plugins/insert-block/ui/ToolbarInsertBlock/index.js +3 -2
  72. package/dist/esm/plugins/list/actions/outdent-list-items-selected.js +40 -1
  73. package/dist/esm/plugins/list/commands/outdent-list.js +15 -2
  74. package/dist/esm/plugins/list/pm-plugins/input-rules/create-list-input-rule.js +30 -12
  75. package/dist/esm/plugins/list/utils/analytics.js +12 -0
  76. package/dist/esm/plugins/media/commands/linking.js +2 -2
  77. package/dist/esm/plugins/media/pm-plugins/alt-text/index.js +1 -12
  78. package/dist/esm/plugins/media/toolbar/alt-text.js +14 -3
  79. package/dist/esm/plugins/media/toolbar/linking-toolbar-appearance.js +6 -6
  80. package/dist/esm/plugins/media/toolbar/linking.js +7 -1
  81. package/dist/esm/plugins/paste/handlers.js +8 -6
  82. package/dist/esm/plugins/placeholder/index.js +1 -0
  83. package/dist/esm/plugins/placeholder/styles.js +1 -1
  84. package/dist/esm/plugins/toolbar-lists-indentation/ui/Toolbar.js +17 -0
  85. package/dist/esm/plugins/type-ahead/ui/InputQuery.js +13 -13
  86. package/dist/esm/utils/document.js +3 -2
  87. package/dist/esm/utils/input-rules.js +3 -2
  88. package/dist/esm/utils/lists.js +24 -0
  89. package/dist/esm/utils/prepare-quick-insert-provider.js +17 -0
  90. package/dist/esm/version-wrapper.js +1 -1
  91. package/dist/esm/version.json +1 -1
  92. package/dist/types/create-editor/create-plugins-list.d.ts +5 -4
  93. package/dist/types/{plugins/feature-flags-context → create-editor}/feature-flags-from-props.d.ts +2 -2
  94. package/dist/types/labs/next/presets/universal.d.ts +30 -0
  95. package/dist/types/plugins/card/ui/EditLinkToolbar.d.ts +4 -0
  96. package/dist/types/plugins/expand/index.d.ts +4 -1
  97. package/dist/types/plugins/floating-toolbar/pm-plugins/force-focus/index.d.ts +18 -0
  98. package/dist/types/plugins/list/utils/analytics.d.ts +5 -2
  99. package/dist/types/plugins/media/commands/linking.d.ts +1 -1
  100. package/dist/types/types/editor-props.d.ts +96 -92
  101. package/dist/types/utils/input-rules.d.ts +2 -1
  102. package/dist/types/utils/lists.d.ts +2 -0
  103. package/dist/types/utils/prepare-quick-insert-provider.d.ts +16 -0
  104. package/package.json +14 -15
  105. package/report.api.md +89 -79
  106. /package/dist/cjs/{plugins/feature-flags-context → create-editor}/feature-flags-from-props.js +0 -0
  107. /package/dist/es2019/{plugins/feature-flags-context → create-editor}/feature-flags-from-props.js +0 -0
  108. /package/dist/esm/{plugins/feature-flags-context → create-editor}/feature-flags-from-props.js +0 -0
@@ -1,3 +1,4 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
1
2
  import React from 'react';
2
3
  import HyperlinkToolbar from '../../hyperlink/ui/HyperlinkAddToolbar';
3
4
  import { showLinkToolbar, hideLinkToolbar } from '../pm-plugins/actions';
@@ -7,7 +8,17 @@ import { changeSelectedCardToLink, updateCard } from '../pm-plugins/doc';
7
8
  import { findCardInfo, displayInfoForCard } from '../utils';
8
9
  import { NodeSelection } from 'prosemirror-state';
9
10
  import { buildEditLinkPayload } from '../../../utils/linking-utils';
11
+ import { forceFocusSelector } from '../../floating-toolbar/pm-plugins/force-focus';
12
+ import { withOuterListeners } from '@atlaskit/editor-common/ui';
13
+ import { linkToolbarMessages } from '../../../messages';
14
+ const HyperLinkToolbarWithListeners = withOuterListeners(HyperlinkToolbar);
10
15
  export class EditLinkToolbar extends React.Component {
16
+ constructor(...args) {
17
+ super(...args);
18
+ _defineProperty(this, "handleEsc", e => {
19
+ forceFocusSelector(`[aria-label="${linkToolbarMessages.editLink.defaultMessage}"]`, this.props.view);
20
+ });
21
+ }
11
22
  componentDidUpdate(prevProps) {
12
23
  if (prevProps.node !== this.props.node) {
13
24
  this.hideLinkToolbar();
@@ -16,6 +27,11 @@ export class EditLinkToolbar extends React.Component {
16
27
  componentWillUnmount() {
17
28
  this.hideLinkToolbar();
18
29
  }
30
+
31
+ /** Focus should move to the 'Edit link' button when the toolbar closes
32
+ * and not close the floating toolbar.
33
+ */
34
+
19
35
  hideLinkToolbar() {
20
36
  const {
21
37
  view
@@ -31,12 +47,13 @@ export class EditLinkToolbar extends React.Component {
31
47
  view,
32
48
  onSubmit
33
49
  } = this.props;
34
- return /*#__PURE__*/React.createElement(HyperlinkToolbar, {
50
+ return /*#__PURE__*/React.createElement(HyperLinkToolbarWithListeners, {
35
51
  view: view,
36
52
  linkPickerOptions: linkPickerOptions,
37
53
  providerFactory: providerFactory,
38
54
  displayUrl: url,
39
- displayText: text
55
+ displayText: text,
56
+ handleEscapeKeydown: this.handleEsc
40
57
  // Assumes that the smart card link picker can only ever be invoked by clicking "edit"
41
58
  // via the floating toolbar
42
59
  ,
@@ -21,6 +21,7 @@ import { findNode } from './utils';
21
21
  import { ErrorBoundary } from '../../ui/ErrorBoundary';
22
22
  import { getFeatureFlags } from '../feature-flags-context';
23
23
  import { processCopyButtonItems } from '../copy-button/toolbar';
24
+ import forceFocusPlugin from './pm-plugins/force-focus';
24
25
  export const getRelevantConfig = (selection, configs) => {
25
26
  // node selections always take precedence, see if
26
27
  let configPair;
@@ -133,6 +134,9 @@ const floatingToolbarPlugin = () => ({
133
134
  plugin: ({
134
135
  dispatch
135
136
  }) => floatingToolbarDataPluginFactory(dispatch)
137
+ }, {
138
+ name: 'forceFocus',
139
+ plugin: () => forceFocusPlugin()
136
140
  }];
137
141
  },
138
142
  contentComponent({
@@ -0,0 +1,59 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { PluginKey } from 'prosemirror-state';
3
+ export const forceFocusStateKey = new PluginKey('forceFocusStatePlugin');
4
+ /**
5
+ * Used in cases where a floating toolbar button opens a submenu which destroys
6
+ * the button, but the user has pressed ESC to close the submenu and focus needs
7
+ * to move back to the button. */
8
+ export default (() => new SafePlugin({
9
+ key: forceFocusStateKey,
10
+ state: {
11
+ init: () => ({
12
+ selector: null
13
+ }),
14
+ apply: (tr, prevState) => {
15
+ const meta = tr.getMeta(forceFocusStateKey);
16
+ if (meta && 'selector' in meta) {
17
+ return {
18
+ selector: meta.selector
19
+ };
20
+ }
21
+ return prevState;
22
+ }
23
+ }
24
+ }));
25
+
26
+ /**
27
+ * The provided selector should be the floating toolbar button that needs focus.
28
+ */
29
+ export function forceFocusSelector(selector, view) {
30
+ if (view) {
31
+ return view.dispatch(view.state.tr.setMeta(forceFocusStateKey, {
32
+ selector
33
+ }));
34
+ }
35
+ }
36
+
37
+ /**
38
+ * If a selector is set and the element exists, focus it.
39
+ */
40
+ export function checkShouldForceFocusAndApply(view) {
41
+ const state = view === null || view === void 0 ? void 0 : view.state;
42
+ if (state) {
43
+ const {
44
+ selector
45
+ } = forceFocusStateKey.getState(state);
46
+ if (selector) {
47
+ const focusableElement = document.querySelector(selector);
48
+ if (focusableElement) {
49
+ focusableElement.scrollIntoView({
50
+ behavior: 'smooth',
51
+ block: 'center',
52
+ inline: 'nearest'
53
+ });
54
+ focusableElement.focus();
55
+ forceFocusSelector(null, view);
56
+ }
57
+ }
58
+ }
59
+ }
@@ -23,8 +23,10 @@ import Announcer from '../../../utils/announcer/announcer';
23
23
  import { injectIntl } from 'react-intl-next';
24
24
  import messages from './messages';
25
25
  import { decorationStateKey, ACTIONS } from '../../base/pm-plugins/decoration';
26
+ import { clearHoverSelection } from '@atlaskit/editor-plugin-table/commands';
26
27
  import ScrollButtons from './ScrollButtons';
27
28
  import { ToolbarArrowKeyNavigationProvider } from '../../../ui/ToolbarArrowKeyNavigationProvider';
29
+ import { checkShouldForceFocusAndApply, forceFocusSelector } from '../pm-plugins/force-focus';
28
30
  const akGridSize = gridSize();
29
31
  const ToolbarItems = /*#__PURE__*/React.memo(({
30
32
  items,
@@ -336,10 +338,8 @@ class Toolbar extends Component {
336
338
  state,
337
339
  dispatch
338
340
  } = this.props.editorView;
339
- // tables use their own decorations
340
- // TODO fix for tables https://product-fabric.atlassian.net/jira/servicedesk/projects/DTR/queues/issue/DTR-617
341
341
  if (table) {
342
- return null;
342
+ return clearHoverSelection()(state, dispatch);
343
343
  }
344
344
  dispatch(state.tr.setMeta(decorationStateKey, {
345
345
  action: ACTIONS.DECORATION_REMOVE
@@ -366,6 +366,8 @@ class Toolbar extends Component {
366
366
  });
367
367
  }
368
368
  componentDidUpdate(prevProps) {
369
+ var _this$props;
370
+ checkShouldForceFocusAndApply((_this$props = this.props) === null || _this$props === void 0 ? void 0 : _this$props.editorView);
369
371
  if (this.props.node !== prevProps.node) {
370
372
  this.resetStyling({
371
373
  table: (prevProps === null || prevProps === void 0 ? void 0 : prevProps.node.type.name) === 'table'
@@ -373,6 +375,7 @@ class Toolbar extends Component {
373
375
  }
374
376
  }
375
377
  componentWillUnmount() {
378
+ forceFocusSelector(null, this.props.editorView);
376
379
  this.resetStyling({
377
380
  table: this.props.node.type.name === 'table'
378
381
  });
@@ -10,7 +10,9 @@ export const BlockInsertElementBrowser = props => {
10
10
  offset: [0, 3],
11
11
  mountTo: props.popupsMountPoint,
12
12
  boundariesElement: props.popupsBoundariesElement,
13
- scrollableElement: props.popupsScrollableElement
13
+ scrollableElement: props.popupsScrollableElement,
14
+ preventOverflow: true,
15
+ alignX: "right"
14
16
  }, /*#__PURE__*/React.createElement(InsertMenu, {
15
17
  editorView: props.editorView,
16
18
  dropdownItems: props.items,
@@ -459,7 +459,8 @@ export class ToolbarInsertBlock extends React.PureComponent {
459
459
  var _this$props$isDisable, _this$props$replacePl;
460
460
  const {
461
461
  buttons,
462
- dropdownItems
462
+ dropdownItems,
463
+ emojiPickerOpen
463
464
  } = this.state;
464
465
  const {
465
466
  isDisabled,
@@ -478,7 +479,7 @@ export class ToolbarInsertBlock extends React.PureComponent {
478
479
  spacing: isReducedSpacing ? 'none' : 'default',
479
480
  disabled: isDisabled || btn.isDisabled,
480
481
  iconBefore: btn.elemBefore,
481
- selected: btn.isActive,
482
+ selected: btn.value.name === 'emoji' && emojiPickerOpen || btn.isActive,
482
483
  title: btn.title,
483
484
  "aria-label": btn['aria-label'],
484
485
  "aria-haspopup": btn['aria-haspopup'],
@@ -7,6 +7,8 @@ import { findFirstParentListItemNode, findRootParentListNode } from '../utils/fi
7
7
  import { normalizeListItemsSelection, createListNodeRange } from '../utils/selection';
8
8
  import { GapCursorSelection } from '../../selection/gap-cursor-selection';
9
9
  import { getFeatureFlags } from '../../feature-flags-context';
10
+ import { storeRestartListsAttributes, getRestartListsAttributes } from '../utils/analytics';
11
+ import { OUTDENT_SCENARIOS } from '@atlaskit/editor-common/analytics';
10
12
  export const outdentListItemsSelected = (tr, state) => {
11
13
  const originalSelection = tr.selection;
12
14
  const normalizedSelection = normalizeListItemsSelection({
@@ -199,22 +201,55 @@ const extractListItemsRangeFromList = ({
199
201
  }
200
202
  let nextList = list.copy(Fragment.empty);
201
203
  const featureFlags = getFeatureFlags(state);
204
+ let nextListStartNumber;
202
205
  if (featureFlags !== null && featureFlags !== void 0 && featureFlags.restartNumberedLists) {
203
206
  // if splitting a numbered list, keep the splitted item
204
207
  // counter as the start of the next (second half) list (instead
205
208
  // of reverting back to 1 as a starting number)
206
209
  const order = getOrderFromOrderedListNode(list);
207
210
  if (list.type.name === 'orderedList') {
211
+ nextListStartNumber = range.endIndex - 1 + order;
208
212
  nextList.attrs = {
209
213
  ...nextList.attrs,
210
- order: range.endIndex - 1 + order
214
+ order: nextListStartNumber
211
215
  };
216
+ const {
217
+ splitListStartNumber
218
+ } = getRestartListsAttributes(tr);
219
+ if (typeof splitListStartNumber !== 'number') {
220
+ storeRestartListsAttributes(tr, {
221
+ splitListStartNumber: nextListStartNumber
222
+ });
223
+ }
212
224
  }
213
225
  }
214
226
  const nextListFragment = listItemContent.append(Fragment.from(nextList));
227
+ if (featureFlags !== null && featureFlags !== void 0 && featureFlags.restartNumberedLists) {
228
+ // if the split list with nextListStartNumber is below another list
229
+ // with order (e.g due to multi-level indent items being lifted), track the
230
+ // list above's order instead, as it will be the split list's order after sibling joins
231
+ nextListFragment.forEach((node, _offset, index) => {
232
+ var _node$attrs;
233
+ if (node.type.name === 'orderedList' && ((_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.order) === nextListStartNumber) {
234
+ var _prev$attrs;
235
+ const prev = nextListFragment.child(index - 1);
236
+ if ((prev === null || prev === void 0 ? void 0 : (_prev$attrs = prev.attrs) === null || _prev$attrs === void 0 ? void 0 : _prev$attrs.order) >= 0) {
237
+ var _prev$attrs2;
238
+ storeRestartListsAttributes(tr, {
239
+ splitListStartNumber: prev === null || prev === void 0 ? void 0 : (_prev$attrs2 = prev.attrs) === null || _prev$attrs2 === void 0 ? void 0 : _prev$attrs2.order
240
+ });
241
+ }
242
+ }
243
+ });
244
+ }
215
245
  if (isTheEntireList) {
216
246
  const slice = new Slice(listItemContent, 0, 0);
217
247
  const step = new ReplaceStep($start.pos - 1, range.end + 1, slice, false);
248
+ if (featureFlags !== null && featureFlags !== void 0 && featureFlags.restartNumberedLists) {
249
+ storeRestartListsAttributes(tr, {
250
+ outdentScenario: undefined
251
+ });
252
+ }
218
253
  tr.step(step);
219
254
  } else if (isAtTop) {
220
255
  const slice = new Slice(nextListFragment, 0, 1);
@@ -225,6 +260,11 @@ const extractListItemsRangeFromList = ({
225
260
  const step = new ReplaceStep($start.pos, listEnd + 1, slice, false);
226
261
  tr.step(step);
227
262
  } else {
263
+ if (featureFlags !== null && featureFlags !== void 0 && featureFlags.restartNumberedLists) {
264
+ storeRestartListsAttributes(tr, {
265
+ outdentScenario: OUTDENT_SCENARIOS.SPLIT_LIST
266
+ });
267
+ }
228
268
  const slice = new Slice(nextListFragment, 1, 1);
229
269
  const step = new ReplaceAroundStep($start.pos, listEnd, range.end, listEnd, slice, slice.size, false);
230
270
  tr.step(step);
@@ -1,10 +1,12 @@
1
+ import { OUTDENT_SCENARIOS } from '@atlaskit/editor-common/analytics';
1
2
  import { addAnalytics, ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '../../analytics';
2
3
  import { isInsideListItem, isInsideTableCell } from '../utils/selection';
3
4
  import { isBulletList } from '../utils/node';
4
5
  import { findFirstParentListNode } from '../utils/find';
5
- import { getCommonListAnalyticsAttributes } from '../utils/analytics';
6
+ import { getCommonListAnalyticsAttributes, getRestartListsAttributes } from '../utils/analytics';
6
7
  import { outdentListItemsSelected as outdentListAction } from '../actions/outdent-list-items-selected';
7
8
  import { closeHistory } from 'prosemirror-history';
9
+ import { getFeatureFlags } from '../../feature-flags-context';
8
10
  export function outdentList(inputMethod = INPUT_METHOD.KEYBOARD) {
9
11
  return function (state, dispatch) {
10
12
  if (!isInsideListItem(state)) {
@@ -29,6 +31,18 @@ export function outdentList(inputMethod = INPUT_METHOD.KEYBOARD) {
29
31
  // If inside table cell and can't outdent list, then let it handle by table keymap
30
32
  return !isInsideTableCell(state);
31
33
  }
34
+ const featureFlags = getFeatureFlags(state);
35
+ const restartListsAttributes = {};
36
+ if (featureFlags !== null && featureFlags !== void 0 && featureFlags.restartNumberedLists) {
37
+ const {
38
+ outdentScenario,
39
+ splitListStartNumber
40
+ } = getRestartListsAttributes(customTr);
41
+ if (outdentScenario === OUTDENT_SCENARIOS.SPLIT_LIST) {
42
+ restartListsAttributes.outdentScenario = outdentScenario;
43
+ restartListsAttributes.splitListStartNumber = splitListStartNumber;
44
+ }
45
+ }
32
46
  addAnalytics(state, customTr, {
33
47
  action: ACTION.OUTDENTED,
34
48
  actionSubject: ACTION_SUBJECT.LIST,
@@ -36,6 +50,7 @@ export function outdentList(inputMethod = INPUT_METHOD.KEYBOARD) {
36
50
  eventType: EVENT_TYPE.TRACK,
37
51
  attributes: {
38
52
  ...getCommonListAnalyticsAttributes(state),
53
+ ...restartListsAttributes,
39
54
  inputMethod
40
55
  }
41
56
  });
@@ -1,27 +1,45 @@
1
1
  import { ruleWithAnalytics, createWrappingJoinRule } from '../../../../utils/input-rules';
2
2
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '../../../analytics';
3
+ import { JOIN_SCENARIOS_WHEN_TYPING_TO_INSERT_LIST } from '@atlaskit/editor-common/analytics';
4
+ const getOrder = matchResult => Number(matchResult[1]);
3
5
  export function createRuleForListType({
4
6
  listType,
5
7
  expression,
6
8
  featureFlags
7
9
  }) {
10
+ let joinScenario = JOIN_SCENARIOS_WHEN_TYPING_TO_INSERT_LIST.NO_JOIN;
8
11
  const isBulletList = listType.name === 'bulletList';
9
12
  const actionSubjectId = isBulletList ? ACTION_SUBJECT_ID.FORMAT_LIST_BULLET : ACTION_SUBJECT_ID.FORMAT_LIST_NUMBER;
10
- const analyticsPayload = {
11
- action: ACTION.INSERTED,
12
- actionSubject: ACTION_SUBJECT.LIST,
13
- actionSubjectId,
14
- eventType: EVENT_TYPE.TRACK,
15
- attributes: {
16
- inputMethod: INPUT_METHOD.FORMATTING
13
+ const getAnalyticsPayload = (state, matchResult) => {
14
+ const analyticsPayload = {
15
+ action: ACTION.INSERTED,
16
+ actionSubject: ACTION_SUBJECT.LIST,
17
+ actionSubjectId,
18
+ eventType: EVENT_TYPE.TRACK,
19
+ attributes: {
20
+ inputMethod: INPUT_METHOD.FORMATTING
21
+ }
22
+ };
23
+ if (featureFlags !== null && featureFlags !== void 0 && featureFlags.restartNumberedLists && listType === state.schema.nodes.orderedList && analyticsPayload.attributes) {
24
+ analyticsPayload.attributes.listStartNumber = getOrder(matchResult);
25
+ analyticsPayload.attributes.joinScenario = joinScenario;
26
+ // we reset the tracked joinScenario after storing it in the event payload
27
+ joinScenario = JOIN_SCENARIOS_WHEN_TYPING_TO_INSERT_LIST.NO_JOIN;
28
+ }
29
+ return analyticsPayload;
30
+ };
31
+ const joinToNeighbourIfSameListType = (_, node, scenario) => {
32
+ const shouldJoin = node.type === listType;
33
+ if (shouldJoin) {
34
+ joinScenario = scenario;
17
35
  }
36
+ return shouldJoin;
18
37
  };
19
- const shouldJoinNextNodeWhen = (_, node) => node.type === listType;
20
38
  let getAttrs = {};
21
39
  if (featureFlags !== null && featureFlags !== void 0 && featureFlags.restartNumberedLists) {
22
40
  getAttrs = matchResult => {
23
41
  return {
24
- order: Number(matchResult[1])
42
+ order: getOrder(matchResult)
25
43
  };
26
44
  };
27
45
  }
@@ -29,7 +47,7 @@ export function createRuleForListType({
29
47
  match: expression,
30
48
  nodeType: listType,
31
49
  getAttrs,
32
- joinPredicate: shouldJoinNextNodeWhen
50
+ joinPredicate: joinToNeighbourIfSameListType
33
51
  });
34
- return ruleWithAnalytics(analyticsPayload)(inputRule);
52
+ return ruleWithAnalytics(getAnalyticsPayload)(inputRule);
35
53
  }
@@ -33,4 +33,16 @@ export const countListItemsInSelection = state => {
33
33
  }
34
34
  });
35
35
  return count;
36
+ };
37
+ export const RESTART_LISTS_ANALYTICS_KEY = 'restartListsAnalytics';
38
+ export const getRestartListsAttributes = tr => {
39
+ var _tr$getMeta;
40
+ return (_tr$getMeta = tr.getMeta(RESTART_LISTS_ANALYTICS_KEY)) !== null && _tr$getMeta !== void 0 ? _tr$getMeta : {};
41
+ };
42
+ export const storeRestartListsAttributes = (tr, attributes) => {
43
+ const meta = getRestartListsAttributes(tr);
44
+ tr.setMeta(RESTART_LISTS_ANALYTICS_KEY, {
45
+ ...meta,
46
+ ...attributes
47
+ });
36
48
  };
@@ -45,11 +45,11 @@ export const showLinkingToolbarWithMediaTypeCheck = (editorState, dispatch, edit
45
45
  const hideLinkingToolbarCommand = createMediaLinkingCommand({
46
46
  type: MediaLinkingActionsTypes.hideToolbar
47
47
  });
48
- export const hideLinkingToolbar = (state, dispatch, view) => {
48
+ export const hideLinkingToolbar = (state, dispatch, view, focusFloatingToolbar) => {
49
49
  hideLinkingToolbarCommand(state, dispatch, view);
50
50
 
51
51
  // restore focus on the editor so keyboard shortcuts aren't lost to the browser
52
- if (view) {
52
+ if (view && !focusFloatingToolbar) {
53
53
  view.focus();
54
54
  }
55
55
  };
@@ -27,18 +27,7 @@ export const createPlugin = ({
27
27
  state: createPluginState(dispatch, {
28
28
  isAltTextEditorOpen: false
29
29
  }),
30
- key: pluginKey,
31
- view: () => {
32
- return {
33
- update: (view, prev) => {
34
- const pluginState = getPluginState(view.state);
35
- const oldPluginState = getPluginState(prev);
36
- if (pluginState && oldPluginState && oldPluginState.isAltTextEditorOpen && !pluginState.isAltTextEditorOpen && !view.hasFocus()) {
37
- view.focus();
38
- }
39
- }
40
- };
41
- }
30
+ key: pluginKey
42
31
  });
43
32
  };
44
33
  export { createCommand, getPluginState };
@@ -6,6 +6,9 @@ import AltTextEdit from '../pm-plugins/alt-text/ui/AltTextEdit';
6
6
  import { CONTAINER_WIDTH_IN_PX } from '../pm-plugins/alt-text/ui/AltTextEdit';
7
7
  import { getMediaNodeFromSelection } from '../utils/media-common';
8
8
  import { ClassNames } from '../pm-plugins/alt-text/style';
9
+ import { withOuterListeners } from '@atlaskit/editor-common/ui';
10
+ import { forceFocusSelector } from '../../floating-toolbar/pm-plugins/force-focus';
11
+ const testId = 'alt-text-edit-button';
9
12
  export const altTextButton = (intl, state) => {
10
13
  const mediaNode = getMediaNodeFromSelection(state);
11
14
  const message = mediaNode && mediaNode.attrs.alt ? messages.editAltText : messages.altText;
@@ -16,13 +19,14 @@ export const altTextButton = (intl, state) => {
16
19
  type: 'button',
17
20
  onClick: openMediaAltTextMenu,
18
21
  showTitle: true,
19
- testId: 'alt-text-edit-button',
22
+ testId,
20
23
  tooltipContent: /*#__PURE__*/React.createElement(ToolTipContent, {
21
24
  description: title,
22
25
  keymap: addAltText
23
26
  })
24
27
  };
25
28
  };
29
+ const AltTextEditWithListeners = withOuterListeners(AltTextEdit);
26
30
  export const altTextEditComponent = options => {
27
31
  return {
28
32
  type: 'custom',
@@ -36,11 +40,18 @@ export const altTextEditComponent = options => {
36
40
  if (!mediaNode) {
37
41
  return null;
38
42
  }
39
- return /*#__PURE__*/React.createElement(AltTextEdit, {
43
+ /** Focus should move to the 'Alt text' button when the toolbar closes
44
+ * and not close the floating toolbar.
45
+ */
46
+ const handleEsc = e => {
47
+ forceFocusSelector(`[data-testid="${testId}"]`, view);
48
+ };
49
+ return /*#__PURE__*/React.createElement(AltTextEditWithListeners, {
40
50
  view: view,
41
51
  key: idx,
42
52
  value: mediaNode.attrs.alt,
43
- altTextValidator: options && options.altTextValidator
53
+ altTextValidator: options && options.altTextValidator,
54
+ handleEscapeKeydown: handleEsc
44
55
  });
45
56
  }
46
57
  };
@@ -25,22 +25,21 @@ export const LinkToolbarAppearance = ({
25
25
  onEditLink,
26
26
  onOpenLink
27
27
  }) => {
28
- const [showLinkingControls, setShowLinkingControls] = useState(false);
28
+ const [showLinkingControls, setShowLinkingControls] = useState(true);
29
29
  useEffect(() => {
30
30
  var _stateKey$getState;
31
- setShowLinkingControls(false);
32
31
  const mediaNode = currentMediaNode(editorState);
33
32
  if (!mediaNode) {
33
+ setShowLinkingControls(false);
34
34
  return;
35
35
  }
36
36
  const mediaClientConfig = (_stateKey$getState = stateKey.getState(editorState)) === null || _stateKey$getState === void 0 ? void 0 : _stateKey$getState.mediaClientConfig;
37
37
  if (!mediaClientConfig) {
38
+ setShowLinkingControls(false);
38
39
  return;
39
40
  }
40
41
  checkMediaType(mediaNode, mediaClientConfig).then(mediaType => {
41
- if (mediaType === 'external' || mediaType === 'image') {
42
- setShowLinkingControls(true);
43
- }
42
+ setShowLinkingControls(mediaType === 'external' || mediaType === 'image');
44
43
  });
45
44
  }, [editorState]);
46
45
  if (!showLinkingControls) {
@@ -58,7 +57,8 @@ export const LinkToolbarAppearance = ({
58
57
  tooltipContent: jsx(ToolTipContent, {
59
58
  description: title,
60
59
  keymap: addLink
61
- })
60
+ }),
61
+ testId: "edit-link-button"
62
62
  }, title)), jsx("div", {
63
63
  css: wrapper
64
64
  }, jsx(Separator, null)), jsx(ToolbarButton, {
@@ -3,6 +3,8 @@ import { hideLinkingToolbar, setUrlToMedia, unlink } from '../commands/linking';
3
3
  import { getMediaLinkingState } from '../pm-plugins/linking';
4
4
  import MediaLinkingToolbar from '../ui/MediaLinkingToolbar';
5
5
  import { RECENT_SEARCH_HEIGHT_IN_PX, RECENT_SEARCH_WIDTH_IN_PX } from '../../../ui/LinkSearch/ToolbarComponents';
6
+ import { forceFocusSelector } from '../../floating-toolbar/pm-plugins/force-focus';
7
+ const FORCE_FOCUS_SELECTOR = '[data-testid="add-link-button"],[data-testid="edit-link-button"]';
6
8
  export function shouldShowMediaLinkToolbar(editorState) {
7
9
  const mediaLinkingState = getMediaLinkingState(editorState);
8
10
  if (!mediaLinkingState || mediaLinkingState.mediaPos === null) {
@@ -61,7 +63,13 @@ export const getLinkingToolbar = (toolbarBaseConfig, mediaLinkingState, state, i
61
63
  }
62
64
  hideLinkingToolbar(view.state, view.dispatch, view);
63
65
  },
64
- onCancel: () => hideLinkingToolbar(view.state, view.dispatch, view),
66
+ onCancel: () => {
67
+ hideLinkingToolbar(view.state, view.dispatch, view, true);
68
+ /** Focus should move to the 'Add link' button when the toolbar closes
69
+ * and not close the floating toolbar.
70
+ */
71
+ forceFocusSelector(FORCE_FOCUS_SELECTOR, view);
72
+ },
65
73
  onSubmit: (href, meta) => {
66
74
  setUrlToMedia(href, meta.inputMethod)(view.state, view.dispatch, view);
67
75
  hideLinkingToolbar(view.state, view.dispatch, view);
@@ -1,7 +1,7 @@
1
1
  import { closeHistory } from 'prosemirror-history';
2
2
  import { Fragment, Node as PMNode, Slice } from 'prosemirror-model';
3
3
  import { TextSelection, NodeSelection } from 'prosemirror-state';
4
- import { findParentNodeOfType, hasParentNodeOfType, safeInsert } from 'prosemirror-utils';
4
+ import { canInsert, findParentNodeOfType, hasParentNodeOfType, safeInsert } from 'prosemirror-utils';
5
5
  import uuid from 'uuid/v4';
6
6
  import { replaceSelectedTable } from '@atlaskit/editor-tables/utils';
7
7
  import { compose, insideTable, isParagraph, isText, isLinkMark, insideTableCell, isInListItem } from '../../utils';
@@ -19,6 +19,7 @@ import { canLinkBeCreatedInRange } from '../hyperlink/pm-plugins/main';
19
19
  import { insertSliceForLists } from './edge-cases';
20
20
  import { startTrackingPastedMacroPositions, stopTrackingPastedMacroPositions } from './commands';
21
21
  import { getPluginState as getPastePluginState } from './pm-plugins/plugin-factory';
22
+ import { doesSelectionWhichStartsOrEndsInListContainEntireList } from '../../utils/lists';
22
23
 
23
24
  // remove text attribute from mention for copy/paste (GDPR)
24
25
  export function handleMention(slice, schema) {
@@ -93,7 +94,7 @@ export function handlePasteIntoTaskOrDecisionOrPanel(slice) {
93
94
  }
94
95
  const transformedSlice = compose.apply(null, filters)(slice);
95
96
  const tr = closeHistory(state.tr);
96
- const transformedSliceIsValidNode = transformedSlice.content.firstChild.type.inlineContent || ['decisionList', 'decisionItem', 'taskList', 'taskItem', 'panel'].includes(transformedSlice.content.firstChild.type.name);
97
+ const transformedSliceIsValidNode = transformedSlice.content.firstChild.type.inlineContent || ['decisionList', 'decisionItem', 'taskList', 'taskItem', 'panel'].includes(transformedSlice.content.firstChild.type.name) && !isInListItem(state);
97
98
  // If the slice or the selection are valid nodes to handle,
98
99
  // and the slice is not a whole node (i.e. openStart is 1 and openEnd is 0)
99
100
  // or the slice's first node is a paragraph,
@@ -120,11 +121,12 @@ export function handlePastePanelIntoList(slice) {
120
121
  const {
121
122
  selection
122
123
  } = tr;
123
-
124
124
  // Check this pasting action is related to copy content from panel node into a selected the list node
125
- const selectionParentListNode = selection.$to.node(selection.$to.depth - 1);
126
125
  const panelNode = slice.content.firstChild;
127
- if (!dispatch || !selectionParentListNode || (selectionParentListNode === null || selectionParentListNode === void 0 ? void 0 : selectionParentListNode.type) !== schema.nodes.listItem || !panelNode || (panelNode === null || panelNode === void 0 ? void 0 : panelNode.type) !== schema.nodes.panel || (panelNode === null || panelNode === void 0 ? void 0 : panelNode.content.firstChild) === undefined) {
126
+ const isSliceWholeNode = slice.openStart === 0 && slice.openEnd === 0;
127
+ const selectionParentListItemNode = selection.$to.node(selection.$to.depth - 1);
128
+ const sliceIsWholeNodeButShouldNotReplaceSelection = isSliceWholeNode && !doesSelectionWhichStartsOrEndsInListContainEntireList(selection);
129
+ if (!dispatch || !selectionParentListItemNode || (selectionParentListItemNode === null || selectionParentListItemNode === void 0 ? void 0 : selectionParentListItemNode.type) !== schema.nodes.listItem || !panelNode || (panelNode === null || panelNode === void 0 ? void 0 : panelNode.type) !== schema.nodes.panel || (panelNode === null || panelNode === void 0 ? void 0 : panelNode.content.firstChild) === undefined || sliceIsWholeNodeButShouldNotReplaceSelection) {
128
130
  return false;
129
131
  }
130
132
 
@@ -679,7 +681,7 @@ export function handleRichText(slice) {
679
681
  return false;
680
682
  }
681
683
  });
682
- if (insideTableCell(state) && (!doesBlockNodeExist || isInListItem(state)) || sliceHasList) {
684
+ if (insideTableCell(state) && (!doesBlockNodeExist || isInListItem(state) && canInsert(selection.$from, slice.content) && canInsert(selection.$to, slice.content)) || sliceHasList) {
683
685
  tr.replaceSelection(slice);
684
686
  } else {
685
687
  // need safeInsert rather than replaceSelection, so that nodes aren't split in half
@@ -32,6 +32,7 @@ export function createPlaceholderDecoration(editorState, placeholderText, pos =
32
32
  // when backspace in GBoard composition
33
33
  if (browser.android && browser.chrome) {
34
34
  const buffNode = document.createElement('span');
35
+ buffNode.setAttribute('class', 'placeholder-android');
35
36
  buffNode.setAttribute('contenteditable', 'true');
36
37
  buffNode.textContent = ' ';
37
38
  placeholderDecoration.appendChild(buffNode);
@@ -6,5 +6,12 @@ export const placeholderStyles = css`
6
6
  width: 100%;
7
7
  pointer-events: none;
8
8
  user-select: none;
9
+
10
+ .placeholder-android {
11
+ pointer-events: none;
12
+ outline: none;
13
+ user-select: none;
14
+ position: absolute;
15
+ }
9
16
  }
10
17
  `;