@atlaskit/editor-plugin-paste-options-toolbar 0.2.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 (87) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/LICENSE.md +13 -0
  3. package/README.md +9 -0
  4. package/dist/cjs/actions.js +15 -0
  5. package/dist/cjs/commands.js +146 -0
  6. package/dist/cjs/index.js +12 -0
  7. package/dist/cjs/messages.js +29 -0
  8. package/dist/cjs/plugin.js +67 -0
  9. package/dist/cjs/pm-plugins/constants.js +13 -0
  10. package/dist/cjs/pm-plugins/main.js +66 -0
  11. package/dist/cjs/pm-plugins/plugin-factory.js +31 -0
  12. package/dist/cjs/reducer.js +50 -0
  13. package/dist/cjs/styles.js +12 -0
  14. package/dist/cjs/toolbar.js +95 -0
  15. package/dist/cjs/types.js +15 -0
  16. package/dist/cjs/ui/paste-icon.js +22 -0
  17. package/dist/cjs/ui/styles.js +12 -0
  18. package/dist/cjs/util/format-handlers.js +186 -0
  19. package/dist/cjs/util/index.js +45 -0
  20. package/dist/es2019/actions.js +9 -0
  21. package/dist/es2019/commands.js +134 -0
  22. package/dist/es2019/index.js +1 -0
  23. package/dist/es2019/messages.js +23 -0
  24. package/dist/es2019/plugin.js +64 -0
  25. package/dist/es2019/pm-plugins/constants.js +7 -0
  26. package/dist/es2019/pm-plugins/main.js +63 -0
  27. package/dist/es2019/pm-plugins/plugin-factory.js +23 -0
  28. package/dist/es2019/reducer.js +44 -0
  29. package/dist/es2019/styles.js +1 -0
  30. package/dist/es2019/toolbar.js +92 -0
  31. package/dist/es2019/types.js +9 -0
  32. package/dist/es2019/ui/paste-icon.js +14 -0
  33. package/dist/es2019/ui/styles.js +10 -0
  34. package/dist/es2019/util/format-handlers.js +196 -0
  35. package/dist/es2019/util/index.js +37 -0
  36. package/dist/esm/actions.js +9 -0
  37. package/dist/esm/commands.js +140 -0
  38. package/dist/esm/index.js +1 -0
  39. package/dist/esm/messages.js +23 -0
  40. package/dist/esm/plugin.js +61 -0
  41. package/dist/esm/pm-plugins/constants.js +7 -0
  42. package/dist/esm/pm-plugins/main.js +60 -0
  43. package/dist/esm/pm-plugins/plugin-factory.js +25 -0
  44. package/dist/esm/reducer.js +43 -0
  45. package/dist/esm/styles.js +1 -0
  46. package/dist/esm/toolbar.js +88 -0
  47. package/dist/esm/types.js +9 -0
  48. package/dist/esm/ui/paste-icon.js +16 -0
  49. package/dist/esm/ui/styles.js +5 -0
  50. package/dist/esm/util/format-handlers.js +178 -0
  51. package/dist/esm/util/index.js +37 -0
  52. package/dist/types/actions.d.ts +34 -0
  53. package/dist/types/commands.d.ts +16 -0
  54. package/dist/types/index.d.ts +1 -0
  55. package/dist/types/messages.d.ts +22 -0
  56. package/dist/types/plugin.d.ts +6 -0
  57. package/dist/types/pm-plugins/constants.d.ts +7 -0
  58. package/dist/types/pm-plugins/main.d.ts +3 -0
  59. package/dist/types/pm-plugins/plugin-factory.d.ts +2 -0
  60. package/dist/types/reducer.d.ts +3 -0
  61. package/dist/types/styles.d.ts +1 -0
  62. package/dist/types/toolbar.d.ts +9 -0
  63. package/dist/types/types.d.ts +25 -0
  64. package/dist/types/ui/paste-icon.d.ts +5 -0
  65. package/dist/types/ui/styles.d.ts +1 -0
  66. package/dist/types/util/format-handlers.d.ts +9 -0
  67. package/dist/types/util/index.d.ts +7 -0
  68. package/dist/types-ts4.5/actions.d.ts +34 -0
  69. package/dist/types-ts4.5/commands.d.ts +16 -0
  70. package/dist/types-ts4.5/index.d.ts +1 -0
  71. package/dist/types-ts4.5/messages.d.ts +22 -0
  72. package/dist/types-ts4.5/plugin.d.ts +9 -0
  73. package/dist/types-ts4.5/pm-plugins/constants.d.ts +7 -0
  74. package/dist/types-ts4.5/pm-plugins/main.d.ts +3 -0
  75. package/dist/types-ts4.5/pm-plugins/plugin-factory.d.ts +2 -0
  76. package/dist/types-ts4.5/reducer.d.ts +3 -0
  77. package/dist/types-ts4.5/styles.d.ts +1 -0
  78. package/dist/types-ts4.5/toolbar.d.ts +9 -0
  79. package/dist/types-ts4.5/types.d.ts +25 -0
  80. package/dist/types-ts4.5/ui/paste-icon.d.ts +5 -0
  81. package/dist/types-ts4.5/ui/styles.d.ts +1 -0
  82. package/dist/types-ts4.5/util/format-handlers.d.ts +9 -0
  83. package/dist/types-ts4.5/util/index.d.ts +7 -0
  84. package/package.json +114 -0
  85. package/report.api.md +46 -0
  86. package/styles/package.json +15 -0
  87. package/tmp/api-report-tmp.d.ts +19 -0
@@ -0,0 +1,92 @@
1
+ import { akEditorFloatingPanelZIndex } from '@atlaskit/editor-shared-styles';
2
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
3
+ import { changeToMarkdownWithAnalytics, changeToPlainTextWithAnalytics, changeToRichTextWithAnalytics, dropdownClickHandler } from './commands';
4
+ import { messages } from './messages';
5
+ import { PASTE_OPTIONS_TEST_ID, PASTE_TOOLBAR_CLASS, PASTE_TOOLBAR_ITEM_CLASS } from './pm-plugins/constants';
6
+ import { pasteOptionsPluginKey, ToolbarDropdownOption } from './types';
7
+ import EditorPasteIcon from './ui/paste-icon';
8
+ import { hasLinkMark, hasMediaNode, hasRuleNode, isPastedFromFabricEditor } from './util';
9
+ export const isToolbarVisible = (state, lastContentPasted) => {
10
+ var _$from$node;
11
+ /**
12
+ * Conditions for not showing the toolbar:
13
+ * 1. Feature flag is disabled
14
+ * 2. Content is pasted at gap cursor
15
+ * 3. Pasting horizontal rule
16
+ * 4. Pasting link, media or text containing media(note: markdown link and images are allowed)
17
+ * 5. Content is pasted in a nested node(i.e. inside a table, panel etc.).
18
+ * (grandParent node should be root doc for showing up the toolbar)
19
+ */
20
+ if (!getBooleanFF('platform.editor.paste-options-toolbar')) {
21
+ return false;
22
+ }
23
+ const $from = state.selection.$from;
24
+ if (hasRuleNode(lastContentPasted.pastedSlice, state.schema)) {
25
+ return false;
26
+ }
27
+ const grandParentNodeType = (_$from$node = $from.node($from.depth - 1)) === null || _$from$node === void 0 ? void 0 : _$from$node.type;
28
+ if (grandParentNodeType && grandParentNodeType.name === state.schema.nodes.doc.name && !isPastedFromFabricEditor(lastContentPasted.pasteSource) && !hasLinkMark(state, lastContentPasted.pasteStartPos, lastContentPasted.pasteEndPos) && !hasMediaNode(lastContentPasted.pastedSlice)) {
29
+ return true;
30
+ }
31
+ return false;
32
+ };
33
+ export const getToolbarMenuConfig = (pluginState, pasteStartPos, plaintext, intl, editorAnalyticsAPI) => {
34
+ const options = [{
35
+ id: 'editor.paste.richText',
36
+ title: intl.formatMessage(messages.richText),
37
+ selected: pluginState.selectedOption === ToolbarDropdownOption.RichText,
38
+ hidden: pluginState.isPlainText,
39
+ onClick: changeToRichTextWithAnalytics(editorAnalyticsAPI)(pasteStartPos)
40
+ }, {
41
+ id: 'editor.paste.markdown',
42
+ title: intl.formatMessage(messages.markdown),
43
+ selected: pluginState.selectedOption === ToolbarDropdownOption.Markdown,
44
+ onClick: changeToMarkdownWithAnalytics(editorAnalyticsAPI, plaintext.length)(pasteStartPos, plaintext)
45
+ }, {
46
+ id: 'editor.paste.plainText',
47
+ title: intl.formatMessage(messages.plainText),
48
+ selected: pluginState.selectedOption === ToolbarDropdownOption.PlainText,
49
+ onClick: changeToPlainTextWithAnalytics(editorAnalyticsAPI, plaintext.length)(pasteStartPos, plaintext)
50
+ }];
51
+ return {
52
+ id: PASTE_TOOLBAR_ITEM_CLASS,
53
+ icon: EditorPasteIcon,
54
+ type: 'dropdown',
55
+ testId: PASTE_OPTIONS_TEST_ID,
56
+ title: intl.formatMessage(messages.pasteOptions),
57
+ options,
58
+ onToggle: onToggleHandler
59
+ };
60
+ };
61
+ const onToggleHandler = (state, dispatch) => {
62
+ return dropdownClickHandler()(state, dispatch);
63
+ };
64
+ export const buildToolbar = (state, pasteStartPos, plaintext, intl, editorAnalyticsAPI) => {
65
+ const {
66
+ schema
67
+ } = state;
68
+ const validNodes = Object.values(schema.nodes);
69
+ const pluginState = pasteOptionsPluginKey.getState(state);
70
+ const menu = getToolbarMenuConfig(pluginState, pasteStartPos, plaintext, intl, editorAnalyticsAPI);
71
+ return {
72
+ title: intl.formatMessage(messages.pasteOptions),
73
+ nodeType: validNodes,
74
+ zIndex: akEditorFloatingPanelZIndex,
75
+ className: PASTE_TOOLBAR_CLASS,
76
+ items: [menu],
77
+ align: 'right',
78
+ onPositionCalculated
79
+ };
80
+ };
81
+ const onPositionCalculated = (editorView, nextPos) => {
82
+ const toolbar = document.querySelector(`div[aria-label="${messages.pasteOptions.defaultMessage}"]`);
83
+ const cursorHeight = parseFloat(window.getComputedStyle(toolbar, undefined).lineHeight || '');
84
+ const {
85
+ from
86
+ } = editorView.state.tr.selection;
87
+ const fromCoords = editorView.coordsAtPos(from);
88
+ return {
89
+ top: fromCoords.top + cursorHeight,
90
+ left: fromCoords.left
91
+ };
92
+ };
@@ -0,0 +1,9 @@
1
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
2
+ export const pasteOptionsPluginKey = new PluginKey('paste-options');
3
+ export let ToolbarDropdownOption = /*#__PURE__*/function (ToolbarDropdownOption) {
4
+ ToolbarDropdownOption[ToolbarDropdownOption["Markdown"] = 0] = "Markdown";
5
+ ToolbarDropdownOption[ToolbarDropdownOption["RichText"] = 1] = "RichText";
6
+ ToolbarDropdownOption[ToolbarDropdownOption["PlainText"] = 2] = "PlainText";
7
+ ToolbarDropdownOption[ToolbarDropdownOption["None"] = 3] = "None";
8
+ return ToolbarDropdownOption;
9
+ }({});
@@ -0,0 +1,14 @@
1
+ //Using a custom icon for now since Design System Team is in the process of updating the icon set for project griffin.
2
+
3
+ const _react = _interopRequireDefault(require('react'));
4
+ const _base = require('@atlaskit/icon/base');
5
+ function _interopRequireDefault(obj) {
6
+ return obj && obj.__esModule ? obj : {
7
+ default: obj
8
+ };
9
+ }
10
+ const EditorPasteIcon = props => /*#__PURE__*/_react.default.createElement(_base.Icon, Object.assign({
11
+ dangerouslySetGlyph: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M14.8293 4C14.4175 2.83481 13.3062 2 12 2C10.6938 2 9.58254 2.83481 9.17071 4H9H6C4.89543 4 4 4.89543 4 6V18C4 19.1046 4.89543 20 6 20H18C19.1046 20 20 19.1046 20 18V6C20 4.89543 19.1046 4 18 4H15H14.8293ZM6 6H8V7C8 7.55228 8.44772 8 9 8H15C15.5523 8 16 7.55228 16 7V6H18V18H6V6ZM12 6C12.5523 6 13 5.55228 13 5C13 4.44772 12.5523 4 12 4C11.4477 4 11 4.44772 11 5C11 5.55228 11.4477 6 12 6Z" fill="currentColor"/><rect x="8" y="11" width="8" height="2" rx="1" fill="currentColor"/><rect x="8" y="14" width="5" height="2" rx="1" fill="currentColor"/></svg>`
12
+ }, props));
13
+ EditorPasteIcon.displayName = 'EditorPasteIcon';
14
+ export default EditorPasteIcon;
@@ -0,0 +1,10 @@
1
+ import { css } from '@emotion/react';
2
+ import { TEXT_HIGHLIGHT_CLASS } from '../pm-plugins/constants';
3
+ export const textHighlightStyle = css`
4
+ .${TEXT_HIGHLIGHT_CLASS} {
5
+ background-color: ${"var(--ds-background-accent-blue-subtlest, #E9F2FF)"};
6
+
7
+ border-bottom: 2px solid
8
+ ${"var(--ds-background-accent-blue-subtler, #cce0ff)"};
9
+ }
10
+ `;
@@ -0,0 +1,196 @@
1
+ import { logException } from '@atlaskit/editor-common/monitoring';
2
+ import { md } from '@atlaskit/editor-common/paste';
3
+ import { MarkdownTransformer } from '@atlaskit/editor-markdown-transformer';
4
+ import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
5
+ import { Selection } from '@atlaskit/editor-prosemirror/state';
6
+ import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
7
+ import { escapeLinks } from './index';
8
+ export const formatMarkdown = (state, pasteStartPos, plaintext) => {
9
+ const schema = state.schema;
10
+ let {
11
+ tr
12
+ } = state;
13
+ const {
14
+ selection
15
+ } = tr;
16
+ if (pasteStartPos < 0) {
17
+ return tr;
18
+ }
19
+ const resolvedPasteStartPos = tr.doc.resolve(pasteStartPos);
20
+ const parentOffset = resolvedPasteStartPos.parentOffset;
21
+ if (parentOffset === 0 && resolvedPasteStartPos.depth > 0) {
22
+ pasteStartPos = resolvedPasteStartPos.before();
23
+ }
24
+ const markdownSlice = getMarkdownSlice(plaintext, schema, selection);
25
+ if (!markdownSlice) {
26
+ return tr;
27
+ }
28
+ const pasteEndPos = selection.$to.pos;
29
+ pasteSliceIntoTransactionWithSelectionAdjust({
30
+ tr,
31
+ pasteStartPos,
32
+ pasteEndPos,
33
+ slice: markdownSlice
34
+ });
35
+ return tr;
36
+ };
37
+ export const getRichTextSlice = (state, pasteStartPos) => {
38
+ const {
39
+ tr
40
+ } = state;
41
+ const {
42
+ selection
43
+ } = tr;
44
+ let pasteEndPos = selection.$to.pos;
45
+ return state.doc.slice(pasteStartPos, pasteEndPos);
46
+ };
47
+ export const formatRichText = (state, pasteStartPos, richTextSlice) => {
48
+ let {
49
+ tr
50
+ } = state;
51
+ const {
52
+ selection
53
+ } = tr;
54
+ if (pasteStartPos < 0) {
55
+ return tr;
56
+ }
57
+ if (richTextSlice === Slice.empty) {
58
+ return tr;
59
+ }
60
+ let pasteEndPos = selection.$to.pos;
61
+ const resolvedPasteStartPos = tr.doc.resolve(pasteStartPos);
62
+ const parentOffset = resolvedPasteStartPos.parentOffset;
63
+ if (parentOffset === 0 && resolvedPasteStartPos.depth > 0) {
64
+ pasteStartPos = resolvedPasteStartPos.before();
65
+ }
66
+ richTextSliceTransactionWithSelectionAdjust({
67
+ tr,
68
+ pasteStartPos,
69
+ pasteEndPos,
70
+ slice: richTextSlice
71
+ });
72
+ return tr;
73
+ };
74
+ export const formatPlainText = (state, pasteStartPos, plaintext) => {
75
+ const {
76
+ tr,
77
+ schema
78
+ } = state;
79
+
80
+ //not possible to create plain text slice with empty string
81
+ if (pasteStartPos < 0 || plaintext === '') {
82
+ return tr;
83
+ }
84
+ const {
85
+ selection
86
+ } = tr;
87
+ const pasteEndPos = selection.$to.pos;
88
+ const resolvedPasteStartPos = tr.doc.resolve(pasteStartPos);
89
+ const parentOffset = resolvedPasteStartPos.parentOffset;
90
+ if (parentOffset === 0 && resolvedPasteStartPos.depth > 0) {
91
+ pasteStartPos = resolvedPasteStartPos.before();
92
+ }
93
+ const plainTextNode = schema.text(plaintext);
94
+ const plainTextFragment = Fragment.from(schema.nodes.paragraph.createAndFill(null, plainTextNode));
95
+ const plainTextSlice = new Slice(plainTextFragment, resolvedPasteStartPos.depth, resolvedPasteStartPos.depth);
96
+ pasteSliceIntoTransactionWithSelectionAdjust({
97
+ tr,
98
+ pasteStartPos,
99
+ pasteEndPos,
100
+ slice: plainTextSlice
101
+ });
102
+ return tr;
103
+ };
104
+ function pasteSliceIntoTransactionWithSelectionAdjust({
105
+ tr,
106
+ pasteStartPos,
107
+ pasteEndPos,
108
+ slice
109
+ }) {
110
+ tr.replaceRange(pasteStartPos, pasteEndPos, slice);
111
+
112
+ // ProseMirror doesn't give a proper way to tell us where something was inserted.
113
+ // However, we can know "how" it inserted something.
114
+ //
115
+ // So, instead of weird depth calculations, we can use the step produced by the transform.
116
+ // For instance:
117
+ // The `replaceStep.to and replaceStep.from`, tell us the real position
118
+ // where the content will be insert.
119
+ // Then, we can use the `tr.mapping.map` to the updated position after the replace operation
120
+ const replaceStep = tr.steps[0];
121
+ if (!(replaceStep instanceof ReplaceStep)) {
122
+ return tr;
123
+ }
124
+ const lastInsertNode = replaceStep.slice.content.lastChild;
125
+ const emptyNodeReference = lastInsertNode === null || lastInsertNode === void 0 ? void 0 : lastInsertNode.type.createAndFill();
126
+ const isLastNodeEmpty = (emptyNodeReference === null || emptyNodeReference === void 0 ? void 0 : emptyNodeReference.nodeSize) === (lastInsertNode === null || lastInsertNode === void 0 ? void 0 : lastInsertNode.nodeSize);
127
+ const isStepSplitingTarget = !(lastInsertNode !== null && lastInsertNode !== void 0 && lastInsertNode.isLeaf) && isLastNodeEmpty;
128
+ const $nextHead = tr.doc.resolve(tr.mapping.map(replaceStep.to));
129
+ const $nextPosition = isStepSplitingTarget && $nextHead.depth > 0 ? tr.doc.resolve($nextHead.before()) : $nextHead;
130
+
131
+ // The findFrom will make search for both: TextSelection and NodeSelections.
132
+ const nextSelection = Selection.findFrom($nextPosition, -1);
133
+ if (nextSelection) {
134
+ tr.setSelection(nextSelection);
135
+ }
136
+ }
137
+ function richTextSliceTransactionWithSelectionAdjust({
138
+ tr,
139
+ pasteStartPos,
140
+ pasteEndPos,
141
+ slice
142
+ }) {
143
+ tr.replaceRange(pasteStartPos, pasteEndPos, slice);
144
+
145
+ // ProseMirror doesn't give a proper way to tell us where something was inserted.
146
+ // However, we can know "how" it inserted something.
147
+ //
148
+ // So, instead of weird depth calculations, we can use the step produced by the transform.
149
+ // For instance:
150
+ // The `replaceStep.to and replaceStep.from`, tell us the real position
151
+ // where the content will be insert.
152
+ // Then, we can use the `tr.mapping.map` to the updated position after the replace operation
153
+ const replaceStep = tr.steps[0];
154
+ if (!(replaceStep instanceof ReplaceStep)) {
155
+ return tr;
156
+ }
157
+ const nextPosition = tr.mapping.map(replaceStep.to);
158
+
159
+ // The findFrom will make search for both: TextSelection and NodeSelections.
160
+ const nextSelection = Selection.findFrom(tr.doc.resolve(Math.min(nextPosition, tr.doc.content.size)), -1);
161
+ if (nextSelection) {
162
+ tr.setSelection(nextSelection);
163
+ }
164
+ }
165
+ export function getMarkdownSlice(text, schema, selection) {
166
+ const targetOpenStartNode = selection.$from.parent;
167
+ const targetOpenEndNode = selection.$to.parent;
168
+ try {
169
+ var _doc$content$firstChi, _doc$content$lastChil;
170
+ let textInput = text;
171
+ const textSplitByCodeBlock = textInput.split(/```/);
172
+ for (let i = 0; i < textSplitByCodeBlock.length; i++) {
173
+ if (i % 2 === 0) {
174
+ textSplitByCodeBlock[i] = textSplitByCodeBlock[i].replace(/\\/g, '\\\\');
175
+ }
176
+ }
177
+ textInput = textSplitByCodeBlock.join('```');
178
+ const atlassianMarkDownParser = new MarkdownTransformer(schema, md);
179
+ const doc = atlassianMarkDownParser.parse(escapeLinks(textInput));
180
+ if (!doc || !doc.content) {
181
+ return;
182
+ }
183
+ const canMergeOpenStart = targetOpenStartNode.type === ((_doc$content$firstChi = doc.content.firstChild) === null || _doc$content$firstChi === void 0 ? void 0 : _doc$content$firstChi.type);
184
+ const canMergeOpenEnd = targetOpenEndNode.type === ((_doc$content$lastChil = doc.content.lastChild) === null || _doc$content$lastChil === void 0 ? void 0 : _doc$content$lastChil.type);
185
+ const $start = Selection.atStart(doc).$from;
186
+ const $end = Selection.atEnd(doc).$from;
187
+ const openStart = canMergeOpenStart ? $start.depth : 0;
188
+ const openEnd = canMergeOpenEnd ? $end.depth : 0;
189
+ return new Slice(doc.content, openStart, openEnd);
190
+ } catch (error) {
191
+ logException(error, {
192
+ location: 'editor-plugin-paste-options-toolbar/util'
193
+ });
194
+ return;
195
+ }
196
+ }
@@ -0,0 +1,37 @@
1
+ export function isPastedFromFabricEditor(pastedFrom) {
2
+ return pastedFrom === 'fabric-editor';
3
+ }
4
+
5
+ // @see https://product-fabric.atlassian.net/browse/ED-3159
6
+ // @see https://github.com/markdown-it/markdown-it/issues/38
7
+ export function escapeLinks(text) {
8
+ return text.replace(/(\[([^\]]+)\]\()?((https?|ftp|jamfselfservice):\/\/[^\s"'>]+)/g, str => {
9
+ return str.match(/^(https?|ftp|jamfselfservice):\/\/[^\s"'>]+$/) ? `<${str}>` : str;
10
+ });
11
+ }
12
+ export const hasMediaNode = slice => {
13
+ if (!slice) {
14
+ return false;
15
+ }
16
+ let hasMedia = false;
17
+ slice.content.descendants(node => {
18
+ if (['media', 'mediaInline', 'mediaGroup', 'mediaSingle'].includes(node.type.name)) {
19
+ hasMedia = true;
20
+ return false;
21
+ }
22
+ return true;
23
+ });
24
+ return hasMedia;
25
+ };
26
+ export const hasRuleNode = (slice, schema) => {
27
+ let hasRuleNode = false;
28
+ slice.content.nodesBetween(0, slice.content.size, (node, start) => {
29
+ if (node.type === schema.nodes.rule) {
30
+ hasRuleNode = true;
31
+ }
32
+ });
33
+ return hasRuleNode;
34
+ };
35
+ export const hasLinkMark = (state, pasteStartPos, pasteEndPos) => {
36
+ return state.doc.rangeHasMark(pasteStartPos, pasteEndPos, state.schema.marks.link);
37
+ };
@@ -0,0 +1,9 @@
1
+ export var PastePluginActionTypes = /*#__PURE__*/function (PastePluginActionTypes) {
2
+ PastePluginActionTypes["START_TRACKING_PASTED_MACRO_POSITIONS"] = "START_TRACKING_PASTED_MACRO_POSITIONS";
3
+ PastePluginActionTypes["STOP_TRACKING_PASTED_MACRO_POSITIONS"] = "STOP_TRACKING_PASTED_MACRO_POSITIONS";
4
+ PastePluginActionTypes["SHOW_PASTE_OPTIONS"] = "SHOW_PASTE_OPTIONS";
5
+ PastePluginActionTypes["HIDE_PASTE_OPTIONS"] = "HIDE_PASTE_OPTIONS";
6
+ PastePluginActionTypes["HIGHLIGHT_CONTENT"] = "HIGHLIGHT_CONTENT";
7
+ PastePluginActionTypes["CHANGE_FORMAT"] = "CHANGE_FORMAT";
8
+ return PastePluginActionTypes;
9
+ }({});
@@ -0,0 +1,140 @@
1
+ import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD, PasteContents, PasteTypes } from '@atlaskit/editor-common/analytics';
2
+ import { withAnalytics } from '@atlaskit/editor-common/editor-analytics';
3
+ import { PastePluginActionTypes as ActionTypes } from './actions';
4
+ import { createCommand } from './pm-plugins/plugin-factory';
5
+ import { pasteOptionsPluginKey, ToolbarDropdownOption } from './types';
6
+ import { formatMarkdown, formatPlainText, formatRichText } from './util/format-handlers';
7
+ export var showToolbar = function showToolbar(lastContentPasted, selectedOption) {
8
+ var commandAction = function commandAction(editorState) {
9
+ return {
10
+ type: ActionTypes.SHOW_PASTE_OPTIONS,
11
+ data: {
12
+ selectedOption: selectedOption,
13
+ plaintext: lastContentPasted.text,
14
+ isPlainText: lastContentPasted.isPlainText,
15
+ richTextSlice: lastContentPasted.pastedSlice,
16
+ pasteStartPos: lastContentPasted.pasteStartPos,
17
+ pasteEndPos: lastContentPasted.pasteEndPos
18
+ }
19
+ };
20
+ };
21
+ return createCommand(commandAction);
22
+ };
23
+ export var changeToPlainText = function changeToPlainText(pasteStartPos, plaintext) {
24
+ var plaintextTransformer = function plaintextTransformer(tr, state) {
25
+ return formatPlainText(state, pasteStartPos, plaintext);
26
+ };
27
+ var commandAction = function commandAction(editorState) {
28
+ return {
29
+ type: ActionTypes.CHANGE_FORMAT,
30
+ data: {
31
+ selectedOption: ToolbarDropdownOption.PlainText
32
+ }
33
+ };
34
+ };
35
+ return createCommand(commandAction, plaintextTransformer);
36
+ };
37
+ export var changeToPlainTextWithAnalytics = function changeToPlainTextWithAnalytics(editorAnalyticsAPI, sliceSize) {
38
+ return function (pasteStartPos, plaintext) {
39
+ return withAnalytics(editorAnalyticsAPI, {
40
+ action: ACTION.PASTED,
41
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
42
+ eventType: EVENT_TYPE.TRACK,
43
+ attributes: {
44
+ inputMethod: INPUT_METHOD.TOOLBAR,
45
+ type: PasteTypes.plain,
46
+ content: PasteContents.text,
47
+ pasteSize: sliceSize
48
+ }
49
+ })(changeToPlainText(pasteStartPos, plaintext));
50
+ };
51
+ };
52
+ export var dropdownClickHandler = function dropdownClickHandler() {
53
+ return highlightContent();
54
+ };
55
+ export var changeToRichText = function changeToRichText(pasteStartPos) {
56
+ var transformer = function transformer(tr, state) {
57
+ var pastePluginState = pasteOptionsPluginKey.getState(state);
58
+ return formatRichText(state, pasteStartPos, pastePluginState.richTextSlice);
59
+ };
60
+ var commandAction = function commandAction(editorState) {
61
+ return {
62
+ type: ActionTypes.CHANGE_FORMAT,
63
+ data: {
64
+ selectedOption: ToolbarDropdownOption.RichText
65
+ }
66
+ };
67
+ };
68
+ return createCommand(commandAction, transformer);
69
+ };
70
+ export var changeToRichTextWithAnalytics = function changeToRichTextWithAnalytics(editorAnalyticsAPI) {
71
+ return function (pasteStartPos) {
72
+ var payloadCallback = function payloadCallback(state) {
73
+ var _pastePluginState$ric;
74
+ var pastePluginState = pasteOptionsPluginKey.getState(state);
75
+ return {
76
+ action: ACTION.PASTED,
77
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
78
+ eventType: EVENT_TYPE.TRACK,
79
+ attributes: {
80
+ inputMethod: INPUT_METHOD.TOOLBAR,
81
+ type: PasteTypes.richText,
82
+ content: PasteContents.text,
83
+ pasteSize: ((_pastePluginState$ric = pastePluginState.richTextSlice) === null || _pastePluginState$ric === void 0 ? void 0 : _pastePluginState$ric.size) || 0
84
+ }
85
+ };
86
+ };
87
+ return withAnalytics(editorAnalyticsAPI, payloadCallback)(changeToRichText(pasteStartPos));
88
+ };
89
+ };
90
+ export var changeToMarkDown = function changeToMarkDown(pasteStartPos, plaintext) {
91
+ var markdownTransformer = function markdownTransformer(tr, state) {
92
+ return formatMarkdown(state, pasteStartPos, plaintext);
93
+ };
94
+ var commandAction = function commandAction(editorState) {
95
+ return {
96
+ type: ActionTypes.CHANGE_FORMAT,
97
+ data: {
98
+ selectedOption: ToolbarDropdownOption.Markdown
99
+ }
100
+ };
101
+ };
102
+ return createCommand(commandAction, markdownTransformer);
103
+ };
104
+ export var changeToMarkdownWithAnalytics = function changeToMarkdownWithAnalytics(editorAnalyticsAPI, sliceSize) {
105
+ return function (pasteStartPos, plaintext) {
106
+ return withAnalytics(editorAnalyticsAPI, {
107
+ action: ACTION.PASTED,
108
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
109
+ eventType: EVENT_TYPE.TRACK,
110
+ attributes: {
111
+ inputMethod: INPUT_METHOD.TOOLBAR,
112
+ type: PasteTypes.markdown,
113
+ content: PasteContents.text,
114
+ pasteSize: sliceSize
115
+ }
116
+ })(changeToMarkDown(pasteStartPos, plaintext));
117
+ };
118
+ };
119
+ export var highlightContent = function highlightContent() {
120
+ var commandAction = function commandAction(editorState) {
121
+ return {
122
+ type: ActionTypes.HIGHLIGHT_CONTENT
123
+ };
124
+ };
125
+ return createCommand(commandAction);
126
+ };
127
+ export var hideToolbar = function hideToolbar() {
128
+ var commandAction = function commandAction(editorState) {
129
+ return {
130
+ type: ActionTypes.HIDE_PASTE_OPTIONS
131
+ };
132
+ };
133
+ return createCommand(commandAction);
134
+ };
135
+ export var checkAndHideToolbar = function checkAndHideToolbar(view) {
136
+ var pluginState = pasteOptionsPluginKey.getState(view.state);
137
+ if (pluginState.showToolbar) {
138
+ hideToolbar()(view.state, view.dispatch);
139
+ }
140
+ };
@@ -0,0 +1 @@
1
+ export { pasteOptionsToolbarPlugin } from './plugin';
@@ -0,0 +1,23 @@
1
+ import { defineMessages } from 'react-intl-next';
2
+ export var messages = defineMessages({
3
+ pasteOptions: {
4
+ id: 'fabric.editor.pasteOptions',
5
+ defaultMessage: 'Paste options floating controls',
6
+ description: 'Opens a menu with additional paste options'
7
+ },
8
+ plainText: {
9
+ id: 'fabric.editor.plainText',
10
+ defaultMessage: 'Use plain text',
11
+ description: 'Converts pasted text into plain text'
12
+ },
13
+ markdown: {
14
+ id: 'fabric.editor.useMarkdown',
15
+ defaultMessage: 'Use Markdown',
16
+ description: 'Converts pasted text into Markdown'
17
+ },
18
+ richText: {
19
+ id: 'fabric.editor.richText',
20
+ defaultMessage: 'Use rich text',
21
+ description: 'Converts pasted text into Rich text'
22
+ }
23
+ });
@@ -0,0 +1,61 @@
1
+ import { useEffect } from 'react';
2
+ import { useSharedPluginState } from '@atlaskit/editor-common/hooks';
3
+ import { hideToolbar, showToolbar } from './commands';
4
+ import { createPlugin } from './pm-plugins/main';
5
+ import { buildToolbar, isToolbarVisible } from './toolbar';
6
+ import { pasteOptionsPluginKey, ToolbarDropdownOption } from './types';
7
+ export var pasteOptionsToolbarPlugin = function pasteOptionsToolbarPlugin(_ref) {
8
+ var _api$analytics;
9
+ var config = _ref.config,
10
+ api = _ref.api;
11
+ var editorAnalyticsAPI = api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
12
+ return {
13
+ name: 'pasteOptionsToolbarPlugin',
14
+ pmPlugins: function pmPlugins() {
15
+ return [{
16
+ name: 'pasteOptionsToolbarPlugin',
17
+ plugin: function plugin(_ref2) {
18
+ var dispatch = _ref2.dispatch;
19
+ return createPlugin(dispatch);
20
+ }
21
+ }];
22
+ },
23
+ pluginsOptions: {
24
+ floatingToolbar: function floatingToolbar(state, intl) {
25
+ var pastePluginState = pasteOptionsPluginKey.getState(state);
26
+ var _ref3 = pastePluginState || {},
27
+ showToolbar = _ref3.showToolbar,
28
+ pasteStartPos = _ref3.pasteStartPos,
29
+ plaintext = _ref3.plaintext;
30
+ if (showToolbar) {
31
+ return buildToolbar(state, pasteStartPos, plaintext, intl, editorAnalyticsAPI);
32
+ }
33
+ return;
34
+ }
35
+ },
36
+ usePluginHook: function usePluginHook(_ref4) {
37
+ var editorView = _ref4.editorView;
38
+ var _useSharedPluginState = useSharedPluginState(api, ['paste']),
39
+ pasteState = _useSharedPluginState.pasteState;
40
+ var lastContentPasted = pasteState === null || pasteState === void 0 ? void 0 : pasteState.lastContentPasted;
41
+ useEffect(function () {
42
+ if (!lastContentPasted) {
43
+ hideToolbar()(editorView.state, editorView.dispatch);
44
+ return;
45
+ }
46
+ var selectedOption = ToolbarDropdownOption.None;
47
+ if (!lastContentPasted.isPlainText) {
48
+ selectedOption = ToolbarDropdownOption.RichText;
49
+ } else if (lastContentPasted.isShiftPressed) {
50
+ selectedOption = ToolbarDropdownOption.PlainText;
51
+ } else {
52
+ selectedOption = ToolbarDropdownOption.Markdown;
53
+ }
54
+ if (!isToolbarVisible(editorView.state, lastContentPasted)) {
55
+ return;
56
+ }
57
+ showToolbar(lastContentPasted, selectedOption)(editorView.state, editorView.dispatch);
58
+ }, [lastContentPasted, editorView]);
59
+ }
60
+ };
61
+ };
@@ -0,0 +1,7 @@
1
+ export var PASTE_TOOLBAR_CLASS = 'ak-editor-paste-toolbar';
2
+ export var PASTE_TOOLBAR_MENU_ID = 'ak-editor-paste-toolbar-item-dropdownList';
3
+ export var TEXT_HIGHLIGHT_CLASS = 'text-highlight';
4
+ export var PASTE_HIGHLIGHT_DECORATION_KEY = 'paste-highlight-decoration-key';
5
+ export var PASTE_TOOLBAR_ITEM_CLASS = 'ak-editor-paste-toolbar-item';
6
+ export var EDITOR_WRAPPER_CLASS = 'akEditor';
7
+ export var PASTE_OPTIONS_TEST_ID = 'paste-options-testid';
@@ -0,0 +1,60 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { Slice } from '@atlaskit/editor-prosemirror/model';
3
+ import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
4
+ import { checkAndHideToolbar } from '../commands';
5
+ import { pasteOptionsPluginKey, ToolbarDropdownOption } from '../types';
6
+ import { PASTE_HIGHLIGHT_DECORATION_KEY, TEXT_HIGHLIGHT_CLASS } from './constants';
7
+ import { createPluginState } from './plugin-factory';
8
+ export function createPlugin(dispatch) {
9
+ return new SafePlugin({
10
+ key: pasteOptionsPluginKey,
11
+ state: createPluginState(dispatch, {
12
+ showToolbar: false,
13
+ pasteStartPos: 0,
14
+ pasteEndPos: 0,
15
+ plaintext: '',
16
+ isPlainText: false,
17
+ highlightContent: false,
18
+ highlightDecorationSet: DecorationSet.empty,
19
+ richTextSlice: Slice.empty,
20
+ selectedOption: ToolbarDropdownOption.None
21
+ }),
22
+ view: function view(editorView) {
23
+ return {
24
+ update: function update(view, prevState) {
25
+ return prevState;
26
+ }
27
+ };
28
+ },
29
+ props: {
30
+ handleDOMEvents: {
31
+ // Hide toolbar when clicked outside the editor
32
+ blur: checkAndHideToolbar,
33
+ // Hide toolbar when clicked anywhere within the editor, tr.getMeta('pointer') does not work if clicked on the same line after pasting so relying on mousedown event
34
+ mousedown: checkAndHideToolbar
35
+ },
36
+ handleKeyDown: function handleKeyDown(view) {
37
+ checkAndHideToolbar(view);
38
+ return false;
39
+ },
40
+ decorations: function decorations(state) {
41
+ var _pasteOptionsPluginKe, _pasteOptionsPluginKe2;
42
+ var _ref = pasteOptionsPluginKey.getState(state) || {},
43
+ highlightContent = _ref.highlightContent,
44
+ pasteStartPos = _ref.pasteStartPos;
45
+ var decorationSet = (_pasteOptionsPluginKe = (_pasteOptionsPluginKe2 = pasteOptionsPluginKey.getState(state)) === null || _pasteOptionsPluginKe2 === void 0 ? void 0 : _pasteOptionsPluginKe2.highlightDecorationSet) !== null && _pasteOptionsPluginKe !== void 0 ? _pasteOptionsPluginKe : DecorationSet.empty;
46
+ if (!highlightContent) {
47
+ return decorationSet;
48
+ }
49
+ var selection = state.tr.selection;
50
+ var pasteEndPos = selection.$anchor.pos;
51
+ var highlightDecoration = Decoration.inline(pasteStartPos, pasteEndPos, {
52
+ class: TEXT_HIGHLIGHT_CLASS
53
+ }, {
54
+ key: PASTE_HIGHLIGHT_DECORATION_KEY
55
+ });
56
+ return decorationSet.add(state.doc, [highlightDecoration]);
57
+ }
58
+ }
59
+ });
60
+ }