@atlaskit/editor-plugin-block-type 4.0.14 → 4.1.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cjs/blockTypePlugin.js +3 -0
  3. package/dist/cjs/pm-plugins/block-types.js +3 -1
  4. package/dist/cjs/pm-plugins/commands/block-type.js +58 -10
  5. package/dist/cjs/pm-plugins/commands/clear-formatting.js +58 -0
  6. package/dist/cjs/pm-plugins/main.js +6 -3
  7. package/dist/cjs/pm-plugins/ui/FloatingToolbarComponent.js +5 -0
  8. package/dist/cjs/pm-plugins/ui/PrimaryToolbarComponent.js +6 -1
  9. package/dist/cjs/pm-plugins/ui/ToolbarBlockType/index.js +82 -45
  10. package/dist/cjs/pm-plugins/ui/ToolbarBlockType/styled.js +15 -1
  11. package/dist/cjs/pm-plugins/utils.js +41 -2
  12. package/dist/es2019/blockTypePlugin.js +4 -1
  13. package/dist/es2019/pm-plugins/block-types.js +2 -0
  14. package/dist/es2019/pm-plugins/commands/block-type.js +49 -0
  15. package/dist/es2019/pm-plugins/commands/clear-formatting.js +50 -0
  16. package/dist/es2019/pm-plugins/main.js +7 -4
  17. package/dist/es2019/pm-plugins/ui/FloatingToolbarComponent.js +5 -0
  18. package/dist/es2019/pm-plugins/ui/PrimaryToolbarComponent.js +6 -1
  19. package/dist/es2019/pm-plugins/ui/ToolbarBlockType/index.js +83 -48
  20. package/dist/es2019/pm-plugins/ui/ToolbarBlockType/styled.js +14 -0
  21. package/dist/es2019/pm-plugins/utils.js +43 -2
  22. package/dist/esm/blockTypePlugin.js +4 -1
  23. package/dist/esm/pm-plugins/block-types.js +2 -0
  24. package/dist/esm/pm-plugins/commands/block-type.js +55 -9
  25. package/dist/esm/pm-plugins/commands/clear-formatting.js +50 -0
  26. package/dist/esm/pm-plugins/main.js +7 -4
  27. package/dist/esm/pm-plugins/ui/FloatingToolbarComponent.js +5 -0
  28. package/dist/esm/pm-plugins/ui/PrimaryToolbarComponent.js +6 -1
  29. package/dist/esm/pm-plugins/ui/ToolbarBlockType/index.js +84 -47
  30. package/dist/esm/pm-plugins/ui/ToolbarBlockType/styled.js +14 -0
  31. package/dist/esm/pm-plugins/utils.js +41 -2
  32. package/dist/types/blockTypePluginType.d.ts +1 -0
  33. package/dist/types/pm-plugins/block-types.d.ts +2 -0
  34. package/dist/types/pm-plugins/commands/block-type.d.ts +1 -0
  35. package/dist/types/pm-plugins/commands/clear-formatting.d.ts +8 -0
  36. package/dist/types/pm-plugins/main.d.ts +2 -7
  37. package/dist/types/pm-plugins/ui/ToolbarBlockType/index.d.ts +1 -0
  38. package/dist/types/pm-plugins/ui/ToolbarBlockType/styled.d.ts +1 -0
  39. package/dist/types/pm-plugins/utils.d.ts +3 -0
  40. package/dist/types-ts4.5/blockTypePluginType.d.ts +1 -0
  41. package/dist/types-ts4.5/pm-plugins/block-types.d.ts +2 -0
  42. package/dist/types-ts4.5/pm-plugins/commands/block-type.d.ts +1 -0
  43. package/dist/types-ts4.5/pm-plugins/commands/clear-formatting.d.ts +8 -0
  44. package/dist/types-ts4.5/pm-plugins/main.d.ts +2 -7
  45. package/dist/types-ts4.5/pm-plugins/ui/ToolbarBlockType/index.d.ts +1 -0
  46. package/dist/types-ts4.5/pm-plugins/ui/ToolbarBlockType/styled.d.ts +1 -0
  47. package/dist/types-ts4.5/pm-plugins/utils.d.ts +3 -0
  48. package/package.json +2 -2
@@ -4,6 +4,7 @@ import { filterChildrenBetween, wrapSelectionIn } from '@atlaskit/editor-common/
4
4
  import { Slice, Fragment } from '@atlaskit/editor-prosemirror/model';
5
5
  import { CellSelection } from '@atlaskit/editor-tables';
6
6
  import { HEADINGS_BY_NAME, NORMAL_TEXT } from '../block-types';
7
+ import { FORMATTING_NODE_TYPES, FORMATTING_MARK_TYPES, cellSelectionNodesBetween, formatTypes, clearNodeFormattingOnSelection } from './clear-formatting';
7
8
  import { wrapSelectionInBlockType } from './wrapSelectionIn';
8
9
  export function setBlockType(name) {
9
10
  return ({
@@ -117,6 +118,54 @@ export function setNormalText(fromBlockQuote) {
117
118
  return tr;
118
119
  };
119
120
  }
121
+ export function clearFormatting() {
122
+ return function ({
123
+ tr
124
+ }) {
125
+ const formattingCleared = [];
126
+ const schema = tr.doc.type.schema;
127
+ FORMATTING_MARK_TYPES.forEach(mark => {
128
+ const {
129
+ from,
130
+ to
131
+ } = tr.selection;
132
+ const markType = schema.marks[mark];
133
+ if (!markType) {
134
+ return;
135
+ }
136
+ if (tr.selection instanceof CellSelection) {
137
+ cellSelectionNodesBetween(tr.selection, tr.doc, (node, pos) => {
138
+ const isTableCell = node.type === schema.nodes.tableCell || node.type === schema.nodes.tableHeader;
139
+ if (!isTableCell) {
140
+ return true;
141
+ }
142
+ if (tr.doc.rangeHasMark(pos, pos + node.nodeSize, markType)) {
143
+ formattingCleared.push(formatTypes[mark]);
144
+ tr.removeMark(pos, pos + node.nodeSize, markType);
145
+ }
146
+ return false;
147
+ });
148
+ } else if (tr.doc.rangeHasMark(from, to, markType)) {
149
+ formattingCleared.push(formatTypes[mark]);
150
+ tr.removeMark(from, to, markType);
151
+ }
152
+ });
153
+ FORMATTING_NODE_TYPES.forEach(nodeName => {
154
+ const formattedNodeType = schema.nodes[nodeName];
155
+ const {
156
+ $from,
157
+ $to
158
+ } = tr.selection;
159
+ if (tr.selection instanceof CellSelection) {
160
+ cellSelectionNodesBetween(tr.selection, tr.doc, clearNodeFormattingOnSelection(schema, tr, formattedNodeType, nodeName, formattingCleared));
161
+ } else {
162
+ tr.doc.nodesBetween($from.pos, $to.pos, clearNodeFormattingOnSelection(schema, tr, formattedNodeType, nodeName, formattingCleared));
163
+ }
164
+ });
165
+ tr.setStoredMarks([]);
166
+ return tr;
167
+ };
168
+ }
120
169
  function withCurrentHeadingLevel(fn) {
121
170
  return ({
122
171
  tr
@@ -0,0 +1,50 @@
1
+ import { ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
2
+ import { liftTarget } from '@atlaskit/editor-prosemirror/transform';
3
+ // Functions duplicated from platform/packages/editor/editor-plugin-text-formatting/src/editor-commands/clear-formatting.ts
4
+ // TODO: Refactor to avoid duplication if platform_editor_blockquote_in_text_formatting_menu experiment is productionalised
5
+ export const FORMATTING_NODE_TYPES = ['heading', 'blockquote'];
6
+ export const FORMATTING_MARK_TYPES = ['em', 'code', 'strike', 'strong', 'underline', 'textColor', 'subsup', 'backgroundColor'];
7
+ export const formatTypes = {
8
+ em: ACTION_SUBJECT_ID.FORMAT_ITALIC,
9
+ code: ACTION_SUBJECT_ID.FORMAT_CODE,
10
+ strike: ACTION_SUBJECT_ID.FORMAT_STRIKE,
11
+ strong: ACTION_SUBJECT_ID.FORMAT_STRONG,
12
+ underline: ACTION_SUBJECT_ID.FORMAT_UNDERLINE,
13
+ textColor: ACTION_SUBJECT_ID.FORMAT_COLOR,
14
+ subsup: 'subsup',
15
+ backgroundColor: ACTION_SUBJECT_ID.FORMAT_BACKGROUND_COLOR
16
+ };
17
+ export const cellSelectionNodesBetween = (selection, doc, f, startPos
18
+ // eslint-disable-next-line @typescript-eslint/max-params
19
+ ) => {
20
+ selection.forEachCell((cell, cellPos) => {
21
+ doc.nodesBetween(cellPos, cellPos + cell.nodeSize, f, startPos);
22
+ });
23
+ };
24
+
25
+ // eslint-disable-next-line @typescript-eslint/max-params
26
+ export function clearNodeFormattingOnSelection(schema, tr, formattedNodeType, nodeName, formattingCleared) {
27
+ return function (node, pos) {
28
+ if (node.type === formattedNodeType) {
29
+ if (formattedNodeType.isTextblock) {
30
+ tr.setNodeMarkup(pos, schema.nodes.paragraph);
31
+ formattingCleared.push(nodeName);
32
+ return false;
33
+ } else {
34
+ // In case of panel or blockquote
35
+ const fromPos = tr.doc.resolve(pos + 1);
36
+ const toPos = tr.doc.resolve(pos + node.nodeSize - 1);
37
+ const nodeRange = fromPos.blockRange(toPos);
38
+ if (nodeRange) {
39
+ const targetLiftDepth = liftTarget(nodeRange);
40
+ if (targetLiftDepth || targetLiftDepth === 0) {
41
+ formattingCleared.push(nodeName);
42
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
43
+ tr.lift(nodeRange, targetLiftDepth);
44
+ }
45
+ }
46
+ }
47
+ }
48
+ return true;
49
+ };
50
+ }
@@ -6,7 +6,7 @@ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
6
6
  import { BLOCK_QUOTE, CODE_BLOCK, HEADING_1, HEADING_2, HEADING_3, HEADING_4, HEADING_5, HEADING_6, HEADINGS_BY_LEVEL, NORMAL_TEXT, OTHER, PANEL, TEXT_BLOCK_TYPES, WRAPPER_BLOCK_TYPES, getBlockTypesInDropdown } from './block-types';
7
7
  import { setHeadingWithAnalytics, setNormalTextWithAnalytics } from './commands/block-type';
8
8
  import { HEADING_KEYS } from './consts';
9
- import { areBlockTypesDisabled } from './utils';
9
+ import { areBlockTypesDisabled, checkFormattingIsPresent, hasBlockQuoteInOptions } from './utils';
10
10
  const blockTypeForNode = (node, schema) => {
11
11
  if (node.type === schema.nodes.heading) {
12
12
  const maybeNode = HEADINGS_BY_LEVEL[node.attrs['level']];
@@ -96,12 +96,14 @@ export const createPlugin = (editorAPI, dispatch, lastNodeMustBeParagraph, inclu
96
96
  const availableWrapperBlockTypes = WRAPPER_BLOCK_TYPES.filter(blockType => isBlockTypeSchemaSupported(blockType, state));
97
97
  const BLOCK_TYPES_IN_DROPDOWN = getBlockTypesInDropdown(includeBlockQuoteAsTextstyleOption);
98
98
  const availableBlockTypesInDropdown = BLOCK_TYPES_IN_DROPDOWN.filter(blockType => isBlockTypeSchemaSupported(blockType, state));
99
+ const formattingIsPresent = hasBlockQuoteInOptions(availableBlockTypesInDropdown) ? checkFormattingIsPresent(state) : undefined;
99
100
  return {
100
101
  currentBlockType: detectBlockType(availableBlockTypesInDropdown, state),
101
102
  blockTypesDisabled: areBlockTypesDisabled(state),
102
103
  availableBlockTypes,
103
104
  availableWrapperBlockTypes,
104
- availableBlockTypesInDropdown
105
+ availableBlockTypesInDropdown,
106
+ formattingIsPresent
105
107
  };
106
108
  },
107
109
  // Ignored via go/ees005
@@ -110,9 +112,10 @@ export const createPlugin = (editorAPI, dispatch, lastNodeMustBeParagraph, inclu
110
112
  const newPluginState = {
111
113
  ...oldPluginState,
112
114
  currentBlockType: detectBlockType(oldPluginState.availableBlockTypesInDropdown, newState),
113
- blockTypesDisabled: areBlockTypesDisabled(newState)
115
+ blockTypesDisabled: areBlockTypesDisabled(newState),
116
+ formattingIsPresent: hasBlockQuoteInOptions(oldPluginState.availableBlockTypesInDropdown) ? checkFormattingIsPresent(newState) : undefined
114
117
  };
115
- if (newPluginState.currentBlockType !== oldPluginState.currentBlockType || newPluginState.blockTypesDisabled !== oldPluginState.blockTypesDisabled) {
118
+ if (newPluginState.currentBlockType !== oldPluginState.currentBlockType || newPluginState.blockTypesDisabled !== oldPluginState.blockTypesDisabled || newPluginState.formattingIsPresent !== oldPluginState.formattingIsPresent) {
116
119
  dispatch(pluginKey, newPluginState);
117
120
  }
118
121
  return newPluginState;
@@ -22,6 +22,10 @@ export function FloatingToolbarComponent({
22
22
  var _api$core2, _api$blockType2, _api$blockType2$comma;
23
23
  return api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockType2 = api.blockType) === null || _api$blockType2 === void 0 ? void 0 : (_api$blockType2$comma = _api$blockType2.commands) === null || _api$blockType2$comma === void 0 ? void 0 : _api$blockType2$comma.insertBlockQuote(INPUT_METHOD.TOOLBAR));
24
24
  }, [api]);
25
+ const clearFormatting = useCallback(() => {
26
+ var _api$core3, _api$blockType3, _api$blockType3$comma;
27
+ return api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockType3 = api.blockType) === null || _api$blockType3 === void 0 ? void 0 : (_api$blockType3$comma = _api$blockType3.commands) === null || _api$blockType3$comma === void 0 ? void 0 : _api$blockType3$comma.clearFormatting());
28
+ }, [api]);
25
29
  return /*#__PURE__*/React.createElement(ToolbarBlockType, {
26
30
  isSmall: FloatingToolbarSettings.isSmall,
27
31
  isDisabled: FloatingToolbarSettings.disabled,
@@ -32,6 +36,7 @@ export function FloatingToolbarComponent({
32
36
  ,
33
37
  pluginState: blockTypeState,
34
38
  wrapBlockQuote: wrapBlockQuote,
39
+ clearFormatting: clearFormatting,
35
40
  shouldUseDefaultRole: FloatingToolbarSettings.shouldUseDefaultRole,
36
41
  api: api
37
42
  });
@@ -23,12 +23,17 @@ export function PrimaryToolbarComponent({
23
23
  var _api$core2, _api$blockType2, _api$blockType2$comma;
24
24
  return api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockType2 = api.blockType) === null || _api$blockType2 === void 0 ? void 0 : (_api$blockType2$comma = _api$blockType2.commands) === null || _api$blockType2$comma === void 0 ? void 0 : _api$blockType2$comma.insertBlockQuote(INPUT_METHOD.TOOLBAR));
25
25
  };
26
+ const clearFormatting = () => {
27
+ var _api$core3, _api$blockType3, _api$blockType3$comma;
28
+ return api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockType3 = api.blockType) === null || _api$blockType3 === void 0 ? void 0 : (_api$blockType3$comma = _api$blockType3.commands) === null || _api$blockType3$comma === void 0 ? void 0 : _api$blockType3$comma.clearFormatting());
29
+ };
26
30
  return /*#__PURE__*/React.createElement(ToolbarBlockType, {
27
31
  isSmall: isSmall,
28
32
  isDisabled: disabled,
29
33
  isReducedSpacing: isToolbarReducedSpacing,
30
34
  setTextLevel: boundSetBlockType,
31
- wrapBlockQuote: wrapBlockQuote
35
+ wrapBlockQuote: wrapBlockQuote,
36
+ clearFormatting: clearFormatting
32
37
  // Ignored via go/ees005
33
38
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
34
39
  ,
@@ -8,13 +8,16 @@ import React from 'react';
8
8
  // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
9
9
  import { jsx } from '@emotion/react';
10
10
  import { injectIntl } from 'react-intl-next';
11
- import { findKeymapByDescription, getAriaKeyshortcuts, tooltip } from '@atlaskit/editor-common/keymaps';
11
+ import { findKeymapByDescription, tooltip, clearFormatting } from '@atlaskit/editor-common/keymaps';
12
+ import { toolbarMessages } from '@atlaskit/editor-common/messages';
12
13
  import { separatorStyles, wrapperStyle } from '@atlaskit/editor-common/styles';
13
14
  import { DropdownMenuWithKeyboardNavigation as DropdownMenu } from '@atlaskit/editor-common/ui-menu';
14
15
  import { akEditorMenuZIndex } from '@atlaskit/editor-shared-styles';
16
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
15
17
  import { ThemeMutationObserver } from '@atlaskit/tokens';
18
+ import { NORMAL_TEXT } from '../../block-types';
16
19
  import { BlockTypeButton } from './blocktype-button';
17
- import { blockTypeMenuItemStyle, keyboardShortcut, keyboardShortcutSelect } from './styled';
20
+ import { blockTypeMenuItemStyle, keyboardShortcut, keyboardShortcutSelect, floatingToolbarWrapperStyle } from './styled';
18
21
  // eslint-disable-next-line @repo/internal/react/no-class-components
19
22
  class ToolbarBlockType extends React.PureComponent {
20
23
  constructor(...args) {
@@ -56,23 +59,22 @@ class ToolbarBlockType extends React.PureComponent {
56
59
  } = this.props;
57
60
  const {
58
61
  currentBlockType,
59
- availableBlockTypesInDropdown
62
+ availableBlockTypesInDropdown,
63
+ formattingIsPresent
60
64
  } = this.props.pluginState;
61
65
  const items = availableBlockTypesInDropdown.map((blockType, index) => {
62
66
  const isActive = currentBlockType === blockType;
63
67
  const tagName = blockType.tagName || 'p';
64
68
  const Tag = tagName;
65
69
  const keyMap = findKeymapByDescription(blockType.title.defaultMessage);
66
- return {
70
+ const item = {
67
71
  content:
68
72
  // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
69
73
  jsx("div", {
70
74
  css: blockTypeMenuItemStyle(tagName, isActive, this.state.typographyTheme)
71
75
  }, jsx(Tag, null, formatMessage(blockType.title))),
72
76
  value: blockType,
73
- label: formatMessage(blockType.title),
74
77
  'aria-label': tooltip(keyMap, formatMessage(blockType.title)),
75
- keyShortcuts: getAriaKeyshortcuts(keyMap),
76
78
  key: `${blockType.name}-${index}`,
77
79
  elemAfter:
78
80
  // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
@@ -81,7 +83,30 @@ class ToolbarBlockType extends React.PureComponent {
81
83
  }, tooltip(keyMap)),
82
84
  isActive
83
85
  };
86
+ return item;
84
87
  });
88
+ if (availableBlockTypesInDropdown.map(blockType => blockType.name).includes('blockquote')) {
89
+ const clearFormattingItem = {
90
+ content: jsx("div", null, jsx("p", null, toolbarMessages.clearFormatting.defaultMessage)),
91
+ value: {
92
+ name: 'clearFormatting'
93
+ },
94
+ 'aria-label': tooltip(clearFormatting, toolbarMessages.clearFormatting.defaultMessage),
95
+ key: 'clear-formatting',
96
+ elemAfter:
97
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
98
+ jsx("div", {
99
+ css: [keyboardShortcut]
100
+ }, tooltip(clearFormatting)),
101
+ isActive: false,
102
+ isDisabled: currentBlockType === NORMAL_TEXT && !formattingIsPresent
103
+ };
104
+ return [{
105
+ items
106
+ }, {
107
+ items: [clearFormattingItem]
108
+ }];
109
+ }
85
110
  return [{
86
111
  items
87
112
  }];
@@ -94,8 +119,12 @@ class ToolbarBlockType extends React.PureComponent {
94
119
  if (blockType.name === 'blockquote') {
95
120
  this.props.wrapBlockQuote(blockType.name);
96
121
  } else {
97
- const fromBlockQuote = this.props.pluginState.currentBlockType.name === 'blockquote';
98
- this.props.setTextLevel(blockType.name, fromBlockQuote);
122
+ if (blockType.name === 'clearFormatting') {
123
+ this.props.clearFormatting();
124
+ } else {
125
+ const fromBlockQuote = this.props.pluginState.currentBlockType.name === 'blockquote';
126
+ this.props.setTextLevel(blockType.name, fromBlockQuote);
127
+ }
99
128
  }
100
129
  if (shouldCloseMenu) {
101
130
  this.setState({
@@ -154,48 +183,54 @@ class ToolbarBlockType extends React.PureComponent {
154
183
  const blockTypeTitles = availableBlockTypesInDropdown.filter(blockType => blockType.name === currentBlockType.name).map(blockType => blockType.title);
155
184
  if (!this.props.isDisabled && !blockTypesDisabled) {
156
185
  const items = this.createItems();
157
- return (
186
+ return jsx("span", {
187
+ css: editorExperiment('platform_editor_blockquote_in_text_formatting_menu', true) ?
188
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
189
+ [wrapperStyle, floatingToolbarWrapperStyle] :
158
190
  // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
159
- jsx("span", {
160
- css: wrapperStyle
161
- }, jsx(DropdownMenu, {
162
- items: items,
163
- onOpenChange: this.onOpenChange,
164
- onItemActivated: this.handleSelectBlockType,
165
- isOpen: active,
166
- mountTo: popupsMountPoint,
167
- boundariesElement: popupsBoundariesElement,
168
- scrollableElement: popupsScrollableElement,
169
- zIndex: akEditorMenuZIndex,
170
- fitHeight: 360,
171
- fitWidth: 106,
172
- shouldUseDefaultRole: shouldUseDefaultRole,
173
- shouldFocusFirstItem: () => {
174
- if (isOpenedByKeyboard) {
175
- // eslint-disable-next-line @repo/internal/react/no-set-state-inside-render
176
- this.setState({
177
- ...this.state,
178
- isOpenedByKeyboard: false
179
- });
180
- }
181
- return isOpenedByKeyboard;
191
+ wrapperStyle
192
+ }, jsx(DropdownMenu, {
193
+ items: items,
194
+ onOpenChange: this.onOpenChange,
195
+ onItemActivated: this.handleSelectBlockType,
196
+ isOpen: active,
197
+ mountTo: popupsMountPoint,
198
+ boundariesElement: popupsBoundariesElement,
199
+ scrollableElement: popupsScrollableElement,
200
+ zIndex: akEditorMenuZIndex,
201
+ fitHeight: 360,
202
+ fitWidth: 106,
203
+ section: {
204
+ hasSeparator: true
205
+ },
206
+ shouldUseDefaultRole: shouldUseDefaultRole
207
+ // hasSeparator={true}
208
+ ,
209
+ shouldFocusFirstItem: () => {
210
+ if (isOpenedByKeyboard) {
211
+ // eslint-disable-next-line @repo/internal/react/no-set-state-inside-render
212
+ this.setState({
213
+ ...this.state,
214
+ isOpenedByKeyboard: false
215
+ });
182
216
  }
183
- }, jsx(BlockTypeButton, {
184
- isSmall: isSmall,
185
- isReducedSpacing: isReducedSpacing,
186
- selected: active,
187
- disabled: false,
188
- title: blockTypeTitles[0],
189
- onClick: this.handleTriggerClick,
190
- onKeyDown: this.handleTriggerByKeyboard,
191
- formatMessage: formatMessage,
192
- "aria-expanded": active,
193
- blockTypeName: currentBlockType.name
194
- })), !(api !== null && api !== void 0 && api.primaryToolbar) && jsx("span", {
195
- // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
196
- css: separatorStyles
197
- }))
198
- );
217
+ return isOpenedByKeyboard;
218
+ }
219
+ }, jsx(BlockTypeButton, {
220
+ isSmall: isSmall,
221
+ isReducedSpacing: isReducedSpacing,
222
+ selected: active,
223
+ disabled: false,
224
+ title: blockTypeTitles[0],
225
+ onClick: this.handleTriggerClick,
226
+ onKeyDown: this.handleTriggerByKeyboard,
227
+ formatMessage: formatMessage,
228
+ "aria-expanded": active,
229
+ blockTypeName: currentBlockType.name
230
+ })), !(api !== null && api !== void 0 && api.primaryToolbar) && jsx("span", {
231
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
232
+ css: separatorStyles
233
+ }));
199
234
  }
200
235
  return (
201
236
  // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
@@ -18,6 +18,12 @@ export const blockTypeMenuItemStyle = (tagName, selected, typographyTheme) => {
18
18
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
19
19
  'h1, h2, h3, h4, h5, h6': {
20
20
  marginTop: 0
21
+ },
22
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
23
+ blockquote: {
24
+ paddingTop: 0,
25
+ paddingBottom: 0,
26
+ marginTop: 0
21
27
  }
22
28
  }
23
29
  },
@@ -45,4 +51,12 @@ export const wrapperSmallStyle = css({
45
51
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-exported-styles -- Ignored via go/DSP-18766
46
52
  export const expandIconWrapperStyle = css({
47
53
  marginLeft: "var(--ds-space-negative-100, -8px)"
54
+ });
55
+
56
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-exported-styles
57
+ export const floatingToolbarWrapperStyle = css({
58
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
59
+ "[data-role='droplistContent']": {
60
+ maxHeight: '90vh'
61
+ }
48
62
  });
@@ -1,7 +1,8 @@
1
+ import { anyMarkActive } from '@atlaskit/editor-common/mark';
1
2
  import { createRule, createWrappingJoinRule } from '@atlaskit/editor-common/utils';
2
3
  import { fg } from '@atlaskit/platform-feature-flags';
3
4
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
4
- import { WRAPPER_BLOCK_TYPES } from './block-types';
5
+ import { WRAPPER_BLOCK_TYPES, FORMATTING_NODE_TYPES, FORMATTING_MARK_TYPES } from './block-types';
5
6
  export const isNodeAWrappingBlockNode = node => {
6
7
  if (!node) {
7
8
  return false;
@@ -107,4 +108,44 @@ export function areBlockTypesDisabled(state) {
107
108
  return nodesTypes.filter(type => type !== panel).length > 0 && (!hasQuote || hasNestedListInQuote);
108
109
  }
109
110
  return nodesTypes.filter(type => type !== panel).length > 0;
110
- }
111
+ }
112
+ const blockStylingIsPresent = state => {
113
+ const {
114
+ from,
115
+ to
116
+ } = state.selection;
117
+ let isBlockStyling = false;
118
+ state.doc.nodesBetween(from, to, node => {
119
+ if (FORMATTING_NODE_TYPES.indexOf(node.type.name) !== -1) {
120
+ isBlockStyling = true;
121
+ return false;
122
+ }
123
+ return true;
124
+ });
125
+ return isBlockStyling;
126
+ };
127
+ const marksArePresent = state => {
128
+ const activeMarkTypes = FORMATTING_MARK_TYPES.filter(mark => {
129
+ if (!!state.schema.marks[mark]) {
130
+ const {
131
+ $from,
132
+ empty
133
+ } = state.selection;
134
+ const {
135
+ marks
136
+ } = state.schema;
137
+ if (empty) {
138
+ return !!marks[mark].isInSet(state.storedMarks || $from.marks());
139
+ }
140
+ return anyMarkActive(state, marks[mark]);
141
+ }
142
+ return false;
143
+ });
144
+ return activeMarkTypes.length > 0;
145
+ };
146
+ export const checkFormattingIsPresent = state => {
147
+ return marksArePresent(state) || blockStylingIsPresent(state);
148
+ };
149
+ export const hasBlockQuoteInOptions = dropdownOptions => {
150
+ return !!dropdownOptions.find(blockType => blockType.name === 'blockquote');
151
+ };
@@ -8,7 +8,7 @@ import { IconHeading, IconQuote } from '@atlaskit/editor-common/quick-insert';
8
8
  import { ToolbarSize } from '@atlaskit/editor-common/types';
9
9
  import { fg } from '@atlaskit/platform-feature-flags';
10
10
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
11
- import { setBlockTypeWithAnalytics, insertBlockQuoteWithAnalytics, insertBlockQuoteWithAnalyticsCommand } from './pm-plugins/commands/block-type';
11
+ import { setBlockTypeWithAnalytics, insertBlockQuoteWithAnalytics, insertBlockQuoteWithAnalyticsCommand, clearFormatting as _clearFormatting } from './pm-plugins/commands/block-type';
12
12
  import inputRulePlugin from './pm-plugins/input-rule';
13
13
  import keymapPlugin from './pm-plugins/keymap';
14
14
  import { createPlugin, pluginKey } from './pm-plugins/main';
@@ -184,6 +184,9 @@ var blockTypePlugin = function blockTypePlugin(_ref3) {
184
184
  insertBlockQuote: function insertBlockQuote(inputMethod) {
185
185
  var _api$analytics5;
186
186
  return insertBlockQuoteWithAnalyticsCommand(inputMethod, api === null || api === void 0 || (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 ? void 0 : _api$analytics5.actions);
187
+ },
188
+ clearFormatting: function clearFormatting() {
189
+ return _clearFormatting();
187
190
  }
188
191
  },
189
192
  getSharedState: function getSharedState(editorState) {
@@ -70,6 +70,8 @@ export var OTHER = {
70
70
  nodeName: ''
71
71
  };
72
72
  export var TEXT_BLOCK_TYPES = [NORMAL_TEXT, HEADING_1, HEADING_2, HEADING_3, HEADING_4, HEADING_5, HEADING_6];
73
+ export var FORMATTING_NODE_TYPES = ['heading', 'blockquote'];
74
+ export var FORMATTING_MARK_TYPES = ['em', 'code', 'strike', 'strong', 'underline', 'textColor', 'subsup', 'backgroundColor'];
73
75
  export var WRAPPER_BLOCK_TYPES = [BLOCK_QUOTE, CODE_BLOCK, PANEL];
74
76
  export var ALL_BLOCK_TYPES = TEXT_BLOCK_TYPES.concat(WRAPPER_BLOCK_TYPES);
75
77
  export var getBlockTypesInDropdown = function getBlockTypesInDropdown(includeBlockQuoteAsTextstyleOption) {
@@ -7,6 +7,7 @@ import { filterChildrenBetween, wrapSelectionIn } from '@atlaskit/editor-common/
7
7
  import { Slice, Fragment } from '@atlaskit/editor-prosemirror/model';
8
8
  import { CellSelection } from '@atlaskit/editor-tables';
9
9
  import { HEADINGS_BY_NAME, NORMAL_TEXT } from '../block-types';
10
+ import { FORMATTING_NODE_TYPES, FORMATTING_MARK_TYPES, cellSelectionNodesBetween, formatTypes, clearNodeFormattingOnSelection } from './clear-formatting';
10
11
  import { wrapSelectionInBlockType } from './wrapSelectionIn';
11
12
  export function setBlockType(name) {
12
13
  return function (_ref) {
@@ -98,9 +99,54 @@ export function setNormalText(fromBlockQuote) {
98
99
  return tr;
99
100
  };
100
101
  }
101
- function withCurrentHeadingLevel(fn) {
102
+ export function clearFormatting() {
102
103
  return function (_ref7) {
103
104
  var tr = _ref7.tr;
105
+ var formattingCleared = [];
106
+ var schema = tr.doc.type.schema;
107
+ FORMATTING_MARK_TYPES.forEach(function (mark) {
108
+ var _tr$selection = tr.selection,
109
+ from = _tr$selection.from,
110
+ to = _tr$selection.to;
111
+ var markType = schema.marks[mark];
112
+ if (!markType) {
113
+ return;
114
+ }
115
+ if (tr.selection instanceof CellSelection) {
116
+ cellSelectionNodesBetween(tr.selection, tr.doc, function (node, pos) {
117
+ var isTableCell = node.type === schema.nodes.tableCell || node.type === schema.nodes.tableHeader;
118
+ if (!isTableCell) {
119
+ return true;
120
+ }
121
+ if (tr.doc.rangeHasMark(pos, pos + node.nodeSize, markType)) {
122
+ formattingCleared.push(formatTypes[mark]);
123
+ tr.removeMark(pos, pos + node.nodeSize, markType);
124
+ }
125
+ return false;
126
+ });
127
+ } else if (tr.doc.rangeHasMark(from, to, markType)) {
128
+ formattingCleared.push(formatTypes[mark]);
129
+ tr.removeMark(from, to, markType);
130
+ }
131
+ });
132
+ FORMATTING_NODE_TYPES.forEach(function (nodeName) {
133
+ var formattedNodeType = schema.nodes[nodeName];
134
+ var _tr$selection2 = tr.selection,
135
+ $from = _tr$selection2.$from,
136
+ $to = _tr$selection2.$to;
137
+ if (tr.selection instanceof CellSelection) {
138
+ cellSelectionNodesBetween(tr.selection, tr.doc, clearNodeFormattingOnSelection(schema, tr, formattedNodeType, nodeName, formattingCleared));
139
+ } else {
140
+ tr.doc.nodesBetween($from.pos, $to.pos, clearNodeFormattingOnSelection(schema, tr, formattedNodeType, nodeName, formattingCleared));
141
+ }
142
+ });
143
+ tr.setStoredMarks([]);
144
+ return tr;
145
+ };
146
+ }
147
+ function withCurrentHeadingLevel(fn) {
148
+ return function (_ref8) {
149
+ var tr = _ref8.tr;
104
150
  // Find all headings and paragraphs of text
105
151
  var _tr$doc$type$schema$n = tr.doc.type.schema.nodes,
106
152
  heading = _tr$doc$type$schema$n.heading,
@@ -137,8 +183,8 @@ function withCurrentHeadingLevel(fn) {
137
183
  }
138
184
  export function setNormalTextWithAnalytics(inputMethod, editorAnalyticsApi, fromBlockQuote) {
139
185
  return withCurrentHeadingLevel(function (previousHeadingLevel) {
140
- return function (_ref8) {
141
- var tr = _ref8.tr;
186
+ return function (_ref9) {
187
+ var tr = _ref9.tr;
142
188
  editorAnalyticsApi === null || editorAnalyticsApi === void 0 || editorAnalyticsApi.attachAnalyticsEvent({
143
189
  action: ACTION.FORMATTED,
144
190
  actionSubject: ACTION_SUBJECT.TEXT,
@@ -161,8 +207,8 @@ export var setHeadingWithAnalytics = function setHeadingWithAnalytics(newHeading
161
207
  // eslint-disable-next-line @typescript-eslint/max-params
162
208
  ) {
163
209
  return withCurrentHeadingLevel(function (previousHeadingLevel) {
164
- return function (_ref9) {
165
- var tr = _ref9.tr;
210
+ return function (_ref10) {
211
+ var tr = _ref10.tr;
166
212
  editorAnalyticsApi === null || editorAnalyticsApi === void 0 || editorAnalyticsApi.attachAnalyticsEvent({
167
213
  action: ACTION.FORMATTED,
168
214
  actionSubject: ACTION_SUBJECT.TEXT,
@@ -211,8 +257,8 @@ export var insertBlockQuoteWithAnalytics = function insertBlockQuoteWithAnalytic
211
257
  };
212
258
  export function insertBlockQuoteWithAnalyticsCommand(inputMethod, editorAnalyticsApi) {
213
259
  return withCurrentHeadingLevel(function (previousHeadingLevel) {
214
- return function (_ref10) {
215
- var tr = _ref10.tr;
260
+ return function (_ref11) {
261
+ var tr = _ref11.tr;
216
262
  var nodes = tr.doc.type.schema.nodes;
217
263
 
218
264
  // TODO: analytics event
@@ -234,8 +280,8 @@ export function insertBlockQuoteWithAnalyticsCommand(inputMethod, editorAnalytic
234
280
  });
235
281
  }
236
282
  export var cleanUpAtTheStartOfDocument = function cleanUpAtTheStartOfDocument(state, dispatch) {
237
- var _ref11 = state.selection,
238
- $cursor = _ref11.$cursor;
283
+ var _ref12 = state.selection,
284
+ $cursor = _ref12.$cursor;
239
285
  if ($cursor && !$cursor.nodeBefore && !$cursor.nodeAfter && $cursor.pos === 1) {
240
286
  var tr = state.tr,
241
287
  schema = state.schema;
@@ -0,0 +1,50 @@
1
+ import { ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
2
+ import { liftTarget } from '@atlaskit/editor-prosemirror/transform';
3
+ // Functions duplicated from platform/packages/editor/editor-plugin-text-formatting/src/editor-commands/clear-formatting.ts
4
+ // TODO: Refactor to avoid duplication if platform_editor_blockquote_in_text_formatting_menu experiment is productionalised
5
+ export var FORMATTING_NODE_TYPES = ['heading', 'blockquote'];
6
+ export var FORMATTING_MARK_TYPES = ['em', 'code', 'strike', 'strong', 'underline', 'textColor', 'subsup', 'backgroundColor'];
7
+ export var formatTypes = {
8
+ em: ACTION_SUBJECT_ID.FORMAT_ITALIC,
9
+ code: ACTION_SUBJECT_ID.FORMAT_CODE,
10
+ strike: ACTION_SUBJECT_ID.FORMAT_STRIKE,
11
+ strong: ACTION_SUBJECT_ID.FORMAT_STRONG,
12
+ underline: ACTION_SUBJECT_ID.FORMAT_UNDERLINE,
13
+ textColor: ACTION_SUBJECT_ID.FORMAT_COLOR,
14
+ subsup: 'subsup',
15
+ backgroundColor: ACTION_SUBJECT_ID.FORMAT_BACKGROUND_COLOR
16
+ };
17
+ export var cellSelectionNodesBetween = function cellSelectionNodesBetween(selection, doc, f, startPos
18
+ // eslint-disable-next-line @typescript-eslint/max-params
19
+ ) {
20
+ selection.forEachCell(function (cell, cellPos) {
21
+ doc.nodesBetween(cellPos, cellPos + cell.nodeSize, f, startPos);
22
+ });
23
+ };
24
+
25
+ // eslint-disable-next-line @typescript-eslint/max-params
26
+ export function clearNodeFormattingOnSelection(schema, tr, formattedNodeType, nodeName, formattingCleared) {
27
+ return function (node, pos) {
28
+ if (node.type === formattedNodeType) {
29
+ if (formattedNodeType.isTextblock) {
30
+ tr.setNodeMarkup(pos, schema.nodes.paragraph);
31
+ formattingCleared.push(nodeName);
32
+ return false;
33
+ } else {
34
+ // In case of panel or blockquote
35
+ var fromPos = tr.doc.resolve(pos + 1);
36
+ var toPos = tr.doc.resolve(pos + node.nodeSize - 1);
37
+ var nodeRange = fromPos.blockRange(toPos);
38
+ if (nodeRange) {
39
+ var targetLiftDepth = liftTarget(nodeRange);
40
+ if (targetLiftDepth || targetLiftDepth === 0) {
41
+ formattingCleared.push(nodeName);
42
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
43
+ tr.lift(nodeRange, targetLiftDepth);
44
+ }
45
+ }
46
+ }
47
+ }
48
+ return true;
49
+ };
50
+ }