@atlaskit/editor-plugin-text-formatting 0.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 (140) hide show
  1. package/.eslintrc.js +7 -0
  2. package/CHANGELOG.md +1 -0
  3. package/LICENSE.md +13 -0
  4. package/README.md +9 -0
  5. package/dist/cjs/actions.js +188 -0
  6. package/dist/cjs/commands/clear-formatting.js +111 -0
  7. package/dist/cjs/commands/text-formatting.js +143 -0
  8. package/dist/cjs/commands/transform-to-code.js +68 -0
  9. package/dist/cjs/index.js +12 -0
  10. package/dist/cjs/plugin.js +133 -0
  11. package/dist/cjs/pm-plugins/clear-formatting-keymap.js +21 -0
  12. package/dist/cjs/pm-plugins/clear-formatting.js +36 -0
  13. package/dist/cjs/pm-plugins/cursor.js +55 -0
  14. package/dist/cjs/pm-plugins/input-rule.js +274 -0
  15. package/dist/cjs/pm-plugins/keymap.js +52 -0
  16. package/dist/cjs/pm-plugins/main.js +113 -0
  17. package/dist/cjs/pm-plugins/plugin-key.js +9 -0
  18. package/dist/cjs/pm-plugins/smart-input-rule.js +176 -0
  19. package/dist/cjs/ui/Toolbar/constants.js +19 -0
  20. package/dist/cjs/ui/Toolbar/dropdown-menu.js +86 -0
  21. package/dist/cjs/ui/Toolbar/hooks/clear-formatting-icon.js +55 -0
  22. package/dist/cjs/ui/Toolbar/hooks/formatting-icons.js +227 -0
  23. package/dist/cjs/ui/Toolbar/hooks/menu-state.js +23 -0
  24. package/dist/cjs/ui/Toolbar/hooks/responsive-toolbar-buttons.js +60 -0
  25. package/dist/cjs/ui/Toolbar/index.js +183 -0
  26. package/dist/cjs/ui/Toolbar/more-button.js +42 -0
  27. package/dist/cjs/ui/Toolbar/single-toolbar-buttons.js +49 -0
  28. package/dist/cjs/ui/Toolbar/types.js +17 -0
  29. package/dist/cjs/utils/cell-selection.js +12 -0
  30. package/dist/cjs/utils.js +86 -0
  31. package/dist/cjs/version.json +5 -0
  32. package/dist/es2019/actions.js +161 -0
  33. package/dist/es2019/commands/clear-formatting.js +105 -0
  34. package/dist/es2019/commands/text-formatting.js +144 -0
  35. package/dist/es2019/commands/transform-to-code.js +71 -0
  36. package/dist/es2019/index.js +1 -0
  37. package/dist/es2019/plugin.js +124 -0
  38. package/dist/es2019/pm-plugins/clear-formatting-keymap.js +10 -0
  39. package/dist/es2019/pm-plugins/clear-formatting.js +26 -0
  40. package/dist/es2019/pm-plugins/cursor.js +52 -0
  41. package/dist/es2019/pm-plugins/input-rule.js +242 -0
  42. package/dist/es2019/pm-plugins/keymap.js +43 -0
  43. package/dist/es2019/pm-plugins/main.js +110 -0
  44. package/dist/es2019/pm-plugins/plugin-key.js +2 -0
  45. package/dist/es2019/pm-plugins/smart-input-rule.js +155 -0
  46. package/dist/es2019/ui/Toolbar/constants.js +20 -0
  47. package/dist/es2019/ui/Toolbar/dropdown-menu.js +66 -0
  48. package/dist/es2019/ui/Toolbar/hooks/clear-formatting-icon.js +44 -0
  49. package/dist/es2019/ui/Toolbar/hooks/formatting-icons.js +212 -0
  50. package/dist/es2019/ui/Toolbar/hooks/menu-state.js +11 -0
  51. package/dist/es2019/ui/Toolbar/hooks/responsive-toolbar-buttons.js +48 -0
  52. package/dist/es2019/ui/Toolbar/index.js +168 -0
  53. package/dist/es2019/ui/Toolbar/more-button.js +34 -0
  54. package/dist/es2019/ui/Toolbar/single-toolbar-buttons.js +39 -0
  55. package/dist/es2019/ui/Toolbar/types.js +10 -0
  56. package/dist/es2019/utils/cell-selection.js +5 -0
  57. package/dist/es2019/utils.js +74 -0
  58. package/dist/es2019/version.json +5 -0
  59. package/dist/esm/actions.js +168 -0
  60. package/dist/esm/commands/clear-formatting.js +101 -0
  61. package/dist/esm/commands/text-formatting.js +134 -0
  62. package/dist/esm/commands/transform-to-code.js +61 -0
  63. package/dist/esm/index.js +1 -0
  64. package/dist/esm/plugin.js +125 -0
  65. package/dist/esm/pm-plugins/clear-formatting-keymap.js +10 -0
  66. package/dist/esm/pm-plugins/clear-formatting.js +28 -0
  67. package/dist/esm/pm-plugins/cursor.js +48 -0
  68. package/dist/esm/pm-plugins/input-rule.js +257 -0
  69. package/dist/esm/pm-plugins/keymap.js +43 -0
  70. package/dist/esm/pm-plugins/main.js +99 -0
  71. package/dist/esm/pm-plugins/plugin-key.js +2 -0
  72. package/dist/esm/pm-plugins/smart-input-rule.js +169 -0
  73. package/dist/esm/ui/Toolbar/constants.js +8 -0
  74. package/dist/esm/ui/Toolbar/dropdown-menu.js +75 -0
  75. package/dist/esm/ui/Toolbar/hooks/clear-formatting-icon.js +47 -0
  76. package/dist/esm/ui/Toolbar/hooks/formatting-icons.js +215 -0
  77. package/dist/esm/ui/Toolbar/hooks/menu-state.js +15 -0
  78. package/dist/esm/ui/Toolbar/hooks/responsive-toolbar-buttons.js +50 -0
  79. package/dist/esm/ui/Toolbar/index.js +174 -0
  80. package/dist/esm/ui/Toolbar/more-button.js +33 -0
  81. package/dist/esm/ui/Toolbar/single-toolbar-buttons.js +38 -0
  82. package/dist/esm/ui/Toolbar/types.js +10 -0
  83. package/dist/esm/utils/cell-selection.js +5 -0
  84. package/dist/esm/utils.js +75 -0
  85. package/dist/esm/version.json +5 -0
  86. package/dist/types/actions.d.ts +22 -0
  87. package/dist/types/commands/clear-formatting.d.ts +6 -0
  88. package/dist/types/commands/text-formatting.d.ts +5 -0
  89. package/dist/types/commands/transform-to-code.d.ts +2 -0
  90. package/dist/types/index.d.ts +3 -0
  91. package/dist/types/plugin.d.ts +17 -0
  92. package/dist/types/pm-plugins/clear-formatting-keymap.d.ts +4 -0
  93. package/dist/types/pm-plugins/clear-formatting.d.ts +8 -0
  94. package/dist/types/pm-plugins/cursor.d.ts +3 -0
  95. package/dist/types/pm-plugins/input-rule.d.ts +23 -0
  96. package/dist/types/pm-plugins/keymap.d.ts +4 -0
  97. package/dist/types/pm-plugins/main.d.ts +7 -0
  98. package/dist/types/pm-plugins/plugin-key.d.ts +3 -0
  99. package/dist/types/pm-plugins/smart-input-rule.d.ts +3 -0
  100. package/dist/types/ui/Toolbar/constants.d.ts +6 -0
  101. package/dist/types/ui/Toolbar/dropdown-menu.d.ts +15 -0
  102. package/dist/types/ui/Toolbar/hooks/clear-formatting-icon.d.ts +7 -0
  103. package/dist/types/ui/Toolbar/hooks/formatting-icons.d.ts +14 -0
  104. package/dist/types/ui/Toolbar/hooks/menu-state.d.ts +1 -0
  105. package/dist/types/ui/Toolbar/hooks/responsive-toolbar-buttons.d.ts +20 -0
  106. package/dist/types/ui/Toolbar/index.d.ts +25 -0
  107. package/dist/types/ui/Toolbar/more-button.d.ts +14 -0
  108. package/dist/types/ui/Toolbar/single-toolbar-buttons.d.ts +10 -0
  109. package/dist/types/ui/Toolbar/types.d.ts +32 -0
  110. package/dist/types/utils/cell-selection.d.ts +3 -0
  111. package/dist/types/utils.d.ts +11 -0
  112. package/dist/types-ts4.5/actions.d.ts +22 -0
  113. package/dist/types-ts4.5/commands/clear-formatting.d.ts +6 -0
  114. package/dist/types-ts4.5/commands/text-formatting.d.ts +5 -0
  115. package/dist/types-ts4.5/commands/transform-to-code.d.ts +2 -0
  116. package/dist/types-ts4.5/index.d.ts +3 -0
  117. package/dist/types-ts4.5/plugin.d.ts +19 -0
  118. package/dist/types-ts4.5/pm-plugins/clear-formatting-keymap.d.ts +4 -0
  119. package/dist/types-ts4.5/pm-plugins/clear-formatting.d.ts +8 -0
  120. package/dist/types-ts4.5/pm-plugins/cursor.d.ts +3 -0
  121. package/dist/types-ts4.5/pm-plugins/input-rule.d.ts +23 -0
  122. package/dist/types-ts4.5/pm-plugins/keymap.d.ts +4 -0
  123. package/dist/types-ts4.5/pm-plugins/main.d.ts +7 -0
  124. package/dist/types-ts4.5/pm-plugins/plugin-key.d.ts +3 -0
  125. package/dist/types-ts4.5/pm-plugins/smart-input-rule.d.ts +3 -0
  126. package/dist/types-ts4.5/ui/Toolbar/constants.d.ts +6 -0
  127. package/dist/types-ts4.5/ui/Toolbar/dropdown-menu.d.ts +15 -0
  128. package/dist/types-ts4.5/ui/Toolbar/hooks/clear-formatting-icon.d.ts +7 -0
  129. package/dist/types-ts4.5/ui/Toolbar/hooks/formatting-icons.d.ts +14 -0
  130. package/dist/types-ts4.5/ui/Toolbar/hooks/menu-state.d.ts +5 -0
  131. package/dist/types-ts4.5/ui/Toolbar/hooks/responsive-toolbar-buttons.d.ts +20 -0
  132. package/dist/types-ts4.5/ui/Toolbar/index.d.ts +25 -0
  133. package/dist/types-ts4.5/ui/Toolbar/more-button.d.ts +14 -0
  134. package/dist/types-ts4.5/ui/Toolbar/single-toolbar-buttons.d.ts +10 -0
  135. package/dist/types-ts4.5/ui/Toolbar/types.d.ts +32 -0
  136. package/dist/types-ts4.5/utils/cell-selection.d.ts +3 -0
  137. package/dist/types-ts4.5/utils.d.ts +11 -0
  138. package/package.json +93 -0
  139. package/report.api.md +66 -0
  140. package/tmp/api-report-tmp.d.ts +39 -0
@@ -0,0 +1,155 @@
1
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, PUNC, SYMBOL } from '@atlaskit/editor-common/analytics';
2
+ import { inputRuleWithAnalytics } from '@atlaskit/editor-common/utils';
3
+ import { Selection } from '@atlaskit/editor-prosemirror/state';
4
+ import { createPlugin, createRule } from '@atlaskit/prosemirror-input-rules';
5
+
6
+ /**
7
+ * Creates an InputRuleHandler that will match on a regular expression of the
8
+ * form `(prefix, content, suffix?)`, and replace it with some given text,
9
+ * maintaining prefix and suffix around the replacement.
10
+ *
11
+ * @param text text to replace with
12
+ */
13
+ function replaceTextUsingCaptureGroup(text) {
14
+ return (state, match, start, end) => {
15
+ const [, prefix,, suffix] = match;
16
+ const replacement = text + (suffix || '');
17
+ let {
18
+ tr,
19
+ selection: {
20
+ $to
21
+ }
22
+ } = state;
23
+ const startPos = start + (prefix || '').length;
24
+ const safePos = Math.min(
25
+ // I know it is almost impossible to have a negative value at this point.
26
+ // But, this is JS #trustnoone
27
+ Math.max(startPos, 0), end);
28
+ tr.replaceWith(safePos, end, state.schema.text(replacement, $to.marks()));
29
+ tr.setSelection(Selection.near(tr.doc.resolve(tr.selection.to)));
30
+ return tr;
31
+ };
32
+ }
33
+ function createReplacementRule(to, from) {
34
+ return createRule(from, replaceTextUsingCaptureGroup(to));
35
+ }
36
+
37
+ /**
38
+ * Create replacement rules fiven a replacement map
39
+ * @param replMap - Replacement map
40
+ * @param trackingEventName - Analytics V2 tracking event name
41
+ * @param replacementRuleWithAnalytics - Analytics GAS V3 middleware for replacement and rules.
42
+ */
43
+ function createReplacementRules(replMap, replacementRuleWithAnalytics) {
44
+ return Object.keys(replMap).map(replacement => {
45
+ const regex = replMap[replacement];
46
+ const rule = createReplacementRule(replacement, regex);
47
+ if (replacementRuleWithAnalytics) {
48
+ return replacementRuleWithAnalytics(replacement)(rule);
49
+ }
50
+ return rule;
51
+ });
52
+ }
53
+
54
+ // We don't agressively upgrade single quotes to smart quotes because
55
+ // they may clash with an emoji. Only do that when we have a matching
56
+ // single quote, or a contraction.
57
+ function createSingleQuotesRules() {
58
+ return [
59
+ // wrapped text
60
+ createRule(/(\s|^)'(\S+.*\S+)'$/, (state, match, start, end) => {
61
+ const OPEN_SMART_QUOTE_CHAR = '‘';
62
+ const CLOSED_SMART_QUOTE_CHAR = '’';
63
+ const [, spacing, innerContent] = match;
64
+ // Edge case where match begins with some spacing. We need to add
65
+ // it back to the document
66
+ const openQuoteReplacement = spacing + OPEN_SMART_QUOTE_CHAR;
67
+ // End is not always where the closed quote is, edge case exists
68
+ // when there is spacing after the closed quote. We need to
69
+ // determine position of closed quote from the start position.
70
+ const positionOfClosedQuote = start + openQuoteReplacement.length + innerContent.length;
71
+ return state.tr.insertText(CLOSED_SMART_QUOTE_CHAR, positionOfClosedQuote, end).insertText(openQuoteReplacement, start, start + openQuoteReplacement.length);
72
+ }),
73
+ // apostrophe
74
+ createReplacementRule('’', /(\w+)(')(\w+)$/)];
75
+ }
76
+
77
+ /**
78
+ * Get replacement rules related to product
79
+ */
80
+ function getProductRules(editorAnalyticsAPI) {
81
+ const productRuleWithAnalytics = product => inputRuleWithAnalytics((_, match) => ({
82
+ action: ACTION.SUBSTITUTED,
83
+ actionSubject: ACTION_SUBJECT.TEXT,
84
+ actionSubjectId: ACTION_SUBJECT_ID.PRODUCT_NAME,
85
+ eventType: EVENT_TYPE.TRACK,
86
+ attributes: {
87
+ product,
88
+ originalSpelling: match[2]
89
+ }
90
+ }), editorAnalyticsAPI);
91
+ return createReplacementRules({
92
+ Atlassian: /(\s+|^)(atlassian)(\s)$/,
93
+ Jira: /(\s+|^)(jira|JIRA)(\s)$/,
94
+ Bitbucket: /(\s+|^)(bitbucket|BitBucket)(\s)$/,
95
+ Hipchat: /(\s+|^)(hipchat|HipChat)(\s)$/,
96
+ Trello: /(\s+|^)(trello)(\s)$/
97
+ }, productRuleWithAnalytics);
98
+ }
99
+
100
+ /**
101
+ * Get replacement rules related to symbol
102
+ */
103
+ function getSymbolRules(editorAnalyticsAPI) {
104
+ const symbolToString = {
105
+ '→': SYMBOL.ARROW_RIGHT,
106
+ '←': SYMBOL.ARROW_LEFT,
107
+ '↔︎': SYMBOL.ARROW_DOUBLE
108
+ };
109
+ const symbolRuleWithAnalytics = symbol => inputRuleWithAnalytics({
110
+ action: ACTION.SUBSTITUTED,
111
+ actionSubject: ACTION_SUBJECT.TEXT,
112
+ actionSubjectId: ACTION_SUBJECT_ID.SYMBOL,
113
+ eventType: EVENT_TYPE.TRACK,
114
+ attributes: {
115
+ symbol: symbolToString[symbol]
116
+ }
117
+ }, editorAnalyticsAPI);
118
+ return createReplacementRules({
119
+ '→': /(\s+|^)(--?>)(\s)$/,
120
+ '←': /(\s+|^)(<--?)(\s)$/,
121
+ '↔︎': /(\s+|^)(<->?)(\s)$/
122
+ }, symbolRuleWithAnalytics);
123
+ }
124
+
125
+ /**
126
+ * Get replacement rules related to punctuation
127
+ */
128
+ function getPunctuationRules(editorAnalyticsAPI) {
129
+ const punctuationToString = {
130
+ '–': PUNC.DASH,
131
+ '…': PUNC.ELLIPSIS,
132
+ '”': PUNC.QUOTE_DOUBLE,
133
+ [PUNC.QUOTE_SINGLE]: PUNC.QUOTE_SINGLE
134
+ };
135
+ const punctuationRuleWithAnalytics = punctuation => inputRuleWithAnalytics({
136
+ action: ACTION.SUBSTITUTED,
137
+ actionSubject: ACTION_SUBJECT.TEXT,
138
+ actionSubjectId: ACTION_SUBJECT_ID.PUNC,
139
+ eventType: EVENT_TYPE.TRACK,
140
+ attributes: {
141
+ punctuation: punctuationToString[punctuation]
142
+ }
143
+ }, editorAnalyticsAPI);
144
+ const dashEllipsisRules = createReplacementRules({
145
+ '–': /(\s+|^)(--)(\s)$/,
146
+ '…': /()(\.\.\.)$/
147
+ }, punctuationRuleWithAnalytics);
148
+ const doubleQuoteRules = createReplacementRules({
149
+ '“': /((?:^|[\s\{\[\(\<'"\u2018\u201C]))(")$/,
150
+ '”': /"$/
151
+ }, punctuationRuleWithAnalytics);
152
+ const singleQuoteRules = createSingleQuotesRules();
153
+ return [...dashEllipsisRules, ...doubleQuoteRules, ...singleQuoteRules.map(rule => punctuationRuleWithAnalytics(PUNC.QUOTE_SINGLE)(rule))];
154
+ }
155
+ export default (editorAnalyticsAPI => createPlugin('text-formatting:smart-input', [...getProductRules(editorAnalyticsAPI), ...getSymbolRules(editorAnalyticsAPI), ...getPunctuationRules(editorAnalyticsAPI)]));
@@ -0,0 +1,20 @@
1
+ import { ToolbarSize } from '@atlaskit/editor-common/types';
2
+ import { IconTypes } from './types';
3
+ export const DefaultButtonsToolbar = [IconTypes.strong, IconTypes.em];
4
+ export const DefaultButtonsMenu = [IconTypes.underline, IconTypes.strike, IconTypes.code, IconTypes.subscript, IconTypes.superscript];
5
+ export const ResponsiveCustomButtonToolbar = {
6
+ [ToolbarSize.XXL]: DefaultButtonsToolbar,
7
+ [ToolbarSize.XL]: DefaultButtonsToolbar,
8
+ [ToolbarSize.L]: DefaultButtonsToolbar,
9
+ [ToolbarSize.M]: [],
10
+ [ToolbarSize.S]: [],
11
+ [ToolbarSize.XXXS]: []
12
+ };
13
+ export const ResponsiveCustomMenu = {
14
+ [ToolbarSize.XXL]: DefaultButtonsMenu,
15
+ [ToolbarSize.XL]: DefaultButtonsMenu,
16
+ [ToolbarSize.L]: DefaultButtonsMenu,
17
+ [ToolbarSize.M]: [IconTypes.strong, IconTypes.em, ...DefaultButtonsMenu],
18
+ [ToolbarSize.S]: [IconTypes.strong, IconTypes.em, ...DefaultButtonsMenu],
19
+ [ToolbarSize.XXXS]: [IconTypes.strong, IconTypes.em, ...DefaultButtonsMenu]
20
+ };
@@ -0,0 +1,66 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import { DropdownMenuWithKeyboardNavigation as DropdownMenu } from '@atlaskit/editor-common/ui-menu';
3
+ import { akEditorMenuZIndex } from '@atlaskit/editor-shared-styles';
4
+ import { useMenuState } from './hooks/menu-state';
5
+ import { MoreButton } from './more-button';
6
+ export const FormattingTextDropdownMenu = /*#__PURE__*/React.memo(({
7
+ editorView,
8
+ moreButtonLabel,
9
+ isReducedSpacing,
10
+ items,
11
+ hasFormattingActive,
12
+ popupsBoundariesElement,
13
+ popupsMountPoint,
14
+ popupsScrollableElement
15
+ }) => {
16
+ const [isMenuOpen, toggleMenu, closeMenu] = useMenuState();
17
+ const [isOpenedByKeyboard, setIsOpenedByKeyboard] = useState(false);
18
+ const group = useMemo(() => [{
19
+ items
20
+ }], [items]);
21
+ const onItemActivated = useCallback(({
22
+ item,
23
+ shouldCloseMenu = true
24
+ }) => {
25
+ item.command(editorView.state, editorView.dispatch);
26
+ if (shouldCloseMenu) {
27
+ closeMenu();
28
+ }
29
+ }, [editorView.state, editorView.dispatch, closeMenu]);
30
+ return /*#__PURE__*/React.createElement(DropdownMenu, {
31
+ mountTo: popupsMountPoint,
32
+ onOpenChange: closeMenu,
33
+ boundariesElement: popupsBoundariesElement,
34
+ scrollableElement: popupsScrollableElement,
35
+ onItemActivated: onItemActivated,
36
+ isOpen: isMenuOpen,
37
+ items: group,
38
+ zIndex: akEditorMenuZIndex,
39
+ fitHeight: 188,
40
+ fitWidth: 136,
41
+ shouldUseDefaultRole: true,
42
+ shouldFocusFirstItem: () => {
43
+ if (isOpenedByKeyboard) {
44
+ setIsOpenedByKeyboard(false);
45
+ }
46
+ return isOpenedByKeyboard;
47
+ }
48
+ }, /*#__PURE__*/React.createElement(MoreButton, {
49
+ isSelected: isMenuOpen || hasFormattingActive,
50
+ label: moreButtonLabel,
51
+ isReducedSpacing: isReducedSpacing,
52
+ isDisabled: false,
53
+ onClick: () => {
54
+ toggleMenu();
55
+ setIsOpenedByKeyboard(false);
56
+ },
57
+ onKeyDown: event => {
58
+ if (event.key === 'Enter' || event.key === ' ') {
59
+ event.preventDefault();
60
+ toggleMenu();
61
+ setIsOpenedByKeyboard(true);
62
+ }
63
+ },
64
+ "aria-expanded": isMenuOpen
65
+ }));
66
+ });
@@ -0,0 +1,44 @@
1
+ /** @jsx jsx */
2
+ import { useCallback, useMemo } from 'react';
3
+ import { jsx } from '@emotion/react';
4
+ import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
5
+ import { clearFormatting as clearFormattingKeymap, tooltip } from '@atlaskit/editor-common/keymaps';
6
+ import { toolbarMessages } from '@atlaskit/editor-common/messages';
7
+ import { shortcutStyle } from '@atlaskit/editor-shared-styles/shortcut';
8
+ import { clearFormattingWithAnalytics } from '../../../commands/clear-formatting';
9
+ import { pluginKey as clearFormattingPluginKey } from '../../../pm-plugins/clear-formatting';
10
+ const useClearFormattingPluginState = editorState => {
11
+ return useMemo(() => clearFormattingPluginKey.getState(editorState), [editorState]);
12
+ };
13
+ export const useClearIcon = ({
14
+ intl,
15
+ editorState,
16
+ editorAnalyticsAPI
17
+ }) => {
18
+ const pluginState = useClearFormattingPluginState(editorState);
19
+ const isPluginAvailable = Boolean(pluginState);
20
+ const formattingIsPresent = Boolean(pluginState === null || pluginState === void 0 ? void 0 : pluginState.formattingIsPresent);
21
+ const clearFormattingLabel = intl.formatMessage(toolbarMessages.clearFormatting);
22
+ const clearFormattingToolbar = useCallback((state, dispatch) => clearFormattingWithAnalytics(INPUT_METHOD.TOOLBAR, editorAnalyticsAPI)(state, dispatch), [editorAnalyticsAPI]);
23
+ return useMemo(() => {
24
+ if (!isPluginAvailable) {
25
+ return null;
26
+ }
27
+ return {
28
+ key: 'clearFormatting',
29
+ command: clearFormattingToolbar,
30
+ content: clearFormattingLabel,
31
+ elemAfter:
32
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
33
+ jsx("div", {
34
+ css: shortcutStyle
35
+ }, tooltip(clearFormattingKeymap)),
36
+ value: {
37
+ name: 'clearFormatting'
38
+ },
39
+ isActive: false,
40
+ isDisabled: !formattingIsPresent,
41
+ 'aria-label': clearFormattingKeymap ? tooltip(clearFormattingKeymap, String(clearFormattingLabel)) : String(clearFormattingLabel)
42
+ };
43
+ }, [isPluginAvailable, clearFormattingToolbar, clearFormattingLabel, formattingIsPresent]);
44
+ };
@@ -0,0 +1,212 @@
1
+ /** @jsx jsx */
2
+ import React, { useMemo } from 'react';
3
+ import { jsx } from '@emotion/react';
4
+ import { INPUT_METHOD, TOOLBAR_ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
5
+ import { getAriaKeyshortcuts, toggleBold, toggleCode, toggleItalic, toggleStrikethrough, toggleSubscript, toggleSuperscript, toggleUnderline, tooltip, ToolTipContent } from '@atlaskit/editor-common/keymaps';
6
+ import { toolbarMessages } from '@atlaskit/editor-common/messages';
7
+ import { shortcutStyle } from '@atlaskit/editor-shared-styles/shortcut';
8
+ import BoldIcon from '@atlaskit/icon/glyph/editor/bold';
9
+ import ItalicIcon from '@atlaskit/icon/glyph/editor/italic';
10
+ import { toggleCodeWithAnalytics, toggleEmWithAnalytics, toggleStrikeWithAnalytics, toggleStrongWithAnalytics, toggleSubscriptWithAnalytics, toggleSuperscriptWithAnalytics, toggleUnderlineWithAnalytics } from '../../../actions';
11
+ import { pluginKey as textFormattingPluginKey } from '../../../pm-plugins/plugin-key';
12
+ import { IconTypes } from '../types';
13
+ const withToolbarInputMethod = func => func({
14
+ inputMethod: INPUT_METHOD.TOOLBAR
15
+ });
16
+ const IconButtons = editorAnalyticsAPI => ({
17
+ strong: {
18
+ buttonId: TOOLBAR_ACTION_SUBJECT_ID.TEXT_FORMATTING_STRONG,
19
+ command: withToolbarInputMethod(toggleStrongWithAnalytics(editorAnalyticsAPI)),
20
+ message: toolbarMessages.bold,
21
+ tooltipKeymap: toggleBold,
22
+ component: () => jsx(BoldIcon, {
23
+ label: ""
24
+ })
25
+ },
26
+ em: {
27
+ buttonId: TOOLBAR_ACTION_SUBJECT_ID.TEXT_FORMATTING_ITALIC,
28
+ command: withToolbarInputMethod(toggleEmWithAnalytics(editorAnalyticsAPI)),
29
+ message: toolbarMessages.italic,
30
+ tooltipKeymap: toggleItalic,
31
+ component: () => jsx(ItalicIcon, {
32
+ label: ""
33
+ })
34
+ },
35
+ underline: {
36
+ command: withToolbarInputMethod(toggleUnderlineWithAnalytics(editorAnalyticsAPI)),
37
+ message: toolbarMessages.underline,
38
+ tooltipKeymap: toggleUnderline
39
+ },
40
+ strike: {
41
+ command: withToolbarInputMethod(toggleStrikeWithAnalytics(editorAnalyticsAPI)),
42
+ message: toolbarMessages.strike,
43
+ tooltipKeymap: toggleStrikethrough
44
+ },
45
+ code: {
46
+ command: withToolbarInputMethod(toggleCodeWithAnalytics(editorAnalyticsAPI)),
47
+ message: toolbarMessages.code,
48
+ tooltipKeymap: toggleCode
49
+ },
50
+ subscript: {
51
+ command: withToolbarInputMethod(toggleSubscriptWithAnalytics(editorAnalyticsAPI)),
52
+ message: toolbarMessages.subscript,
53
+ tooltipKeymap: toggleSubscript
54
+ },
55
+ superscript: {
56
+ command: withToolbarInputMethod(toggleSuperscriptWithAnalytics(editorAnalyticsAPI)),
57
+ message: toolbarMessages.superscript,
58
+ tooltipKeymap: toggleSuperscript
59
+ }
60
+ });
61
+ const getIcon = ({
62
+ iconType,
63
+ isDisabled,
64
+ isActive,
65
+ intl,
66
+ editorAnalyticsAPI
67
+ }) => {
68
+ const icon = IconButtons(editorAnalyticsAPI)[iconType];
69
+ const content = intl.formatMessage(icon.message);
70
+ const {
71
+ tooltipKeymap
72
+ } = icon;
73
+ return {
74
+ content,
75
+ buttonId: icon.buttonId,
76
+ iconMark: iconType,
77
+ key: iconType,
78
+ command: icon.command,
79
+ iconElement: icon.component ? icon.component() : undefined,
80
+ tooltipElement: tooltipKeymap ? jsx(ToolTipContent, {
81
+ description: content,
82
+ keymap: tooltipKeymap
83
+ }) : undefined,
84
+ elemAfter: tooltipKeymap ?
85
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
86
+ jsx("div", {
87
+ css: shortcutStyle
88
+ }, tooltip(tooltipKeymap)) : undefined,
89
+ value: {
90
+ name: iconType
91
+ },
92
+ isActive,
93
+ isDisabled,
94
+ 'aria-label': tooltipKeymap ? tooltip(tooltipKeymap, String(content)) : String(content),
95
+ 'aria-keyshortcuts': getAriaKeyshortcuts(tooltipKeymap)
96
+ };
97
+ };
98
+ const IconsMarkSchema = {
99
+ [IconTypes.strong]: 'strong',
100
+ [IconTypes.em]: 'em',
101
+ [IconTypes.strike]: 'strike',
102
+ [IconTypes.code]: 'code',
103
+ [IconTypes.underline]: 'underline',
104
+ [IconTypes.superscript]: 'subsup',
105
+ [IconTypes.subscript]: 'subsup'
106
+ };
107
+ const buildMenuIconState = iconMark => ({
108
+ schema,
109
+ textFormattingPluginState
110
+ }) => {
111
+ const hasPluginState = Boolean(Object.keys(textFormattingPluginState || {}).length);
112
+ const markSchema = IconsMarkSchema[iconMark];
113
+ const hasSchemaMark = Boolean(schema.marks[markSchema]);
114
+ if (!hasPluginState) {
115
+ return {
116
+ isActive: false,
117
+ isDisabled: true,
118
+ isHidden: false,
119
+ hasSchemaMark
120
+ };
121
+ }
122
+ const isActive = textFormattingPluginState[`${iconMark}Active`];
123
+ const isDisabled = textFormattingPluginState[`${iconMark}Disabled`];
124
+ const isHidden = textFormattingPluginState[`${iconMark}Hidden`];
125
+ return {
126
+ isActive: Boolean(isActive),
127
+ isDisabled: Boolean(isDisabled),
128
+ isHidden: Boolean(isHidden),
129
+ hasSchemaMark
130
+ };
131
+ };
132
+ const buildIcon = (iconMark, editorAnalyticsAPI) => {
133
+ const getState = buildMenuIconState(iconMark);
134
+ return ({
135
+ schema,
136
+ textFormattingPluginState,
137
+ intl,
138
+ isToolbarDisabled
139
+ }) => {
140
+ const iconState = getState({
141
+ schema,
142
+ textFormattingPluginState
143
+ });
144
+ const {
145
+ isActive,
146
+ isDisabled,
147
+ isHidden,
148
+ hasSchemaMark
149
+ } = iconState;
150
+ const iconComponent = useMemo(() => getIcon({
151
+ iconType: IconTypes[iconMark],
152
+ isDisabled: isToolbarDisabled || isDisabled,
153
+ isActive,
154
+ intl,
155
+ editorAnalyticsAPI
156
+ }), [isToolbarDisabled, isDisabled, isActive, intl]);
157
+ const shouldRenderIcon = hasSchemaMark && !isHidden;
158
+ return useMemo(() => shouldRenderIcon ? iconComponent : null, [shouldRenderIcon, iconComponent]);
159
+ };
160
+ };
161
+ const useTextFormattingPluginState = editorState => useMemo(() => {
162
+ const pluginState = textFormattingPluginKey.getState(editorState);
163
+
164
+ // TODO: ED-13910 for reasons that goes beyond my knowledge. This is the only way to make the current unit tests happy. Even thought this was wrong before
165
+ return pluginState;
166
+ }, [editorState]);
167
+ export const useFormattingIcons = ({
168
+ isToolbarDisabled,
169
+ editorState,
170
+ intl,
171
+ editorAnalyticsAPI
172
+ }) => {
173
+ const textFormattingPluginState = useTextFormattingPluginState(editorState);
174
+ const {
175
+ schema
176
+ } = editorState;
177
+ const props = {
178
+ schema,
179
+ textFormattingPluginState,
180
+ intl,
181
+ isToolbarDisabled: Boolean(isToolbarDisabled)
182
+ };
183
+ const buildStrongIcon = buildIcon(IconTypes.strong, editorAnalyticsAPI);
184
+ const buildEmIcon = buildIcon(IconTypes.em, editorAnalyticsAPI);
185
+ const buildUnderlineIcon = buildIcon(IconTypes.underline, editorAnalyticsAPI);
186
+ const buildStrikeIcon = buildIcon(IconTypes.strike, editorAnalyticsAPI);
187
+ const buildCodeIcon = buildIcon(IconTypes.code, editorAnalyticsAPI);
188
+ const buildSubscriptIcon = buildIcon(IconTypes.subscript, editorAnalyticsAPI);
189
+ const buildSuperscriptIcon = buildIcon(IconTypes.superscript, editorAnalyticsAPI);
190
+ const strongIcon = buildStrongIcon(props);
191
+ const emIcon = buildEmIcon(props);
192
+ const underlineIcon = buildUnderlineIcon(props);
193
+ const strikeIcon = buildStrikeIcon(props);
194
+ const codeIcon = buildCodeIcon(props);
195
+ const subscriptIcon = buildSubscriptIcon(props);
196
+ const superscriptIcon = buildSuperscriptIcon(props);
197
+ const result = useMemo(() => [strongIcon, emIcon, underlineIcon, strikeIcon, codeIcon, subscriptIcon, superscriptIcon], [strongIcon, emIcon, underlineIcon, strikeIcon, codeIcon, subscriptIcon, superscriptIcon]);
198
+ return result;
199
+ };
200
+ export const useHasFormattingActived = ({
201
+ editorState,
202
+ iconTypeList
203
+ }) => {
204
+ const textFormattingPluginState = useTextFormattingPluginState(editorState);
205
+ const hasActiveFormatting = useMemo(() => {
206
+ if (!textFormattingPluginState) {
207
+ return false;
208
+ }
209
+ return iconTypeList.some(iconType => textFormattingPluginState[`${iconType}Active`]);
210
+ }, [textFormattingPluginState, iconTypeList]);
211
+ return hasActiveFormatting;
212
+ };
@@ -0,0 +1,11 @@
1
+ import { useCallback, useState } from 'react';
2
+ export const useMenuState = () => {
3
+ const [isMenuOpen, setIsMenuOpened] = useState(false);
4
+ const toggleMenu = useCallback(() => {
5
+ setIsMenuOpened(!isMenuOpen);
6
+ }, [isMenuOpen]);
7
+ const closeMenu = useCallback(() => {
8
+ setIsMenuOpened(false);
9
+ }, []);
10
+ return [isMenuOpen, toggleMenu, closeMenu];
11
+ };
@@ -0,0 +1,48 @@
1
+ import { useMemo } from 'react';
2
+ import { DefaultButtonsMenu, DefaultButtonsToolbar, ResponsiveCustomButtonToolbar, ResponsiveCustomMenu } from '../constants';
3
+ export const useResponsiveIconTypeButtons = ({
4
+ toolbarSize,
5
+ responsivenessEnabled
6
+ }) => {
7
+ const iconTypeList = useMemo(() => ResponsiveCustomButtonToolbar[toolbarSize], [toolbarSize]);
8
+ return responsivenessEnabled ? iconTypeList : DefaultButtonsToolbar;
9
+ };
10
+ export const useResponsiveIconTypeMenu = ({
11
+ toolbarSize,
12
+ responsivenessEnabled
13
+ }) => {
14
+ const iconTypeList = useMemo(() => ResponsiveCustomMenu[toolbarSize], [toolbarSize]);
15
+ return responsivenessEnabled ? iconTypeList : DefaultButtonsMenu;
16
+ };
17
+ export const useResponsiveToolbarButtons = ({
18
+ icons,
19
+ toolbarSize,
20
+ responsivenessEnabled
21
+ }) => {
22
+ const iconTypeList = useResponsiveIconTypeButtons({
23
+ toolbarSize,
24
+ responsivenessEnabled
25
+ });
26
+ const iconsPosition = useMemo(() => {
27
+ return icons.reduce((acc, icon) => {
28
+ if (!icon || !icon.iconMark) {
29
+ return acc;
30
+ }
31
+ const isIconSingleButton = iconTypeList.includes(icon.iconMark);
32
+ if (isIconSingleButton) {
33
+ return {
34
+ dropdownItems: acc.dropdownItems,
35
+ singleItems: [...acc.singleItems, icon]
36
+ };
37
+ }
38
+ return {
39
+ dropdownItems: [...acc.dropdownItems, icon],
40
+ singleItems: acc.singleItems
41
+ };
42
+ }, {
43
+ dropdownItems: [],
44
+ singleItems: []
45
+ });
46
+ }, [icons, iconTypeList]);
47
+ return iconsPosition;
48
+ };