@atlaskit/editor-plugin-block-type 1.0.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 (120) hide show
  1. package/.eslintrc.js +14 -0
  2. package/CHANGELOG.md +1 -0
  3. package/LICENSE.md +13 -0
  4. package/README.md +32 -0
  5. package/consts/package.json +15 -0
  6. package/dist/cjs/consts.js +66 -0
  7. package/dist/cjs/index.js +12 -0
  8. package/dist/cjs/messages.js +19 -0
  9. package/dist/cjs/plugin/block-types.js +106 -0
  10. package/dist/cjs/plugin/commands/block-type.js +183 -0
  11. package/dist/cjs/plugin/commands/delete-and-move-cursor.js +56 -0
  12. package/dist/cjs/plugin/commands/delete-block-content.js +45 -0
  13. package/dist/cjs/plugin/commands/index.js +69 -0
  14. package/dist/cjs/plugin/consts.js +15 -0
  15. package/dist/cjs/plugin/index.js +217 -0
  16. package/dist/cjs/plugin/messages.js +160 -0
  17. package/dist/cjs/plugin/pm-plugins/input-rule.js +104 -0
  18. package/dist/cjs/plugin/pm-plugins/keymap.js +34 -0
  19. package/dist/cjs/plugin/pm-plugins/main.js +151 -0
  20. package/dist/cjs/plugin/styles.js +15 -0
  21. package/dist/cjs/plugin/types.js +5 -0
  22. package/dist/cjs/plugin/ui/ToolbarBlockType/blocktype-button.js +60 -0
  23. package/dist/cjs/plugin/ui/ToolbarBlockType/index.js +208 -0
  24. package/dist/cjs/plugin/ui/ToolbarBlockType/styled.js +34 -0
  25. package/dist/cjs/plugin/ui/ToolbarBlockType/toolbar-messages.js +15 -0
  26. package/dist/cjs/plugin/utils.js +87 -0
  27. package/dist/cjs/styles.js +12 -0
  28. package/dist/es2019/consts.js +1 -0
  29. package/dist/es2019/index.js +1 -0
  30. package/dist/es2019/messages.js +2 -0
  31. package/dist/es2019/plugin/block-types.js +84 -0
  32. package/dist/es2019/plugin/commands/block-type.js +170 -0
  33. package/dist/es2019/plugin/commands/delete-and-move-cursor.js +55 -0
  34. package/dist/es2019/plugin/commands/delete-block-content.js +42 -0
  35. package/dist/es2019/plugin/commands/index.js +8 -0
  36. package/dist/es2019/plugin/consts.js +8 -0
  37. package/dist/es2019/plugin/index.js +204 -0
  38. package/dist/es2019/plugin/messages.js +153 -0
  39. package/dist/es2019/plugin/pm-plugins/input-rule.js +93 -0
  40. package/dist/es2019/plugin/pm-plugins/keymap.js +25 -0
  41. package/dist/es2019/plugin/pm-plugins/main.js +137 -0
  42. package/dist/es2019/plugin/styles.js +8 -0
  43. package/dist/es2019/plugin/types.js +1 -0
  44. package/dist/es2019/plugin/ui/ToolbarBlockType/blocktype-button.js +50 -0
  45. package/dist/es2019/plugin/ui/ToolbarBlockType/index.js +185 -0
  46. package/dist/es2019/plugin/ui/ToolbarBlockType/styled.js +49 -0
  47. package/dist/es2019/plugin/ui/ToolbarBlockType/toolbar-messages.js +8 -0
  48. package/dist/es2019/plugin/utils.js +76 -0
  49. package/dist/es2019/styles.js +1 -0
  50. package/dist/esm/consts.js +1 -0
  51. package/dist/esm/index.js +1 -0
  52. package/dist/esm/messages.js +2 -0
  53. package/dist/esm/plugin/block-types.js +84 -0
  54. package/dist/esm/plugin/commands/block-type.js +169 -0
  55. package/dist/esm/plugin/commands/delete-and-move-cursor.js +50 -0
  56. package/dist/esm/plugin/commands/delete-block-content.js +39 -0
  57. package/dist/esm/plugin/commands/index.js +8 -0
  58. package/dist/esm/plugin/consts.js +8 -0
  59. package/dist/esm/plugin/index.js +205 -0
  60. package/dist/esm/plugin/messages.js +153 -0
  61. package/dist/esm/plugin/pm-plugins/input-rule.js +96 -0
  62. package/dist/esm/plugin/pm-plugins/keymap.js +25 -0
  63. package/dist/esm/plugin/pm-plugins/main.js +142 -0
  64. package/dist/esm/plugin/styles.js +7 -0
  65. package/dist/esm/plugin/types.js +1 -0
  66. package/dist/esm/plugin/ui/ToolbarBlockType/blocktype-button.js +50 -0
  67. package/dist/esm/plugin/ui/ToolbarBlockType/index.js +201 -0
  68. package/dist/esm/plugin/ui/ToolbarBlockType/styled.js +20 -0
  69. package/dist/esm/plugin/ui/ToolbarBlockType/toolbar-messages.js +8 -0
  70. package/dist/esm/plugin/utils.js +77 -0
  71. package/dist/esm/styles.js +1 -0
  72. package/dist/types/consts.d.ts +1 -0
  73. package/dist/types/index.d.ts +6 -0
  74. package/dist/types/messages.d.ts +2 -0
  75. package/dist/types/plugin/block-types.d.ts +19 -0
  76. package/dist/types/plugin/commands/block-type.d.ts +18 -0
  77. package/dist/types/plugin/commands/delete-and-move-cursor.d.ts +12 -0
  78. package/dist/types/plugin/commands/delete-block-content.d.ts +10 -0
  79. package/dist/types/plugin/commands/index.d.ts +9 -0
  80. package/dist/types/plugin/consts.d.ts +1 -0
  81. package/dist/types/plugin/index.d.ts +18 -0
  82. package/dist/types/plugin/messages.d.ts +152 -0
  83. package/dist/types/plugin/pm-plugins/input-rule.d.ts +6 -0
  84. package/dist/types/plugin/pm-plugins/keymap.d.ts +5 -0
  85. package/dist/types/plugin/pm-plugins/main.d.ts +17 -0
  86. package/dist/types/plugin/styles.d.ts +2 -0
  87. package/dist/types/plugin/types.d.ts +22 -0
  88. package/dist/types/plugin/ui/ToolbarBlockType/blocktype-button.d.ts +22 -0
  89. package/dist/types/plugin/ui/ToolbarBlockType/index.d.ts +29 -0
  90. package/dist/types/plugin/ui/ToolbarBlockType/styled.d.ts +8 -0
  91. package/dist/types/plugin/ui/ToolbarBlockType/toolbar-messages.d.ts +7 -0
  92. package/dist/types/plugin/utils.d.ts +16 -0
  93. package/dist/types/styles.d.ts +1 -0
  94. package/dist/types-ts4.5/consts.d.ts +1 -0
  95. package/dist/types-ts4.5/index.d.ts +6 -0
  96. package/dist/types-ts4.5/messages.d.ts +2 -0
  97. package/dist/types-ts4.5/plugin/block-types.d.ts +19 -0
  98. package/dist/types-ts4.5/plugin/commands/block-type.d.ts +18 -0
  99. package/dist/types-ts4.5/plugin/commands/delete-and-move-cursor.d.ts +12 -0
  100. package/dist/types-ts4.5/plugin/commands/delete-block-content.d.ts +10 -0
  101. package/dist/types-ts4.5/plugin/commands/index.d.ts +9 -0
  102. package/dist/types-ts4.5/plugin/consts.d.ts +1 -0
  103. package/dist/types-ts4.5/plugin/index.d.ts +20 -0
  104. package/dist/types-ts4.5/plugin/messages.d.ts +152 -0
  105. package/dist/types-ts4.5/plugin/pm-plugins/input-rule.d.ts +6 -0
  106. package/dist/types-ts4.5/plugin/pm-plugins/keymap.d.ts +5 -0
  107. package/dist/types-ts4.5/plugin/pm-plugins/main.d.ts +17 -0
  108. package/dist/types-ts4.5/plugin/styles.d.ts +2 -0
  109. package/dist/types-ts4.5/plugin/types.d.ts +22 -0
  110. package/dist/types-ts4.5/plugin/ui/ToolbarBlockType/blocktype-button.d.ts +22 -0
  111. package/dist/types-ts4.5/plugin/ui/ToolbarBlockType/index.d.ts +29 -0
  112. package/dist/types-ts4.5/plugin/ui/ToolbarBlockType/styled.d.ts +8 -0
  113. package/dist/types-ts4.5/plugin/ui/ToolbarBlockType/toolbar-messages.d.ts +7 -0
  114. package/dist/types-ts4.5/plugin/utils.d.ts +16 -0
  115. package/dist/types-ts4.5/styles.d.ts +1 -0
  116. package/messages/package.json +15 -0
  117. package/package.json +105 -0
  118. package/report.api.md +108 -0
  119. package/styles/package.json +15 -0
  120. package/tmp/api-report-tmp.d.ts +75 -0
@@ -0,0 +1,93 @@
1
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
+ import { insertBlock } from '@atlaskit/editor-common/commands';
3
+ import { inputRuleWithAnalytics } from '@atlaskit/editor-common/utils';
4
+ import { createPlugin, createRule, leafNodeReplacementCharacter } from '@atlaskit/prosemirror-input-rules';
5
+ import { createJoinNodesRule, createWrappingTextBlockRule } from '../utils';
6
+ const MAX_HEADING_LEVEL = 6;
7
+ function getHeadingLevel(match) {
8
+ return {
9
+ level: match[1].length
10
+ };
11
+ }
12
+ function headingRule(nodeType, maxLevel) {
13
+ return createWrappingTextBlockRule({
14
+ match: new RegExp('^(#{1,' + maxLevel + '})\\s$'),
15
+ nodeType,
16
+ getAttrs: getHeadingLevel
17
+ });
18
+ }
19
+ function blockQuoteRule(nodeType) {
20
+ return createJoinNodesRule(/^\s*>\s$/, nodeType);
21
+ }
22
+
23
+ /**
24
+ * Get heading rules
25
+ *
26
+ * @param {Schema} schema
27
+ * @returns {InputRuleWithHandler[]}
28
+ */
29
+ function getHeadingRules(editorAnalyticsAPI, schema) {
30
+ // '# ' for h1, '## ' for h2 and etc
31
+ const hashRule = headingRule(schema.nodes.heading, MAX_HEADING_LEVEL);
32
+ const leftNodeReplacementHashRule = createRule(new RegExp(`${leafNodeReplacementCharacter}(#{1,6})\\s$`), (state, match, start, end) => {
33
+ const level = match[1].length;
34
+ return insertBlock(state, schema.nodes.heading, start, end, {
35
+ level
36
+ });
37
+ });
38
+
39
+ // New analytics handler
40
+ const ruleWithHeadingAnalytics = inputRuleWithAnalytics((_state, matchResult) => ({
41
+ action: ACTION.FORMATTED,
42
+ actionSubject: ACTION_SUBJECT.TEXT,
43
+ eventType: EVENT_TYPE.TRACK,
44
+ actionSubjectId: ACTION_SUBJECT_ID.FORMAT_HEADING,
45
+ attributes: {
46
+ inputMethod: INPUT_METHOD.FORMATTING,
47
+ newHeadingLevel: getHeadingLevel(matchResult).level
48
+ }
49
+ }), editorAnalyticsAPI);
50
+ return [ruleWithHeadingAnalytics(hashRule), ruleWithHeadingAnalytics(leftNodeReplacementHashRule)];
51
+ }
52
+
53
+ /**
54
+ * Get all block quote input rules
55
+ *
56
+ * @param {Schema} schema
57
+ * @returns {InputRuleWithHandler[]}
58
+ */
59
+ function getBlockQuoteRules(editorAnalyticsAPI, schema) {
60
+ // '> ' for blockquote
61
+ const greatherThanRule = blockQuoteRule(schema.nodes.blockquote);
62
+ const leftNodeReplacementGreatherRule = createRule(new RegExp(`${leafNodeReplacementCharacter}\\s*>\\s$`), (state, _match, start, end) => {
63
+ return insertBlock(state, schema.nodes.blockquote, start, end);
64
+ });
65
+
66
+ // Analytics V3 handler
67
+ const ruleWithBlockQuoteAnalytics = inputRuleWithAnalytics({
68
+ action: ACTION.FORMATTED,
69
+ actionSubject: ACTION_SUBJECT.TEXT,
70
+ eventType: EVENT_TYPE.TRACK,
71
+ actionSubjectId: ACTION_SUBJECT_ID.FORMAT_BLOCK_QUOTE,
72
+ attributes: {
73
+ inputMethod: INPUT_METHOD.FORMATTING
74
+ }
75
+ }, editorAnalyticsAPI);
76
+ return [ruleWithBlockQuoteAnalytics(greatherThanRule), ruleWithBlockQuoteAnalytics(leftNodeReplacementGreatherRule)];
77
+ }
78
+ function inputRulePlugin(editorAnalyticsAPI, schema, featureFlags) {
79
+ const rules = [];
80
+ if (schema.nodes.heading) {
81
+ rules.push(...getHeadingRules(editorAnalyticsAPI, schema));
82
+ }
83
+ if (schema.nodes.blockquote) {
84
+ rules.push(...getBlockQuoteRules(editorAnalyticsAPI, schema));
85
+ }
86
+ if (rules.length !== 0) {
87
+ return createPlugin('block-type', rules, {
88
+ isBlockNodeRule: true
89
+ });
90
+ }
91
+ return;
92
+ }
93
+ export default inputRulePlugin;
@@ -0,0 +1,25 @@
1
+ import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
+ import { backspace, bindKeymapWithCommand, deleteKey, findKeyMapForBrowser, findShortcutByKeymap, forwardDelete, insertNewLine, keymap, moveDown, moveUp, redo as redoKeymap, toggleBlockQuote, undo as undoKeymap } from '@atlaskit/editor-common/keymaps';
3
+ import { createNewParagraphAbove, createNewParagraphBelow, deleteEmptyParagraphAndMoveBlockUp, insertNewLineWithAnalytics } from '@atlaskit/editor-common/utils';
4
+ import { chainCommands } from '@atlaskit/editor-prosemirror/commands';
5
+ import { redo, undo } from '@atlaskit/editor-prosemirror/history';
6
+ import * as blockTypes from '../block-types';
7
+ import { cleanUpAtTheStartOfDocument, deleteAndMoveCursor, deleteBlockContent, insertBlockQuoteWithAnalytics } from '../commands';
8
+ import { isNodeAWrappingBlockNode } from '../utils';
9
+ const backspaceCommand = chainCommands(cleanUpAtTheStartOfDocument, deleteBlockContent(isNodeAWrappingBlockNode), deleteAndMoveCursor);
10
+ const del = chainCommands(deleteEmptyParagraphAndMoveBlockUp(isNodeAWrappingBlockNode), deleteBlockContent(isNodeAWrappingBlockNode), deleteAndMoveCursor);
11
+ export default function keymapPlugin(editorAnalyticsApi, schema, _featureFlags) {
12
+ const list = {};
13
+ bindKeymapWithCommand(insertNewLine.common, insertNewLineWithAnalytics(editorAnalyticsApi), list);
14
+ bindKeymapWithCommand(moveUp.common, createNewParagraphAbove, list);
15
+ bindKeymapWithCommand(moveDown.common, createNewParagraphBelow, list);
16
+ bindKeymapWithCommand(findKeyMapForBrowser(redoKeymap), redo, list);
17
+ bindKeymapWithCommand(undoKeymap.common, undo, list);
18
+ bindKeymapWithCommand(backspace.common, backspaceCommand, list);
19
+ bindKeymapWithCommand(deleteKey.common, del, list);
20
+ bindKeymapWithCommand(forwardDelete.mac, del, list);
21
+ if (schema.nodes[blockTypes.BLOCK_QUOTE.nodeName]) {
22
+ bindKeymapWithCommand(findShortcutByKeymap(toggleBlockQuote), insertBlockQuoteWithAnalytics(INPUT_METHOD.KEYBOARD, editorAnalyticsApi), list);
23
+ }
24
+ return keymap(list);
25
+ }
@@ -0,0 +1,137 @@
1
+ import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
+ import { browser } from '@atlaskit/editor-common/utils';
4
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
5
+ 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 { setHeadingWithAnalytics, setNormalTextWithAnalytics } from '../commands';
7
+ import { HEADING_KEYS } from '../consts';
8
+ import { areBlockTypesDisabled } from '../utils';
9
+ const blockTypeForNode = (node, schema) => {
10
+ if (node.type === schema.nodes.heading) {
11
+ const maybeNode = HEADINGS_BY_LEVEL[node.attrs['level']];
12
+ if (maybeNode) {
13
+ return maybeNode;
14
+ }
15
+ } else if (node.type === schema.nodes.paragraph) {
16
+ return NORMAL_TEXT;
17
+ }
18
+ return OTHER;
19
+ };
20
+ const isBlockTypeSchemaSupported = (blockType, state) => {
21
+ switch (blockType) {
22
+ case NORMAL_TEXT:
23
+ return !!state.schema.nodes.paragraph;
24
+ case HEADING_1:
25
+ case HEADING_2:
26
+ case HEADING_3:
27
+ case HEADING_4:
28
+ case HEADING_5:
29
+ case HEADING_6:
30
+ return !!state.schema.nodes.heading;
31
+ case BLOCK_QUOTE:
32
+ return !!state.schema.nodes.blockquote;
33
+ case CODE_BLOCK:
34
+ return !!state.schema.nodes.codeBlock;
35
+ case PANEL:
36
+ return !!state.schema.nodes.panel;
37
+ }
38
+ return;
39
+ };
40
+ const detectBlockType = (availableBlockTypes, state) => {
41
+ // Before a document is loaded, there is no selection.
42
+ if (!state.selection) {
43
+ return NORMAL_TEXT;
44
+ }
45
+ let blockType;
46
+ const {
47
+ $from,
48
+ $to
49
+ } = state.selection;
50
+ state.doc.nodesBetween($from.pos, $to.pos, node => {
51
+ const nodeBlockType = availableBlockTypes.filter(blockType => blockType === blockTypeForNode(node, state.schema));
52
+ if (nodeBlockType.length > 0) {
53
+ if (!blockType) {
54
+ blockType = nodeBlockType[0];
55
+ } else if (blockType !== OTHER && blockType !== nodeBlockType[0]) {
56
+ blockType = OTHER;
57
+ }
58
+ }
59
+ });
60
+ return blockType || OTHER;
61
+ };
62
+ const autoformatHeading = (headingLevel, view, editorAnalyticsApi) => {
63
+ if (headingLevel === 0) {
64
+ setNormalTextWithAnalytics(INPUT_METHOD.FORMATTING, editorAnalyticsApi)(view.state, view.dispatch);
65
+ } else {
66
+ setHeadingWithAnalytics(headingLevel, INPUT_METHOD.FORMATTING, editorAnalyticsApi)(view.state, view.dispatch);
67
+ }
68
+ return true;
69
+ };
70
+ export const pluginKey = new PluginKey('blockTypePlugin');
71
+ export const createPlugin = (editorAnalyticsApi, dispatch, lastNodeMustBeParagraph) => {
72
+ let altKeyLocation = 0;
73
+ return new SafePlugin({
74
+ appendTransaction(_transactions, _oldState, newState) {
75
+ if (lastNodeMustBeParagraph) {
76
+ const pos = newState.doc.resolve(newState.doc.content.size - 1);
77
+ const lastNode = pos.node(1);
78
+ const {
79
+ paragraph
80
+ } = newState.schema.nodes;
81
+ if (lastNode && lastNode.isBlock && lastNode.type !== paragraph) {
82
+ return newState.tr.insert(newState.doc.content.size, newState.schema.nodes.paragraph.create()).setMeta('addToHistory', false);
83
+ }
84
+ }
85
+ },
86
+ state: {
87
+ init(_config, state) {
88
+ const availableBlockTypes = TEXT_BLOCK_TYPES.filter(blockType => isBlockTypeSchemaSupported(blockType, state));
89
+ const availableWrapperBlockTypes = WRAPPER_BLOCK_TYPES.filter(blockType => isBlockTypeSchemaSupported(blockType, state));
90
+ return {
91
+ currentBlockType: detectBlockType(availableBlockTypes, state),
92
+ blockTypesDisabled: areBlockTypesDisabled(state),
93
+ availableBlockTypes,
94
+ availableWrapperBlockTypes
95
+ };
96
+ },
97
+ apply(_tr, oldPluginState, _oldState, newState) {
98
+ const newPluginState = {
99
+ ...oldPluginState,
100
+ currentBlockType: detectBlockType(oldPluginState.availableBlockTypes, newState),
101
+ blockTypesDisabled: areBlockTypesDisabled(newState)
102
+ };
103
+ if (newPluginState.currentBlockType !== oldPluginState.currentBlockType || newPluginState.blockTypesDisabled !== oldPluginState.blockTypesDisabled) {
104
+ dispatch(pluginKey, newPluginState);
105
+ }
106
+ return newPluginState;
107
+ }
108
+ },
109
+ key: pluginKey,
110
+ props: {
111
+ /**
112
+ * As we only want the left alt key to work for headings shortcuts on Windows
113
+ * we can't use prosemirror-keymap and need to handle these shortcuts specially
114
+ * Shortcut on Mac: Cmd-Opt-{heading level}
115
+ * Shortcut on Windows: Ctrl-LeftAlt-{heading level}
116
+ */
117
+ handleKeyDown: (view, event) => {
118
+ const headingLevel = HEADING_KEYS.indexOf(event.keyCode);
119
+ if (headingLevel > -1 && event.altKey) {
120
+ if (browser.mac && event.metaKey) {
121
+ return autoformatHeading(headingLevel, view, editorAnalyticsApi);
122
+ } else if (!browser.mac && event.ctrlKey && altKeyLocation !== event.DOM_KEY_LOCATION_RIGHT) {
123
+ return autoformatHeading(headingLevel, view, editorAnalyticsApi);
124
+ }
125
+ } else if (event.key === 'Alt') {
126
+ // event.location is for the current key only; when a user hits Ctrl-Alt-1 the
127
+ // location refers to the location of the '1' key
128
+ // We store the location of the Alt key when it is hit to check against later
129
+ altKeyLocation = event.location;
130
+ } else if (!event.altKey) {
131
+ altKeyLocation = 0;
132
+ }
133
+ return false;
134
+ }
135
+ }
136
+ });
137
+ };
@@ -0,0 +1,8 @@
1
+ import { css } from '@emotion/react';
2
+ import { blockquoteSharedStyles, headingsSharedStyles } from '@atlaskit/editor-common/styles';
3
+ export const blocktypeStyles = props => css`
4
+ .ProseMirror {
5
+ ${blockquoteSharedStyles};
6
+ ${headingsSharedStyles(props)};
7
+ }
8
+ `;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ /** @jsx jsx */
2
+ import React from 'react';
3
+ import { jsx } from '@emotion/react';
4
+ import { defineMessages, FormattedMessage } from 'react-intl-next';
5
+ import { wrapperStyle } from '@atlaskit/editor-common/styles';
6
+ import { ToolbarButton } from '@atlaskit/editor-common/ui-menu';
7
+ import ExpandIcon from '@atlaskit/icon/glyph/chevron-down';
8
+ import TextStyleIcon from '@atlaskit/icon/glyph/editor/text-style';
9
+ import { NORMAL_TEXT } from '../../block-types';
10
+ import { buttonContentReducedSpacingStyle, buttonContentStyle, expandIconWrapperStyle, wrapperSmallStyle } from './styled';
11
+ export const messages = defineMessages({
12
+ textStyles: {
13
+ id: 'fabric.editor.textStyles',
14
+ defaultMessage: 'Text styles',
15
+ description: 'Menu provides access to various heading styles or normal text'
16
+ }
17
+ });
18
+ export const BlockTypeButton = props => {
19
+ const labelTextStyles = props.formatMessage(messages.textStyles);
20
+ return jsx(ToolbarButton, {
21
+ spacing: props.isReducedSpacing ? 'none' : 'default',
22
+ selected: props.selected,
23
+ className: "block-type-btn",
24
+ disabled: props.disabled,
25
+ onClick: props.onClick,
26
+ onKeyDown: props.onKeyDown,
27
+ title: labelTextStyles,
28
+ "aria-label": labelTextStyles,
29
+ "aria-haspopup": true,
30
+ "aria-expanded": props['aria-expanded'],
31
+ iconAfter: jsx("span", {
32
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
33
+ css: [wrapperStyle, props.isSmall && wrapperSmallStyle],
34
+ "data-testid": "toolbar-block-type-text-styles-icon"
35
+ }, props.isSmall && jsx(TextStyleIcon, {
36
+ label: labelTextStyles
37
+ }), jsx("span", {
38
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
39
+ css: expandIconWrapperStyle
40
+ }, jsx(ExpandIcon, {
41
+ label: ""
42
+ })))
43
+ }, !props.isSmall && jsx("span", {
44
+ css: [
45
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
46
+ buttonContentStyle,
47
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
48
+ props.isReducedSpacing && buttonContentReducedSpacingStyle]
49
+ }, jsx(FormattedMessage, props.title || NORMAL_TEXT.title)));
50
+ };
@@ -0,0 +1,185 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ /** @jsx jsx */
3
+ import React from 'react';
4
+ import { jsx } from '@emotion/react';
5
+ import { injectIntl } from 'react-intl-next';
6
+ import { findKeymapByDescription, getAriaKeyshortcuts, tooltip } from '@atlaskit/editor-common/keymaps';
7
+ import { separatorStyles, wrapperStyle } from '@atlaskit/editor-common/styles';
8
+ import { DropdownMenuWithKeyboardNavigation as DropdownMenu } from '@atlaskit/editor-common/ui-menu';
9
+ import { akEditorMenuZIndex } from '@atlaskit/editor-shared-styles';
10
+ import { BlockTypeButton } from './blocktype-button';
11
+ import { blockTypeMenuItemStyle, keyboardShortcut, keyboardShortcutSelect } from './styled';
12
+ // eslint-disable-next-line @repo/internal/react/no-class-components
13
+ class ToolbarBlockType extends React.PureComponent {
14
+ constructor(...args) {
15
+ super(...args);
16
+ _defineProperty(this, "state", {
17
+ active: false,
18
+ isOpenedByKeyboard: false
19
+ });
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ _defineProperty(this, "onOpenChange", attrs => {
22
+ this.setState({
23
+ ...this.state,
24
+ active: attrs.isOpen,
25
+ isOpenedByKeyboard: attrs.isOpenedByKeyboard
26
+ });
27
+ });
28
+ _defineProperty(this, "handleTriggerClick", () => {
29
+ this.onOpenChange({
30
+ isOpen: !this.state.active,
31
+ isOpenedByKeyboard: false
32
+ });
33
+ });
34
+ _defineProperty(this, "handleTriggerByKeyboard", event => {
35
+ if (event.key === 'Enter' || event.key === ' ') {
36
+ event.preventDefault();
37
+ this.onOpenChange({
38
+ isOpen: !this.state.active,
39
+ isOpenedByKeyboard: true
40
+ });
41
+ }
42
+ });
43
+ _defineProperty(this, "createItems", () => {
44
+ const {
45
+ intl: {
46
+ formatMessage
47
+ }
48
+ } = this.props;
49
+ const {
50
+ currentBlockType,
51
+ availableBlockTypes
52
+ } = this.props.pluginState;
53
+ const items = availableBlockTypes.map((blockType, index) => {
54
+ const isActive = currentBlockType === blockType;
55
+ const tagName = blockType.tagName || 'p';
56
+ const Tag = tagName;
57
+ const keyMap = findKeymapByDescription(blockType.title.defaultMessage);
58
+ return {
59
+ content:
60
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
61
+ jsx("div", {
62
+ css: blockTypeMenuItemStyle(tagName, isActive)
63
+ }, jsx(Tag, null, formatMessage(blockType.title))),
64
+ value: blockType,
65
+ label: formatMessage(blockType.title),
66
+ 'aria-label': tooltip(keyMap, formatMessage(blockType.title)),
67
+ keyShortcuts: getAriaKeyshortcuts(keyMap),
68
+ key: `${blockType.name}-${index}`,
69
+ elemAfter:
70
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
71
+ jsx("div", {
72
+ css: [keyboardShortcut, isActive && keyboardShortcutSelect]
73
+ }, tooltip(keyMap)),
74
+ isActive
75
+ };
76
+ });
77
+ return [{
78
+ items
79
+ }];
80
+ });
81
+ _defineProperty(this, "handleSelectBlockType", ({
82
+ item,
83
+ shouldCloseMenu = true
84
+ }) => {
85
+ const blockType = item.value;
86
+ this.props.setBlockType(blockType.name);
87
+ if (shouldCloseMenu) {
88
+ this.setState({
89
+ ...this.state,
90
+ active: false
91
+ });
92
+ }
93
+ });
94
+ }
95
+ render() {
96
+ const {
97
+ active,
98
+ isOpenedByKeyboard
99
+ } = this.state;
100
+ const {
101
+ popupsMountPoint,
102
+ popupsBoundariesElement,
103
+ popupsScrollableElement,
104
+ isSmall,
105
+ isReducedSpacing,
106
+ pluginState: {
107
+ currentBlockType,
108
+ blockTypesDisabled,
109
+ availableBlockTypes
110
+ },
111
+ intl: {
112
+ formatMessage
113
+ }
114
+ } = this.props;
115
+ const isHeadingDisabled = !availableBlockTypes.some(blockType => blockType.nodeName === 'heading');
116
+ if (isHeadingDisabled) {
117
+ return null;
118
+ }
119
+ const blockTypeTitles = availableBlockTypes.filter(blockType => blockType.name === currentBlockType.name).map(blockType => blockType.title);
120
+ if (!this.props.isDisabled && !blockTypesDisabled) {
121
+ const items = this.createItems();
122
+ return (
123
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
124
+ jsx("span", {
125
+ css: wrapperStyle
126
+ }, jsx(DropdownMenu, {
127
+ items: items,
128
+ onOpenChange: this.onOpenChange,
129
+ onItemActivated: this.handleSelectBlockType,
130
+ isOpen: active,
131
+ mountTo: popupsMountPoint,
132
+ boundariesElement: popupsBoundariesElement,
133
+ scrollableElement: popupsScrollableElement,
134
+ zIndex: akEditorMenuZIndex,
135
+ fitHeight: 360,
136
+ fitWidth: 106,
137
+ shouldUseDefaultRole: true,
138
+ shouldFocusFirstItem: () => {
139
+ if (isOpenedByKeyboard) {
140
+ // eslint-disable-next-line @repo/internal/react/no-set-state-inside-render
141
+ this.setState({
142
+ ...this.state,
143
+ isOpenedByKeyboard: false
144
+ });
145
+ }
146
+ return isOpenedByKeyboard;
147
+ }
148
+ }, jsx(BlockTypeButton, {
149
+ isSmall: isSmall,
150
+ isReducedSpacing: isReducedSpacing,
151
+ selected: active,
152
+ disabled: false,
153
+ title: blockTypeTitles[0],
154
+ onClick: this.handleTriggerClick,
155
+ onKeyDown: this.handleTriggerByKeyboard,
156
+ formatMessage: formatMessage,
157
+ "aria-expanded": active
158
+ })), jsx("span", {
159
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
160
+ css: separatorStyles
161
+ }))
162
+ );
163
+ }
164
+ return (
165
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
166
+ jsx("span", {
167
+ css: wrapperStyle
168
+ }, jsx(BlockTypeButton, {
169
+ isSmall: isSmall,
170
+ isReducedSpacing: isReducedSpacing,
171
+ selected: active,
172
+ disabled: true,
173
+ title: blockTypeTitles[0],
174
+ onClick: this.handleTriggerClick,
175
+ onKeyDown: this.handleTriggerByKeyboard,
176
+ formatMessage: formatMessage,
177
+ "aria-expanded": active
178
+ }), jsx("span", {
179
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
180
+ css: separatorStyles
181
+ }))
182
+ );
183
+ }
184
+ }
185
+ export default injectIntl(ToolbarBlockType);
@@ -0,0 +1,49 @@
1
+ /** @jsx jsx */
2
+ import { css } from '@emotion/react';
3
+ import { headingsSharedStyles } from '@atlaskit/editor-common/styles';
4
+ import { shortcutStyle } from '@atlaskit/editor-shared-styles/shortcut';
5
+ import { N400 } from '@atlaskit/theme/colors';
6
+ export const blockTypeMenuItemStyle = (tagName, selected) => {
7
+ // TEMP FIX: See https://product-fabric.atlassian.net/browse/ED-13878
8
+ const selectedStyle = selected ? `${tagName} { color: ${"var(--ds-text, white)"} !important; }` : '';
9
+ return themeProps => css`
10
+ ${headingsSharedStyles(themeProps)};
11
+ > {
12
+ h1,
13
+ h2,
14
+ h3,
15
+ h4,
16
+ h5,
17
+ h6 {
18
+ margin-top: 0;
19
+ }
20
+ }
21
+ ${selectedStyle};
22
+ `;
23
+ };
24
+ export const keyboardShortcut = css`
25
+ ${shortcutStyle}
26
+ margin-left: ${"var(--ds-space-200, 16px)"};
27
+ `;
28
+ export const keyboardShortcutSelect = css`
29
+ color: ${`var(--ds-icon, ${N400})`};
30
+ `;
31
+ export const buttonContentStyle = css`
32
+ display: flex;
33
+ min-width: 80px;
34
+ align-items: center;
35
+ overflow: hidden;
36
+ justify-content: center;
37
+ flex-direction: column;
38
+ padding: ${"var(--ds-space-075, 6px)"};
39
+ `;
40
+ export const buttonContentReducedSpacingStyle = css`
41
+ padding: ${"var(--ds-space-100, 8px)"};
42
+ `;
43
+ export const wrapperSmallStyle = css`
44
+ margin-left: ${"var(--ds-space-050, 4px)"};
45
+ min-width: 40px;
46
+ `;
47
+ export const expandIconWrapperStyle = css`
48
+ margin-left: -8px;
49
+ `;
@@ -0,0 +1,8 @@
1
+ import { defineMessages } from 'react-intl-next';
2
+ export const toolbarMessages = defineMessages({
3
+ textStyles: {
4
+ id: 'fabric.editor.textStyles',
5
+ defaultMessage: 'Text styles',
6
+ description: 'Menu provides access to various heading styles or normal text'
7
+ }
8
+ });
@@ -0,0 +1,76 @@
1
+ import { createWrappingJoinRule } from '@atlaskit/editor-common/utils';
2
+ import { createRule } from '@atlaskit/prosemirror-input-rules';
3
+ import { WRAPPER_BLOCK_TYPES } from './block-types';
4
+ export const isNodeAWrappingBlockNode = node => {
5
+ if (!node) {
6
+ return false;
7
+ }
8
+ return WRAPPER_BLOCK_TYPES.some(blockNode => blockNode.name === node.type.name);
9
+ };
10
+ export const createJoinNodesRule = (match, nodeType) => {
11
+ return createWrappingJoinRule({
12
+ nodeType,
13
+ match,
14
+ getAttrs: {},
15
+ joinPredicate: (_, node) => node.type === nodeType
16
+ });
17
+ };
18
+ export const createWrappingTextBlockRule = ({
19
+ match,
20
+ nodeType,
21
+ getAttrs
22
+ }) => {
23
+ const handler = (state, match, start, end) => {
24
+ const fixedStart = Math.max(start, 1);
25
+ const $start = state.doc.resolve(fixedStart);
26
+ const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
27
+ const nodeBefore = $start.node(-1);
28
+ if (nodeBefore && !nodeBefore.canReplaceWith($start.index(-1), $start.indexAfter(-1), nodeType)) {
29
+ return null;
30
+ }
31
+ return state.tr.delete(fixedStart, end).setBlockType(fixedStart, fixedStart, nodeType, attrs);
32
+ };
33
+ return createRule(match, handler);
34
+ };
35
+
36
+ /**
37
+ * Function will create a list of wrapper blocks present in a selection.
38
+ */
39
+ function getSelectedWrapperNodes(state) {
40
+ const nodes = [];
41
+ if (state.selection) {
42
+ const {
43
+ $from,
44
+ $to
45
+ } = state.selection;
46
+ const {
47
+ blockquote,
48
+ panel,
49
+ orderedList,
50
+ bulletList,
51
+ listItem,
52
+ codeBlock,
53
+ decisionItem,
54
+ decisionList,
55
+ taskItem,
56
+ taskList
57
+ } = state.schema.nodes;
58
+ state.doc.nodesBetween($from.pos, $to.pos, node => {
59
+ if (node.isBlock && [blockquote, panel, orderedList, bulletList, listItem, codeBlock, decisionItem, decisionList, taskItem, taskList].indexOf(node.type) >= 0) {
60
+ nodes.push(node.type);
61
+ }
62
+ });
63
+ }
64
+ return nodes;
65
+ }
66
+
67
+ /**
68
+ * Function will check if changing block types: Paragraph, Heading is enabled.
69
+ */
70
+ export function areBlockTypesDisabled(state) {
71
+ const nodesTypes = getSelectedWrapperNodes(state);
72
+ const {
73
+ panel
74
+ } = state.schema.nodes;
75
+ return nodesTypes.filter(type => type !== panel).length > 0;
76
+ }
@@ -0,0 +1 @@
1
+ export { blocktypeStyles } from './plugin/styles';
@@ -0,0 +1 @@
1
+ export { BLOCK_QUOTE, CODE_BLOCK, HEADING_1, HEADING_2, HEADING_3, HEADING_4, HEADING_5, HEADING_6, NORMAL_TEXT, PANEL } from './plugin/block-types';
@@ -0,0 +1 @@
1
+ export { blockTypePlugin } from './plugin';
@@ -0,0 +1,2 @@
1
+ export { messages } from './plugin/messages';
2
+ export { toolbarMessages } from './plugin/ui/ToolbarBlockType/toolbar-messages';