@atlaskit/editor-plugin-block-type 4.0.11 → 4.0.13

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 (46) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/blockTypePlugin.js +9 -4
  3. package/dist/cjs/pm-plugins/block-types.js +9 -2
  4. package/dist/cjs/pm-plugins/commands/block-type.js +64 -15
  5. package/dist/cjs/pm-plugins/commands/wrapSelectionIn.js +61 -0
  6. package/dist/cjs/pm-plugins/main.js +12 -4
  7. package/dist/cjs/pm-plugins/ui/FloatingToolbarComponent.js +5 -0
  8. package/dist/cjs/pm-plugins/ui/PrimaryToolbarComponent.js +7 -2
  9. package/dist/cjs/pm-plugins/ui/ToolbarBlockType/index.js +22 -19
  10. package/dist/cjs/pm-plugins/ui/ToolbarBlockType/styled.js +1 -1
  11. package/dist/cjs/pm-plugins/utils.js +17 -1
  12. package/dist/es2019/blockTypePlugin.js +10 -6
  13. package/dist/es2019/pm-plugins/block-types.js +8 -1
  14. package/dist/es2019/pm-plugins/commands/block-type.js +61 -13
  15. package/dist/es2019/pm-plugins/commands/wrapSelectionIn.js +54 -0
  16. package/dist/es2019/pm-plugins/main.js +11 -5
  17. package/dist/es2019/pm-plugins/ui/FloatingToolbarComponent.js +5 -0
  18. package/dist/es2019/pm-plugins/ui/PrimaryToolbarComponent.js +7 -2
  19. package/dist/es2019/pm-plugins/ui/ToolbarBlockType/index.js +12 -6
  20. package/dist/es2019/pm-plugins/ui/ToolbarBlockType/styled.js +2 -2
  21. package/dist/es2019/pm-plugins/utils.js +15 -1
  22. package/dist/esm/blockTypePlugin.js +10 -5
  23. package/dist/esm/pm-plugins/block-types.js +8 -1
  24. package/dist/esm/pm-plugins/commands/block-type.js +62 -15
  25. package/dist/esm/pm-plugins/commands/wrapSelectionIn.js +55 -0
  26. package/dist/esm/pm-plugins/main.js +13 -5
  27. package/dist/esm/pm-plugins/ui/FloatingToolbarComponent.js +5 -0
  28. package/dist/esm/pm-plugins/ui/PrimaryToolbarComponent.js +7 -2
  29. package/dist/esm/pm-plugins/ui/ToolbarBlockType/index.js +22 -19
  30. package/dist/esm/pm-plugins/ui/ToolbarBlockType/styled.js +2 -2
  31. package/dist/esm/pm-plugins/utils.js +17 -1
  32. package/dist/types/blockTypePluginType.d.ts +2 -1
  33. package/dist/types/pm-plugins/block-types.d.ts +1 -0
  34. package/dist/types/pm-plugins/commands/block-type.d.ts +6 -4
  35. package/dist/types/pm-plugins/commands/wrapSelectionIn.d.ts +3 -0
  36. package/dist/types/pm-plugins/main.d.ts +3 -1
  37. package/dist/types/pm-plugins/types.d.ts +1 -0
  38. package/dist/types/pm-plugins/ui/ToolbarBlockType/index.d.ts +2 -1
  39. package/dist/types-ts4.5/blockTypePluginType.d.ts +2 -1
  40. package/dist/types-ts4.5/pm-plugins/block-types.d.ts +1 -0
  41. package/dist/types-ts4.5/pm-plugins/commands/block-type.d.ts +6 -4
  42. package/dist/types-ts4.5/pm-plugins/commands/wrapSelectionIn.d.ts +3 -0
  43. package/dist/types-ts4.5/pm-plugins/main.d.ts +3 -1
  44. package/dist/types-ts4.5/pm-plugins/types.d.ts +1 -0
  45. package/dist/types-ts4.5/pm-plugins/ui/ToolbarBlockType/index.d.ts +2 -1
  46. package/package.json +5 -5
@@ -1,4 +1,5 @@
1
1
  import { blockTypeMessages as messages } from '@atlaskit/editor-common/messages';
2
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
2
3
  export const NORMAL_TEXT = {
3
4
  name: 'normal',
4
5
  title: messages.normal,
@@ -50,7 +51,8 @@ export const HEADING_6 = {
50
51
  export const BLOCK_QUOTE = {
51
52
  name: 'blockquote',
52
53
  title: messages.blockquote,
53
- nodeName: 'blockquote'
54
+ nodeName: 'blockquote',
55
+ tagName: 'blockquote'
54
56
  };
55
57
  export const CODE_BLOCK = {
56
58
  name: 'codeblock',
@@ -70,6 +72,11 @@ export const OTHER = {
70
72
  export const TEXT_BLOCK_TYPES = [NORMAL_TEXT, HEADING_1, HEADING_2, HEADING_3, HEADING_4, HEADING_5, HEADING_6];
71
73
  export const WRAPPER_BLOCK_TYPES = [BLOCK_QUOTE, CODE_BLOCK, PANEL];
72
74
  export const ALL_BLOCK_TYPES = TEXT_BLOCK_TYPES.concat(WRAPPER_BLOCK_TYPES);
75
+ export const getBlockTypesInDropdown = includeBlockQuoteAsTextstyleOption => {
76
+ return editorExperiment('platform_editor_blockquote_in_text_formatting_menu', true, {
77
+ exposure: true
78
+ }) && includeBlockQuoteAsTextstyleOption ? [...TEXT_BLOCK_TYPES, BLOCK_QUOTE] : TEXT_BLOCK_TYPES;
79
+ };
73
80
  export const HEADINGS_BY_LEVEL = TEXT_BLOCK_TYPES.reduce((acc, blockType) => {
74
81
  if (blockType.level && blockType.nodeName === 'heading') {
75
82
  acc[blockType.level] = blockType;
@@ -1,8 +1,10 @@
1
1
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
2
2
  import { withAnalytics } from '@atlaskit/editor-common/editor-analytics';
3
3
  import { filterChildrenBetween, wrapSelectionIn } from '@atlaskit/editor-common/utils';
4
+ import { Slice, Fragment } from '@atlaskit/editor-prosemirror/model';
4
5
  import { CellSelection } from '@atlaskit/editor-tables';
5
6
  import { HEADINGS_BY_NAME, NORMAL_TEXT } from '../block-types';
7
+ import { wrapSelectionInBlockType } from './wrapSelectionIn';
6
8
  export function setBlockType(name) {
7
9
  return ({
8
10
  tr
@@ -24,7 +26,7 @@ export function setBlockType(name) {
24
26
  return null;
25
27
  };
26
28
  }
27
- export function setHeading(level) {
29
+ export function setHeading(level, fromBlockQuote) {
28
30
  return function ({
29
31
  tr
30
32
  }) {
@@ -41,14 +43,27 @@ export function setHeading(level) {
41
43
  $from,
42
44
  $to
43
45
  }) => {
44
- tr.setBlockType($from.pos, $to.pos, schema.nodes.heading, {
45
- level
46
- });
46
+ if (fromBlockQuote) {
47
+ const range = $from.blockRange($to);
48
+ if (!range) {
49
+ return;
50
+ }
51
+ const content = $from.node().content;
52
+ const headingNode = schema.nodes.heading.createChecked({
53
+ level
54
+ }, content);
55
+ const slice = new Slice(Fragment.from(headingNode), 0, 0);
56
+ tr.replaceRange(range.start, range.end, slice);
57
+ } else {
58
+ tr.setBlockType($from.pos, $to.pos, schema.nodes.heading, {
59
+ level
60
+ });
61
+ }
47
62
  });
48
63
  return tr;
49
64
  };
50
65
  }
51
- export function setBlockTypeWithAnalytics(name, inputMethod, editorAnalyticsApi) {
66
+ export function setBlockTypeWithAnalytics(name, inputMethod, editorAnalyticsApi, fromBlockQuote) {
52
67
  return ({
53
68
  tr
54
69
  }) => {
@@ -56,20 +71,20 @@ export function setBlockTypeWithAnalytics(name, inputMethod, editorAnalyticsApi)
56
71
  nodes
57
72
  } = tr.doc.type.schema;
58
73
  if (name === 'normal' && nodes.paragraph) {
59
- return setNormalTextWithAnalytics(inputMethod, editorAnalyticsApi)({
74
+ return setNormalTextWithAnalytics(inputMethod, editorAnalyticsApi, fromBlockQuote)({
60
75
  tr
61
76
  });
62
77
  }
63
78
  const headingBlockType = HEADINGS_BY_NAME[name];
64
79
  if (headingBlockType && nodes.heading && headingBlockType.level) {
65
- return setHeadingWithAnalytics(headingBlockType.level, inputMethod, editorAnalyticsApi)({
80
+ return setHeadingWithAnalytics(headingBlockType.level, inputMethod, editorAnalyticsApi, fromBlockQuote)({
66
81
  tr
67
82
  });
68
83
  }
69
84
  return null;
70
85
  };
71
86
  }
72
- function setNormalText() {
87
+ export function setNormalText(fromBlockQuote) {
73
88
  return function ({
74
89
  tr
75
90
  }) {
@@ -86,7 +101,15 @@ function setNormalText() {
86
101
  $from,
87
102
  $to
88
103
  }) => {
89
- tr.setBlockType($from.pos, $to.pos, schema.nodes.paragraph);
104
+ if (fromBlockQuote) {
105
+ const range = $from.blockRange($to);
106
+ if (!range) {
107
+ return;
108
+ }
109
+ tr.lift(range, 0);
110
+ } else {
111
+ tr.setBlockType($from.pos, $to.pos, schema.nodes.paragraph);
112
+ }
90
113
  });
91
114
  return tr;
92
115
  };
@@ -121,7 +144,7 @@ function withCurrentHeadingLevel(fn) {
121
144
  });
122
145
  };
123
146
  }
124
- export function setNormalTextWithAnalytics(inputMethod, editorAnalyticsApi) {
147
+ export function setNormalTextWithAnalytics(inputMethod, editorAnalyticsApi, fromBlockQuote) {
125
148
  return withCurrentHeadingLevel(previousHeadingLevel => ({
126
149
  tr
127
150
  }) => {
@@ -136,12 +159,12 @@ export function setNormalTextWithAnalytics(inputMethod, editorAnalyticsApi) {
136
159
  previousHeadingLevel
137
160
  }
138
161
  })(tr);
139
- return setNormalText()({
162
+ return setNormalText(fromBlockQuote)({
140
163
  tr
141
164
  });
142
165
  });
143
166
  }
144
- export const setHeadingWithAnalytics = (newHeadingLevel, inputMethod, editorAnalyticsApi) => {
167
+ export const setHeadingWithAnalytics = (newHeadingLevel, inputMethod, editorAnalyticsApi, fromBlockQuote) => {
145
168
  return withCurrentHeadingLevel(previousHeadingLevel => ({
146
169
  tr
147
170
  }) => {
@@ -156,7 +179,7 @@ export const setHeadingWithAnalytics = (newHeadingLevel, inputMethod, editorAnal
156
179
  previousHeadingLevel
157
180
  }
158
181
  })(tr);
159
- return setHeading(newHeadingLevel)({
182
+ return setHeading(newHeadingLevel, fromBlockQuote)({
160
183
  tr
161
184
  });
162
185
  });
@@ -192,6 +215,31 @@ export const insertBlockQuoteWithAnalytics = (inputMethod, editorAnalyticsApi) =
192
215
  }
193
216
  })(insertBlockQuote());
194
217
  };
218
+ export function insertBlockQuoteWithAnalyticsCommand(inputMethod, editorAnalyticsApi) {
219
+ return withCurrentHeadingLevel(previousHeadingLevel => ({
220
+ tr
221
+ }) => {
222
+ const {
223
+ nodes
224
+ } = tr.doc.type.schema;
225
+
226
+ // TODO: analytics event
227
+
228
+ // editorAnalyticsApi?.attachAnalyticsEvent({
229
+ // action: ACTION.FORMATTED,
230
+ // actionSubject: ACTION_SUBJECT.TEXT,
231
+ // eventType: EVENT_TYPE.TRACK,
232
+ // actionSubjectId: ACTION_SUBJECT_ID.FORMAT_BLOCK_QUOTE,
233
+ // attributes: {
234
+ // inputMethod: inputMethod,
235
+ // },
236
+ // })(tr);
237
+
238
+ return wrapSelectionInBlockType(nodes.blockquote)({
239
+ tr
240
+ });
241
+ });
242
+ }
195
243
  export const cleanUpAtTheStartOfDocument = (state, dispatch) => {
196
244
  const {
197
245
  $cursor
@@ -0,0 +1,54 @@
1
+ import { Slice, Fragment } from '@atlaskit/editor-prosemirror/model';
2
+ import { findWrapping } from '@atlaskit/editor-prosemirror/transform';
3
+ export function wrapSelectionInBlockType(nodeType) {
4
+ return ({
5
+ tr
6
+ }) => {
7
+ const {
8
+ nodes
9
+ } = tr.doc.type.schema;
10
+ const {
11
+ alignment,
12
+ indentation
13
+ } = tr.doc.type.schema.marks;
14
+ if (nodes.paragraph && nodes.blockquote) {
15
+ /**Remove alignment and indentation marks from the selection */
16
+ const marksToRemove = [alignment, indentation];
17
+ const hasMark = mark => marksToRemove.indexOf(mark.type) > -1;
18
+ const not = fn => arg => !fn(arg);
19
+
20
+ /**
21
+ * When you need to toggle the selection
22
+ * when another type which does not allow alignment is applied
23
+ */
24
+ tr.doc.nodesBetween(tr.selection.from, tr.selection.to, (node, pos) => {
25
+ if (node.type === nodes.paragraph && node.marks.some(hasMark)) {
26
+ const resolvedPos = tr.doc.resolve(pos);
27
+ const withoutBlockMarks = node.marks.filter(not(hasMark));
28
+ tr = tr.setNodeMarkup(resolvedPos.pos, undefined, node.attrs, withoutBlockMarks);
29
+ }
30
+ });
31
+
32
+ /** Get range and wrapping needed for the selection */
33
+ const {
34
+ $from,
35
+ $to
36
+ } = tr.selection;
37
+ const range = $from.blockRange($to);
38
+ const wrapping = range && findWrapping(range, nodes.blockquote);
39
+ if (wrapping) {
40
+ /** Wrap the selection */
41
+ tr.wrap(range, wrapping).scrollIntoView();
42
+ } else {
43
+ /** If wrapping is not possible, replace with a blockquote */
44
+ const start = $from.start();
45
+ const end = $to.end();
46
+ const content = $from.node().content;
47
+ const blockquote = nodes.blockquote.create({}, nodes.paragraph.create({}, content));
48
+ const slice = new Slice(Fragment.from(blockquote), 0, 0);
49
+ tr.replaceRange(start, end, slice).scrollIntoView();
50
+ }
51
+ }
52
+ return tr;
53
+ };
54
+ }
@@ -3,7 +3,7 @@ import { browser } from '@atlaskit/editor-common/browser';
3
3
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
4
4
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
5
5
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
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 } from './block-types';
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
9
  import { areBlockTypesDisabled } from './utils';
@@ -15,6 +15,8 @@ const blockTypeForNode = (node, schema) => {
15
15
  }
16
16
  } else if (node.type === schema.nodes.paragraph) {
17
17
  return NORMAL_TEXT;
18
+ } else if (node.type === schema.nodes.blockquote) {
19
+ return BLOCK_QUOTE;
18
20
  }
19
21
  return OTHER;
20
22
  };
@@ -56,6 +58,7 @@ const detectBlockType = (availableBlockTypes, state) => {
56
58
  } else if (blockType !== OTHER && blockType !== nodeBlockType[0]) {
57
59
  blockType = OTHER;
58
60
  }
61
+ return false;
59
62
  }
60
63
  });
61
64
  return blockType || OTHER;
@@ -67,7 +70,7 @@ const autoformatHeading = (headingLevel, editorAnalyticsApi) => {
67
70
  return setHeadingWithAnalytics(headingLevel, INPUT_METHOD.FORMATTING, editorAnalyticsApi);
68
71
  };
69
72
  export const pluginKey = new PluginKey('blockTypePlugin');
70
- export const createPlugin = (editorAPI, dispatch, lastNodeMustBeParagraph) => {
73
+ export const createPlugin = (editorAPI, dispatch, lastNodeMustBeParagraph, includeBlockQuoteAsTextstyleOption) => {
71
74
  var _editorAPI$analytics;
72
75
  const editorAnalyticsApi = editorAPI === null || editorAPI === void 0 ? void 0 : (_editorAPI$analytics = editorAPI.analytics) === null || _editorAPI$analytics === void 0 ? void 0 : _editorAPI$analytics.actions;
73
76
  let altKeyLocation = 0;
@@ -88,17 +91,20 @@ export const createPlugin = (editorAPI, dispatch, lastNodeMustBeParagraph) => {
88
91
  init(_config, state) {
89
92
  const availableBlockTypes = TEXT_BLOCK_TYPES.filter(blockType => isBlockTypeSchemaSupported(blockType, state));
90
93
  const availableWrapperBlockTypes = WRAPPER_BLOCK_TYPES.filter(blockType => isBlockTypeSchemaSupported(blockType, state));
94
+ const BLOCK_TYPES_IN_DROPDOWN = getBlockTypesInDropdown(includeBlockQuoteAsTextstyleOption);
95
+ const availableBlockTypesInDropdown = BLOCK_TYPES_IN_DROPDOWN.filter(blockType => isBlockTypeSchemaSupported(blockType, state));
91
96
  return {
92
- currentBlockType: detectBlockType(availableBlockTypes, state),
97
+ currentBlockType: detectBlockType(availableBlockTypesInDropdown, state),
93
98
  blockTypesDisabled: areBlockTypesDisabled(state),
94
99
  availableBlockTypes,
95
- availableWrapperBlockTypes
100
+ availableWrapperBlockTypes,
101
+ availableBlockTypesInDropdown
96
102
  };
97
103
  },
98
104
  apply(_tr, oldPluginState, _oldState, newState) {
99
105
  const newPluginState = {
100
106
  ...oldPluginState,
101
- currentBlockType: detectBlockType(oldPluginState.availableBlockTypes, newState),
107
+ currentBlockType: detectBlockType(oldPluginState.availableBlockTypesInDropdown, newState),
102
108
  blockTypesDisabled: areBlockTypesDisabled(newState)
103
109
  };
104
110
  if (newPluginState.currentBlockType !== oldPluginState.currentBlockType || newPluginState.blockTypesDisabled !== oldPluginState.blockTypesDisabled) {
@@ -18,12 +18,17 @@ export function FloatingToolbarComponent({
18
18
  var _api$core, _api$blockType, _api$blockType$comman;
19
19
  return api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockType = api.blockType) === null || _api$blockType === void 0 ? void 0 : (_api$blockType$comman = _api$blockType.commands) === null || _api$blockType$comman === void 0 ? void 0 : _api$blockType$comman.setTextLevel(name, INPUT_METHOD.FLOATING_TB));
20
20
  }, [api]);
21
+ const wrapBlockQuote = useCallback(() => {
22
+ var _api$core2, _api$blockType2, _api$blockType2$comma;
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
+ }, [api]);
21
25
  return /*#__PURE__*/React.createElement(ToolbarBlockType, {
22
26
  isSmall: FloatingToolbarSettings.isSmall,
23
27
  isDisabled: FloatingToolbarSettings.disabled,
24
28
  isReducedSpacing: FloatingToolbarSettings.isToolbarReducedSpacing,
25
29
  setTextLevel: boundSetBlockType,
26
30
  pluginState: blockTypeState,
31
+ wrapBlockQuote: wrapBlockQuote,
27
32
  shouldUseDefaultRole: FloatingToolbarSettings.shouldUseDefaultRole,
28
33
  api: api
29
34
  });
@@ -15,15 +15,20 @@ export function PrimaryToolbarComponent({
15
15
  const {
16
16
  blockTypeState
17
17
  } = useSharedPluginState(api, ['blockType']);
18
- const boundSetBlockType = name => {
18
+ const boundSetBlockType = (name, fromBlockQuote) => {
19
19
  var _api$core, _api$blockType, _api$blockType$comman;
20
- return api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockType = api.blockType) === null || _api$blockType === void 0 ? void 0 : (_api$blockType$comman = _api$blockType.commands) === null || _api$blockType$comman === void 0 ? void 0 : _api$blockType$comman.setTextLevel(name, INPUT_METHOD.TOOLBAR));
20
+ return api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockType = api.blockType) === null || _api$blockType === void 0 ? void 0 : (_api$blockType$comman = _api$blockType.commands) === null || _api$blockType$comman === void 0 ? void 0 : _api$blockType$comman.setTextLevel(name, INPUT_METHOD.TOOLBAR, fromBlockQuote));
21
+ };
22
+ const wrapBlockQuote = () => {
23
+ var _api$core2, _api$blockType2, _api$blockType2$comma;
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));
21
25
  };
22
26
  return /*#__PURE__*/React.createElement(ToolbarBlockType, {
23
27
  isSmall: isSmall,
24
28
  isDisabled: disabled,
25
29
  isReducedSpacing: isToolbarReducedSpacing,
26
30
  setTextLevel: boundSetBlockType,
31
+ wrapBlockQuote: wrapBlockQuote,
27
32
  pluginState: blockTypeState,
28
33
  popupsMountPoint: popupsMountPoint,
29
34
  popupsBoundariesElement: popupsBoundariesElement,
@@ -56,9 +56,9 @@ class ToolbarBlockType extends React.PureComponent {
56
56
  } = this.props;
57
57
  const {
58
58
  currentBlockType,
59
- availableBlockTypes
59
+ availableBlockTypesInDropdown
60
60
  } = this.props.pluginState;
61
- const items = availableBlockTypes.map((blockType, index) => {
61
+ const items = availableBlockTypesInDropdown.map((blockType, index) => {
62
62
  const isActive = currentBlockType === blockType;
63
63
  const tagName = blockType.tagName || 'p';
64
64
  const Tag = tagName;
@@ -91,7 +91,12 @@ class ToolbarBlockType extends React.PureComponent {
91
91
  shouldCloseMenu = true
92
92
  }) => {
93
93
  const blockType = item.value;
94
- this.props.setTextLevel(blockType.name);
94
+ if (blockType.name === 'blockquote') {
95
+ this.props.wrapBlockQuote(blockType.name);
96
+ } else {
97
+ const fromBlockQuote = this.props.pluginState.currentBlockType.name === 'blockquote';
98
+ this.props.setTextLevel(blockType.name, fromBlockQuote);
99
+ }
95
100
  if (shouldCloseMenu) {
96
101
  this.setState({
97
102
  ...this.state,
@@ -133,7 +138,8 @@ class ToolbarBlockType extends React.PureComponent {
133
138
  pluginState: {
134
139
  currentBlockType,
135
140
  blockTypesDisabled,
136
- availableBlockTypes
141
+ availableBlockTypes,
142
+ availableBlockTypesInDropdown
137
143
  },
138
144
  shouldUseDefaultRole,
139
145
  intl: {
@@ -145,8 +151,8 @@ class ToolbarBlockType extends React.PureComponent {
145
151
  if (isHeadingDisabled) {
146
152
  return null;
147
153
  }
148
- const blockTypeTitles = availableBlockTypes.filter(blockType => blockType.name === currentBlockType.name).map(blockType => blockType.title);
149
- if (!this.props.isDisabled && !blockTypesDisabled) {
154
+ const blockTypeTitles = availableBlockTypesInDropdown.filter(blockType => blockType.name === currentBlockType.name).map(blockType => blockType.title);
155
+ if (!this.props.isDisabled && (!blockTypesDisabled || currentBlockType.name === 'blockquote')) {
150
156
  const items = this.createItems();
151
157
  return (
152
158
  // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
@@ -4,7 +4,7 @@
4
4
  */
5
5
  // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
6
6
  import { css } from '@emotion/react';
7
- import { headingsSharedStyles } from '@atlaskit/editor-common/styles';
7
+ import { headingsSharedStyles, blockquoteSharedStyles } from '@atlaskit/editor-common/styles';
8
8
  import { shortcutStyle } from '@atlaskit/editor-shared-styles/shortcut';
9
9
  import { N400 } from '@atlaskit/theme/colors';
10
10
  export const blockTypeMenuItemStyle = (tagName, selected, typographyTheme) => {
@@ -12,7 +12,7 @@ export const blockTypeMenuItemStyle = (tagName, selected, typographyTheme) => {
12
12
  const selectedStyle = selected ? `${tagName} { color: ${"var(--ds-text, white)"} !important; }` : '';
13
13
  return () => css(
14
14
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
15
- headingsSharedStyles(typographyTheme), {
15
+ tagName === 'blockquote' ? blockquoteSharedStyles : headingsSharedStyles(typographyTheme), {
16
16
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
17
17
  '>': {
18
18
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
@@ -1,5 +1,6 @@
1
1
  import { createRule, createWrappingJoinRule } from '@atlaskit/editor-common/utils';
2
2
  import { fg } from '@atlaskit/platform-feature-flags';
3
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
3
4
  import { WRAPPER_BLOCK_TYPES } from './block-types';
4
5
  export const isNodeAWrappingBlockNode = node => {
5
6
  if (!node) {
@@ -75,7 +76,20 @@ function getSelectedWrapperNodes(state) {
75
76
  export function areBlockTypesDisabled(state) {
76
77
  const nodesTypes = getSelectedWrapperNodes(state);
77
78
  const {
78
- panel
79
+ panel,
80
+ blockquote
79
81
  } = state.schema.nodes;
82
+ if (editorExperiment('platform_editor_blockquote_in_text_formatting_menu', true)) {
83
+ let hasQuote = false;
84
+ const {
85
+ $from,
86
+ $to
87
+ } = state.selection;
88
+ state.doc.nodesBetween($from.pos, $to.pos, node => {
89
+ hasQuote = node.type === blockquote;
90
+ return !hasQuote;
91
+ });
92
+ return nodesTypes.filter(type => type !== panel).length > 0 || hasQuote;
93
+ }
80
94
  return nodesTypes.filter(type => type !== panel).length > 0;
81
95
  }
@@ -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 } from './pm-plugins/commands/block-type';
11
+ import { setBlockTypeWithAnalytics, insertBlockQuoteWithAnalytics, insertBlockQuoteWithAnalyticsCommand } 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';
@@ -146,7 +146,7 @@ var blockTypePlugin = function blockTypePlugin(_ref3) {
146
146
  name: 'blockType',
147
147
  plugin: function plugin(_ref5) {
148
148
  var dispatch = _ref5.dispatch;
149
- return createPlugin(api, dispatch, options && options.lastNodeMustBeParagraph);
149
+ return createPlugin(api, dispatch, options && options.lastNodeMustBeParagraph, options === null || options === void 0 ? void 0 : options.includeBlockQuoteAsTextstyleOption);
150
150
  }
151
151
  }, {
152
152
  name: 'blockTypeInputRule',
@@ -178,7 +178,12 @@ var blockTypePlugin = function blockTypePlugin(_ref3) {
178
178
  commands: {
179
179
  setTextLevel: function setTextLevel(level, inputMethod) {
180
180
  var _api$analytics4;
181
- return setBlockTypeWithAnalytics(level, inputMethod, api === null || api === void 0 || (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 ? void 0 : _api$analytics4.actions);
181
+ var fromBlockQuote = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
182
+ return setBlockTypeWithAnalytics(level, inputMethod, api === null || api === void 0 || (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 ? void 0 : _api$analytics4.actions, fromBlockQuote);
183
+ },
184
+ insertBlockQuote: function insertBlockQuote(inputMethod) {
185
+ var _api$analytics5;
186
+ return insertBlockQuoteWithAnalyticsCommand(inputMethod, api === null || api === void 0 || (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 ? void 0 : _api$analytics5.actions);
182
187
  }
183
188
  },
184
189
  getSharedState: function getSharedState(editorState) {
@@ -215,9 +220,9 @@ var blockTypePlugin = function blockTypePlugin(_ref3) {
215
220
  }
216
221
  },
217
222
  quickInsert: function quickInsert(intl) {
218
- var _api$analytics5, _api$analytics6;
223
+ var _api$analytics6, _api$analytics7;
219
224
  var exclude = options && options.allowBlockType && options.allowBlockType.exclude ? options.allowBlockType.exclude : [];
220
- return [].concat(_toConsumableArray(blockquotePluginOptions(intl, exclude.indexOf('blockquote') === -1, api === null || api === void 0 || (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 ? void 0 : _api$analytics5.actions)), _toConsumableArray(headingPluginOptions(intl, exclude.indexOf('heading') === -1, api === null || api === void 0 || (_api$analytics6 = api.analytics) === null || _api$analytics6 === void 0 ? void 0 : _api$analytics6.actions)));
225
+ return [].concat(_toConsumableArray(blockquotePluginOptions(intl, exclude.indexOf('blockquote') === -1, api === null || api === void 0 || (_api$analytics6 = api.analytics) === null || _api$analytics6 === void 0 ? void 0 : _api$analytics6.actions)), _toConsumableArray(headingPluginOptions(intl, exclude.indexOf('heading') === -1, api === null || api === void 0 || (_api$analytics7 = api.analytics) === null || _api$analytics7 === void 0 ? void 0 : _api$analytics7.actions)));
221
226
  }
222
227
  }
223
228
  };
@@ -1,4 +1,5 @@
1
1
  import { blockTypeMessages as messages } from '@atlaskit/editor-common/messages';
2
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
2
3
  export var NORMAL_TEXT = {
3
4
  name: 'normal',
4
5
  title: messages.normal,
@@ -50,7 +51,8 @@ export var HEADING_6 = {
50
51
  export var BLOCK_QUOTE = {
51
52
  name: 'blockquote',
52
53
  title: messages.blockquote,
53
- nodeName: 'blockquote'
54
+ nodeName: 'blockquote',
55
+ tagName: 'blockquote'
54
56
  };
55
57
  export var CODE_BLOCK = {
56
58
  name: 'codeblock',
@@ -70,6 +72,11 @@ export var OTHER = {
70
72
  export var TEXT_BLOCK_TYPES = [NORMAL_TEXT, HEADING_1, HEADING_2, HEADING_3, HEADING_4, HEADING_5, HEADING_6];
71
73
  export var WRAPPER_BLOCK_TYPES = [BLOCK_QUOTE, CODE_BLOCK, PANEL];
72
74
  export var ALL_BLOCK_TYPES = TEXT_BLOCK_TYPES.concat(WRAPPER_BLOCK_TYPES);
75
+ export var getBlockTypesInDropdown = function getBlockTypesInDropdown(includeBlockQuoteAsTextstyleOption) {
76
+ return editorExperiment('platform_editor_blockquote_in_text_formatting_menu', true, {
77
+ exposure: true
78
+ }) && includeBlockQuoteAsTextstyleOption ? [].concat(TEXT_BLOCK_TYPES, [BLOCK_QUOTE]) : TEXT_BLOCK_TYPES;
79
+ };
73
80
  export var HEADINGS_BY_LEVEL = TEXT_BLOCK_TYPES.reduce(function (acc, blockType) {
74
81
  if (blockType.level && blockType.nodeName === 'heading') {
75
82
  acc[blockType.level] = blockType;
@@ -4,8 +4,10 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
4
4
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
5
5
  import { withAnalytics } from '@atlaskit/editor-common/editor-analytics';
6
6
  import { filterChildrenBetween, wrapSelectionIn } from '@atlaskit/editor-common/utils';
7
+ import { Slice, Fragment } from '@atlaskit/editor-prosemirror/model';
7
8
  import { CellSelection } from '@atlaskit/editor-tables';
8
9
  import { HEADINGS_BY_NAME, NORMAL_TEXT } from '../block-types';
10
+ import { wrapSelectionInBlockType } from './wrapSelectionIn';
9
11
  export function setBlockType(name) {
10
12
  return function (_ref) {
11
13
  var tr = _ref.tr;
@@ -24,7 +26,7 @@ export function setBlockType(name) {
24
26
  return null;
25
27
  };
26
28
  }
27
- export function setHeading(level) {
29
+ export function setHeading(level, fromBlockQuote) {
28
30
  return function (_ref2) {
29
31
  var tr = _ref2.tr;
30
32
  var selection = tr.selection,
@@ -33,32 +35,45 @@ export function setHeading(level) {
33
35
  ranges.forEach(function (_ref3) {
34
36
  var $from = _ref3.$from,
35
37
  $to = _ref3.$to;
36
- tr.setBlockType($from.pos, $to.pos, schema.nodes.heading, {
37
- level: level
38
- });
38
+ if (fromBlockQuote) {
39
+ var range = $from.blockRange($to);
40
+ if (!range) {
41
+ return;
42
+ }
43
+ var content = $from.node().content;
44
+ var headingNode = schema.nodes.heading.createChecked({
45
+ level: level
46
+ }, content);
47
+ var slice = new Slice(Fragment.from(headingNode), 0, 0);
48
+ tr.replaceRange(range.start, range.end, slice);
49
+ } else {
50
+ tr.setBlockType($from.pos, $to.pos, schema.nodes.heading, {
51
+ level: level
52
+ });
53
+ }
39
54
  });
40
55
  return tr;
41
56
  };
42
57
  }
43
- export function setBlockTypeWithAnalytics(name, inputMethod, editorAnalyticsApi) {
58
+ export function setBlockTypeWithAnalytics(name, inputMethod, editorAnalyticsApi, fromBlockQuote) {
44
59
  return function (_ref4) {
45
60
  var tr = _ref4.tr;
46
61
  var nodes = tr.doc.type.schema.nodes;
47
62
  if (name === 'normal' && nodes.paragraph) {
48
- return setNormalTextWithAnalytics(inputMethod, editorAnalyticsApi)({
63
+ return setNormalTextWithAnalytics(inputMethod, editorAnalyticsApi, fromBlockQuote)({
49
64
  tr: tr
50
65
  });
51
66
  }
52
67
  var headingBlockType = HEADINGS_BY_NAME[name];
53
68
  if (headingBlockType && nodes.heading && headingBlockType.level) {
54
- return setHeadingWithAnalytics(headingBlockType.level, inputMethod, editorAnalyticsApi)({
69
+ return setHeadingWithAnalytics(headingBlockType.level, inputMethod, editorAnalyticsApi, fromBlockQuote)({
55
70
  tr: tr
56
71
  });
57
72
  }
58
73
  return null;
59
74
  };
60
75
  }
61
- function setNormalText() {
76
+ export function setNormalText(fromBlockQuote) {
62
77
  return function (_ref5) {
63
78
  var tr = _ref5.tr;
64
79
  var selection = tr.selection,
@@ -67,7 +82,15 @@ function setNormalText() {
67
82
  ranges.forEach(function (_ref6) {
68
83
  var $from = _ref6.$from,
69
84
  $to = _ref6.$to;
70
- tr.setBlockType($from.pos, $to.pos, schema.nodes.paragraph);
85
+ if (fromBlockQuote) {
86
+ var range = $from.blockRange($to);
87
+ if (!range) {
88
+ return;
89
+ }
90
+ tr.lift(range, 0);
91
+ } else {
92
+ tr.setBlockType($from.pos, $to.pos, schema.nodes.paragraph);
93
+ }
71
94
  });
72
95
  return tr;
73
96
  };
@@ -109,7 +132,7 @@ function withCurrentHeadingLevel(fn) {
109
132
  });
110
133
  };
111
134
  }
112
- export function setNormalTextWithAnalytics(inputMethod, editorAnalyticsApi) {
135
+ export function setNormalTextWithAnalytics(inputMethod, editorAnalyticsApi, fromBlockQuote) {
113
136
  return withCurrentHeadingLevel(function (previousHeadingLevel) {
114
137
  return function (_ref8) {
115
138
  var tr = _ref8.tr;
@@ -124,13 +147,13 @@ export function setNormalTextWithAnalytics(inputMethod, editorAnalyticsApi) {
124
147
  previousHeadingLevel: previousHeadingLevel
125
148
  }
126
149
  })(tr);
127
- return setNormalText()({
150
+ return setNormalText(fromBlockQuote)({
128
151
  tr: tr
129
152
  });
130
153
  };
131
154
  });
132
155
  }
133
- export var setHeadingWithAnalytics = function setHeadingWithAnalytics(newHeadingLevel, inputMethod, editorAnalyticsApi) {
156
+ export var setHeadingWithAnalytics = function setHeadingWithAnalytics(newHeadingLevel, inputMethod, editorAnalyticsApi, fromBlockQuote) {
134
157
  return withCurrentHeadingLevel(function (previousHeadingLevel) {
135
158
  return function (_ref9) {
136
159
  var tr = _ref9.tr;
@@ -145,7 +168,7 @@ export var setHeadingWithAnalytics = function setHeadingWithAnalytics(newHeading
145
168
  previousHeadingLevel: previousHeadingLevel
146
169
  }
147
170
  })(tr);
148
- return setHeading(newHeadingLevel)({
171
+ return setHeading(newHeadingLevel, fromBlockQuote)({
149
172
  tr: tr
150
173
  });
151
174
  };
@@ -180,9 +203,33 @@ export var insertBlockQuoteWithAnalytics = function insertBlockQuoteWithAnalytic
180
203
  }
181
204
  })(insertBlockQuote());
182
205
  };
206
+ export function insertBlockQuoteWithAnalyticsCommand(inputMethod, editorAnalyticsApi) {
207
+ return withCurrentHeadingLevel(function (previousHeadingLevel) {
208
+ return function (_ref10) {
209
+ var tr = _ref10.tr;
210
+ var nodes = tr.doc.type.schema.nodes;
211
+
212
+ // TODO: analytics event
213
+
214
+ // editorAnalyticsApi?.attachAnalyticsEvent({
215
+ // action: ACTION.FORMATTED,
216
+ // actionSubject: ACTION_SUBJECT.TEXT,
217
+ // eventType: EVENT_TYPE.TRACK,
218
+ // actionSubjectId: ACTION_SUBJECT_ID.FORMAT_BLOCK_QUOTE,
219
+ // attributes: {
220
+ // inputMethod: inputMethod,
221
+ // },
222
+ // })(tr);
223
+
224
+ return wrapSelectionInBlockType(nodes.blockquote)({
225
+ tr: tr
226
+ });
227
+ };
228
+ });
229
+ }
183
230
  export var cleanUpAtTheStartOfDocument = function cleanUpAtTheStartOfDocument(state, dispatch) {
184
- var _ref10 = state.selection,
185
- $cursor = _ref10.$cursor;
231
+ var _ref11 = state.selection,
232
+ $cursor = _ref11.$cursor;
186
233
  if ($cursor && !$cursor.nodeBefore && !$cursor.nodeAfter && $cursor.pos === 1) {
187
234
  var tr = state.tr,
188
235
  schema = state.schema;