@atlaskit/editor-plugin-annotation 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 (80) hide show
  1. package/.eslintrc.js +18 -0
  2. package/CHANGELOG.md +1 -0
  3. package/LICENSE.md +13 -0
  4. package/README.md +30 -0
  5. package/dist/cjs/commands/index.js +150 -0
  6. package/dist/cjs/commands/transform.js +86 -0
  7. package/dist/cjs/index.js +12 -0
  8. package/dist/cjs/nodeviews/index.js +59 -0
  9. package/dist/cjs/plugin.js +132 -0
  10. package/dist/cjs/pm-plugins/inline-comment.js +246 -0
  11. package/dist/cjs/pm-plugins/keymap.js +15 -0
  12. package/dist/cjs/pm-plugins/plugin-factory.js +107 -0
  13. package/dist/cjs/pm-plugins/reducer.js +84 -0
  14. package/dist/cjs/pm-plugins/types.js +17 -0
  15. package/dist/cjs/toolbar.js +59 -0
  16. package/dist/cjs/types.js +20 -0
  17. package/dist/cjs/ui/AnnotationViewWrapper.js +39 -0
  18. package/dist/cjs/ui/InlineCommentView.js +149 -0
  19. package/dist/cjs/utils.js +372 -0
  20. package/dist/es2019/commands/index.js +123 -0
  21. package/dist/es2019/commands/transform.js +64 -0
  22. package/dist/es2019/index.js +1 -0
  23. package/dist/es2019/nodeviews/index.js +31 -0
  24. package/dist/es2019/plugin.js +127 -0
  25. package/dist/es2019/pm-plugins/inline-comment.js +181 -0
  26. package/dist/es2019/pm-plugins/keymap.js +9 -0
  27. package/dist/es2019/pm-plugins/plugin-factory.js +108 -0
  28. package/dist/es2019/pm-plugins/reducer.js +94 -0
  29. package/dist/es2019/pm-plugins/types.js +11 -0
  30. package/dist/es2019/toolbar.js +53 -0
  31. package/dist/es2019/types.js +14 -0
  32. package/dist/es2019/ui/AnnotationViewWrapper.js +15 -0
  33. package/dist/es2019/ui/InlineCommentView.js +145 -0
  34. package/dist/es2019/utils.js +334 -0
  35. package/dist/esm/commands/index.js +143 -0
  36. package/dist/esm/commands/transform.js +80 -0
  37. package/dist/esm/index.js +1 -0
  38. package/dist/esm/nodeviews/index.js +52 -0
  39. package/dist/esm/plugin.js +120 -0
  40. package/dist/esm/pm-plugins/inline-comment.js +239 -0
  41. package/dist/esm/pm-plugins/keymap.js +9 -0
  42. package/dist/esm/pm-plugins/plugin-factory.js +101 -0
  43. package/dist/esm/pm-plugins/reducer.js +77 -0
  44. package/dist/esm/pm-plugins/types.js +11 -0
  45. package/dist/esm/toolbar.js +52 -0
  46. package/dist/esm/types.js +14 -0
  47. package/dist/esm/ui/AnnotationViewWrapper.js +32 -0
  48. package/dist/esm/ui/InlineCommentView.js +142 -0
  49. package/dist/esm/utils.js +345 -0
  50. package/dist/types/commands/index.d.ts +15 -0
  51. package/dist/types/commands/transform.d.ts +11 -0
  52. package/dist/types/index.d.ts +3 -0
  53. package/dist/types/nodeviews/index.d.ts +11 -0
  54. package/dist/types/plugin.d.ts +6 -0
  55. package/dist/types/pm-plugins/inline-comment.d.ts +3 -0
  56. package/dist/types/pm-plugins/keymap.d.ts +3 -0
  57. package/dist/types/pm-plugins/plugin-factory.d.ts +2 -0
  58. package/dist/types/pm-plugins/reducer.d.ts +3 -0
  59. package/dist/types/pm-plugins/types.d.ts +78 -0
  60. package/dist/types/toolbar.d.ts +5 -0
  61. package/dist/types/types.d.ts +100 -0
  62. package/dist/types/ui/AnnotationViewWrapper.d.ts +10 -0
  63. package/dist/types/ui/InlineCommentView.d.ts +12 -0
  64. package/dist/types/utils.d.ts +44 -0
  65. package/dist/types-ts4.5/commands/index.d.ts +15 -0
  66. package/dist/types-ts4.5/commands/transform.d.ts +11 -0
  67. package/dist/types-ts4.5/index.d.ts +3 -0
  68. package/dist/types-ts4.5/nodeviews/index.d.ts +11 -0
  69. package/dist/types-ts4.5/plugin.d.ts +6 -0
  70. package/dist/types-ts4.5/pm-plugins/inline-comment.d.ts +3 -0
  71. package/dist/types-ts4.5/pm-plugins/keymap.d.ts +3 -0
  72. package/dist/types-ts4.5/pm-plugins/plugin-factory.d.ts +2 -0
  73. package/dist/types-ts4.5/pm-plugins/reducer.d.ts +3 -0
  74. package/dist/types-ts4.5/pm-plugins/types.d.ts +78 -0
  75. package/dist/types-ts4.5/toolbar.d.ts +5 -0
  76. package/dist/types-ts4.5/types.d.ts +102 -0
  77. package/dist/types-ts4.5/ui/AnnotationViewWrapper.d.ts +10 -0
  78. package/dist/types-ts4.5/ui/InlineCommentView.d.ts +12 -0
  79. package/dist/types-ts4.5/utils.d.ts +44 -0
  80. package/package.json +106 -0
@@ -0,0 +1,14 @@
1
+ export let AnnotationSelectionType = /*#__PURE__*/function (AnnotationSelectionType) {
2
+ AnnotationSelectionType["INVALID"] = "invalid";
3
+ AnnotationSelectionType["DISABLED"] = "disabled";
4
+ AnnotationSelectionType["VALID"] = "valid";
5
+ return AnnotationSelectionType;
6
+ }({}); // Annotation can be created
7
+ const prefix = 'ak-editor-annotation';
8
+ export const AnnotationTestIds = {
9
+ prefix,
10
+ floatingComponent: `${prefix}-floating-component`,
11
+ floatingToolbarCreateButton: `${prefix}-toolbar-create-button`,
12
+ componentSave: `${prefix}-dummy-save-button`,
13
+ componentClose: `${prefix}-dummy-close-button`
14
+ };
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ // eslint-disable-next-line @repo/internal/react/no-class-components
3
+ export class AnnotationViewWrapper extends React.PureComponent {
4
+ componentDidMount() {
5
+ const {
6
+ onViewed
7
+ } = this.props;
8
+ if (onViewed) {
9
+ onViewed();
10
+ }
11
+ }
12
+ render() {
13
+ return this.props.children;
14
+ }
15
+ }
@@ -0,0 +1,145 @@
1
+ import React from 'react';
2
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, CONTENT_COMPONENT, EVENT_TYPE, RESOLVE_METHOD } from '@atlaskit/editor-common/analytics';
3
+ import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
4
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
5
+ import { closeComponent, createAnnotation, removeInlineCommentNearSelection, setInlineCommentDraftState, updateInlineCommentResolvedState } from '../commands';
6
+ import { AnnotationTestIds } from '../types';
7
+ import { getAllAnnotations, getAnnotationViewKey, getPluginState, getSelectionPositions } from '../utils';
8
+ import { AnnotationViewWrapper } from './AnnotationViewWrapper';
9
+ const findPosForDOM = sel => {
10
+ const {
11
+ $from,
12
+ from
13
+ } = sel;
14
+
15
+ // Retrieve current TextNode
16
+ const index = $from.index();
17
+ const node = index < $from.parent.childCount && $from.parent.child(index);
18
+
19
+ // Right edge of a mark.
20
+ if (!node && $from.nodeBefore && $from.nodeBefore.isText && $from.nodeBefore.marks.find(mark => mark.type.name === 'annotation')) {
21
+ return from - 1;
22
+ }
23
+ return from;
24
+ };
25
+ export function InlineCommentView({
26
+ providers,
27
+ editorView,
28
+ editorAnalyticsAPI,
29
+ dispatchAnalyticsEvent
30
+ }) {
31
+ // As inlineComment is the only annotation present, this function is not generic
32
+ const {
33
+ inlineComment: inlineCommentProvider
34
+ } = providers;
35
+ const {
36
+ state,
37
+ dispatch
38
+ } = editorView;
39
+ const {
40
+ createComponent: CreateComponent,
41
+ viewComponent: ViewComponent
42
+ } = inlineCommentProvider;
43
+ const inlineCommentState = getPluginState(state);
44
+ const {
45
+ bookmark,
46
+ selectedAnnotations,
47
+ annotations,
48
+ isInlineCommentViewClosed
49
+ } = inlineCommentState || {};
50
+ const annotationsList = getAllAnnotations(editorView.state.doc);
51
+ const selection = getSelectionPositions(state, inlineCommentState);
52
+ const position = findPosForDOM(selection);
53
+ let dom;
54
+ try {
55
+ dom = findDomRefAtPos(position, editorView.domAtPos.bind(editorView));
56
+ } catch (error) {
57
+ // eslint-disable-next-line no-console
58
+ console.warn(error);
59
+ if (dispatchAnalyticsEvent) {
60
+ const payload = {
61
+ action: ACTION.ERRORED,
62
+ actionSubject: ACTION_SUBJECT.CONTENT_COMPONENT,
63
+ eventType: EVENT_TYPE.OPERATIONAL,
64
+ attributes: {
65
+ component: CONTENT_COMPONENT.INLINE_COMMENT,
66
+ selection: selection.toJSON(),
67
+ position,
68
+ docSize: editorView.state.doc.nodeSize,
69
+ error: error.toString()
70
+ }
71
+ };
72
+ dispatchAnalyticsEvent(payload);
73
+ }
74
+ }
75
+ if (!dom) {
76
+ return null;
77
+ }
78
+
79
+ // Create Component
80
+ if (bookmark) {
81
+ if (!CreateComponent) {
82
+ return null;
83
+ }
84
+
85
+ //getting all text between bookmarked positions
86
+ const textSelection = state.doc.textBetween(selection.from, selection.to);
87
+ return /*#__PURE__*/React.createElement("div", {
88
+ "data-testid": AnnotationTestIds.floatingComponent,
89
+ "data-editor-popup": "true"
90
+ }, /*#__PURE__*/React.createElement(CreateComponent, {
91
+ dom: dom,
92
+ textSelection: textSelection,
93
+ onCreate: id => {
94
+ createAnnotation(editorAnalyticsAPI)(id)(editorView.state, editorView.dispatch);
95
+ !editorView.hasFocus() && editorView.focus();
96
+ },
97
+ onClose: () => {
98
+ setInlineCommentDraftState(editorAnalyticsAPI)(false)(editorView.state, editorView.dispatch);
99
+ !editorView.hasFocus() && editorView.focus();
100
+ }
101
+ }));
102
+ }
103
+
104
+ // View Component
105
+ const activeAnnotations = (selectedAnnotations === null || selectedAnnotations === void 0 ? void 0 : selectedAnnotations.filter(mark => annotations && annotations[mark.id] === false)) || [];
106
+ if (!ViewComponent || activeAnnotations.length === 0) {
107
+ return null;
108
+ }
109
+ const onAnnotationViewed = () => {
110
+ if (!dispatchAnalyticsEvent) {
111
+ return;
112
+ }
113
+ // fire analytics
114
+ const payload = {
115
+ action: ACTION.VIEWED,
116
+ actionSubject: ACTION_SUBJECT.ANNOTATION,
117
+ actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT,
118
+ eventType: EVENT_TYPE.TRACK,
119
+ attributes: {
120
+ overlap: activeAnnotations.length ? activeAnnotations.length - 1 : 0
121
+ }
122
+ };
123
+ dispatchAnalyticsEvent(payload);
124
+ };
125
+ if (getBooleanFF('platform.editor.annotation.decouple-inline-comment-closed_flmox') && isInlineCommentViewClosed || !selectedAnnotations) {
126
+ return null;
127
+ }
128
+ return /*#__PURE__*/React.createElement(AnnotationViewWrapper, {
129
+ "data-editor-popup": "true",
130
+ "data-testid": AnnotationTestIds.floatingComponent,
131
+ key: getAnnotationViewKey(activeAnnotations),
132
+ onViewed: onAnnotationViewed
133
+ }, /*#__PURE__*/React.createElement(ViewComponent, {
134
+ annotationsList: annotationsList,
135
+ annotations: activeAnnotations,
136
+ dom: dom,
137
+ onDelete: id => removeInlineCommentNearSelection(id)(state, dispatch),
138
+ onResolve: id => updateInlineCommentResolvedState(editorAnalyticsAPI)({
139
+ [id]: true
140
+ }, RESOLVE_METHOD.COMPONENT)(editorView.state, editorView.dispatch),
141
+ onClose: () => {
142
+ closeComponent()(editorView.state, editorView.dispatch);
143
+ }
144
+ }));
145
+ }
@@ -0,0 +1,334 @@
1
+ import { AnnotationTypes } from '@atlaskit/adf-schema';
2
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
3
+ import { AnnotationSharedClassNames } from '@atlaskit/editor-common/styles';
4
+ import { canApplyAnnotationOnRange, containsAnyAnnotations, getAnnotationIdsFromRange, hasAnnotationMark, isParagraph, isText } from '@atlaskit/editor-common/utils';
5
+ import { AllSelection, PluginKey, TextSelection } from '@atlaskit/editor-prosemirror/state';
6
+ import { Decoration } from '@atlaskit/editor-prosemirror/view';
7
+ import { AnnotationSelectionType } from './types';
8
+ export { hasAnnotationMark, containsAnyAnnotations };
9
+ function sum(arr, f) {
10
+ return arr.reduce((val, x) => val + f(x), 0);
11
+ }
12
+ /**
13
+ * Finds the marks in the nodes to the left and right.
14
+ * @param $pos Position to center search around
15
+ */
16
+ export const surroundingMarks = $pos => {
17
+ const {
18
+ nodeBefore,
19
+ nodeAfter
20
+ } = $pos;
21
+ const markNodeBefore = nodeBefore && $pos.doc.nodeAt(Math.max(0, $pos.pos - nodeBefore.nodeSize - 1));
22
+ const markNodeAfter = nodeAfter && $pos.doc.nodeAt($pos.pos + nodeAfter.nodeSize);
23
+ return [markNodeBefore && markNodeBefore.marks || [], markNodeAfter && markNodeAfter.marks || []];
24
+ };
25
+
26
+ /**
27
+ * Finds annotation marks, and returns their IDs.
28
+ * @param marks Array of marks to search in
29
+ */
30
+ const filterAnnotationIds = marks => {
31
+ if (!marks.length) {
32
+ return [];
33
+ }
34
+ const {
35
+ annotation
36
+ } = marks[0].type.schema.marks;
37
+ return marks.filter(mark => mark.type === annotation).map(mark => mark.attrs.id);
38
+ };
39
+
40
+ /**
41
+ * Re-orders the annotation array based on the order in the document.
42
+ *
43
+ * This places the marks that do not appear in the surrounding nodes
44
+ * higher in the list. That is, the inner-most one appears first.
45
+ *
46
+ * Undo, for example, can re-order annotation marks in the document.
47
+ * @param annotations annotation metadata
48
+ * @param $from location to look around (usually the selection)
49
+ */
50
+ const reorderAnnotations = (annotations, $from) => {
51
+ const idSet = surroundingMarks($from).map(filterAnnotationIds);
52
+ annotations.sort((a, b) => sum(idSet, ids => ids.indexOf(a.id)) - sum(idSet, ids => ids.indexOf(b.id)));
53
+ };
54
+ export const getAllAnnotations = doc => {
55
+ const allAnnotationIds = new Set();
56
+ doc.descendants(node => {
57
+ node.marks.filter(mark => mark.type.name === 'annotation')
58
+ // filter out annotations with invalid attributes as they cause errors when interacting with them
59
+ .filter(validateAnnotationMark).forEach(m => allAnnotationIds.add(m.attrs.id));
60
+ return true;
61
+ });
62
+ return Array.from(allAnnotationIds);
63
+ };
64
+
65
+ /*
66
+ * verifies if annotation mark contains valid attributes
67
+ */
68
+ const validateAnnotationMark = annotationMark => {
69
+ const {
70
+ id,
71
+ annotationType
72
+ } = annotationMark.attrs;
73
+ return validateAnnotationId(id) && validateAnnotationType(annotationType);
74
+ function validateAnnotationId(id) {
75
+ if (!id || typeof id !== 'string') {
76
+ return false;
77
+ }
78
+ const invalidIds = ['null', 'undefined'];
79
+ return !invalidIds.includes(id.toLowerCase());
80
+ }
81
+ function validateAnnotationType(type) {
82
+ if (!type || typeof type !== 'string') {
83
+ return false;
84
+ }
85
+ const allowedTypes = Object.values(AnnotationTypes);
86
+ return allowedTypes.includes(type);
87
+ }
88
+ };
89
+
90
+ /*
91
+ * add decoration for the comment selection in draft state
92
+ * (when creating new comment)
93
+ */
94
+ export const addDraftDecoration = (start, end) => {
95
+ return Decoration.inline(start, end, {
96
+ class: `${AnnotationSharedClassNames.draft}`
97
+ });
98
+ };
99
+ export const getAnnotationViewKey = annotations => {
100
+ const keys = annotations.map(mark => mark.id).join('_');
101
+ return `view-annotation-wrapper_${keys}`;
102
+ };
103
+ export const findAnnotationsInSelection = (selection, doc) => {
104
+ const {
105
+ empty,
106
+ $anchor,
107
+ anchor
108
+ } = selection;
109
+ // Only detect annotations on caret selection
110
+ if (!empty || !doc) {
111
+ return [];
112
+ }
113
+ const node = doc.nodeAt(anchor);
114
+ if (!node && !$anchor.nodeBefore) {
115
+ return [];
116
+ }
117
+ const annotationMark = doc.type.schema.marks.annotation;
118
+ const nodeBefore = $anchor.nodeBefore;
119
+ const anchorAnnotationMarks = node && node.marks || [];
120
+ let marks = [];
121
+ if (annotationMark.isInSet(anchorAnnotationMarks)) {
122
+ marks = anchorAnnotationMarks;
123
+ } else if (nodeBefore && annotationMark.isInSet(nodeBefore.marks)) {
124
+ marks = nodeBefore.marks;
125
+ }
126
+ const annotations = marks.filter(mark => mark.type.name === 'annotation').map(mark => ({
127
+ id: mark.attrs.id,
128
+ type: mark.attrs.annotationType
129
+ }));
130
+ reorderAnnotations(annotations, $anchor);
131
+ return annotations;
132
+ };
133
+
134
+ /**
135
+ * get selection from position to apply new comment for
136
+ * @return bookmarked positions if they exists, otherwise current selection positions
137
+ */
138
+ export function getSelectionPositions(editorState, inlineCommentState) {
139
+ const {
140
+ bookmark
141
+ } = inlineCommentState || {};
142
+ // get positions via saved bookmark if it is available
143
+ // this is to make comments box positioned relative to temporary highlight rather then current selection
144
+ if (bookmark) {
145
+ return bookmark.resolve(editorState.doc);
146
+ }
147
+ return editorState.selection;
148
+ }
149
+ export const inlineCommentPluginKey = new PluginKey('inlineCommentPluginKey');
150
+ export const getPluginState = state => {
151
+ return inlineCommentPluginKey.getState(state);
152
+ };
153
+
154
+ /**
155
+ * get number of unique annotations within current selection
156
+ */
157
+ const getAnnotationsInSelectionCount = state => {
158
+ const {
159
+ from,
160
+ to
161
+ } = state.selection;
162
+ const annotations = getAnnotationIdsFromRange({
163
+ from,
164
+ to
165
+ }, state.doc, state.schema);
166
+ return annotations.length;
167
+ };
168
+
169
+ /**
170
+ * get payload for the open/close analytics event
171
+ */
172
+ export const getDraftCommandAnalyticsPayload = (drafting, inputMethod) => {
173
+ const payload = state => {
174
+ let attributes = {};
175
+ if (drafting) {
176
+ attributes = {
177
+ inputMethod,
178
+ overlap: getAnnotationsInSelectionCount(state)
179
+ };
180
+ }
181
+ return {
182
+ action: drafting ? ACTION.OPENED : ACTION.CLOSED,
183
+ actionSubject: ACTION_SUBJECT.ANNOTATION,
184
+ actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT,
185
+ eventType: EVENT_TYPE.TRACK,
186
+ attributes
187
+ };
188
+ };
189
+ return payload;
190
+ };
191
+ export const isSelectionValid = state => {
192
+ const {
193
+ selection
194
+ } = state;
195
+ const {
196
+ disallowOnWhitespace
197
+ } = getPluginState(state) || {};
198
+ if (selection.empty || !(selection instanceof TextSelection || selection instanceof AllSelection)) {
199
+ return AnnotationSelectionType.INVALID;
200
+ }
201
+ const containsInvalidNodes = hasInvalidNodes(state);
202
+
203
+ // A selection that only covers 1 pos, and is an invalid node
204
+ // e.g. a text selection over a mention
205
+ if (containsInvalidNodes && selection.to - selection.from === 1) {
206
+ return AnnotationSelectionType.INVALID;
207
+ }
208
+ if (containsInvalidNodes) {
209
+ return AnnotationSelectionType.DISABLED;
210
+ }
211
+ if (disallowOnWhitespace && hasInvalidWhitespaceNode(selection, state.schema)) {
212
+ return AnnotationSelectionType.INVALID;
213
+ }
214
+ if (isEmptyTextSelection(selection, state.schema)) {
215
+ return AnnotationSelectionType.INVALID;
216
+ }
217
+ return AnnotationSelectionType.VALID;
218
+ };
219
+ export const hasInvalidNodes = state => {
220
+ const {
221
+ selection,
222
+ doc,
223
+ schema
224
+ } = state;
225
+ return !canApplyAnnotationOnRange({
226
+ from: selection.from,
227
+ to: selection.to
228
+ }, doc, schema);
229
+ };
230
+
231
+ /**
232
+ * Checks if selection contains only empty text
233
+ * e.g. when you select across multiple empty paragraphs
234
+ */
235
+ function isEmptyTextSelection(selection, schema) {
236
+ const {
237
+ text,
238
+ paragraph
239
+ } = schema.nodes;
240
+ let hasContent = false;
241
+ selection.content().content.descendants(node => {
242
+ // for empty paragraph - consider empty (nothing to comment on)
243
+ if (node.type === paragraph && !node.content.size) {
244
+ return false;
245
+ }
246
+ // for not a text or nonempty text - consider nonempty (can comment if the node is supported for annotations)
247
+ if (node.type !== text || !node.textContent) {
248
+ hasContent = true;
249
+ }
250
+ return !hasContent;
251
+ });
252
+ return !hasContent;
253
+ }
254
+
255
+ /**
256
+ * Checks if any of the nodes in a given selection are completely whitespace
257
+ * This is to conform to Confluence annotation specifications
258
+ */
259
+ export function hasInvalidWhitespaceNode(selection, schema) {
260
+ let foundInvalidWhitespace = false;
261
+ const content = selection.content().content;
262
+ content.descendants(node => {
263
+ if (isText(node, schema)) {
264
+ return false;
265
+ }
266
+ if (node.textContent.trim() === '') {
267
+ // Trailing new lines do not result in the annotation spanning into
268
+ // the trailing new line so can be ignored when looking for invalid
269
+ // whitespace nodes.
270
+ const nodeIsTrailingNewLine =
271
+ // it is the final node
272
+ node.eq(content.lastChild) &&
273
+ // and there are multiple nodes
274
+ !node.eq(content.firstChild) &&
275
+ // and it is a paragraph node
276
+ isParagraph(node, schema);
277
+ if (!nodeIsTrailingNewLine) {
278
+ foundInvalidWhitespace = true;
279
+ }
280
+ }
281
+ return !foundInvalidWhitespace;
282
+ });
283
+ return foundInvalidWhitespace;
284
+ }
285
+
286
+ /*
287
+ * verifies that the annotation exists by the given id
288
+ */
289
+ export function annotationExists(annotationId, state) {
290
+ const commentsPluginState = getPluginState(state);
291
+ return !!(commentsPluginState !== null && commentsPluginState !== void 0 && commentsPluginState.annotations) && Object.keys(commentsPluginState.annotations).includes(annotationId);
292
+ }
293
+
294
+ /*
295
+ * remove annotations that dont exsist in plugin state from slice
296
+ */
297
+ export function stripNonExistingAnnotations(slice, state) {
298
+ if (!slice.content.size) {
299
+ return false;
300
+ }
301
+ slice.content.forEach(node => {
302
+ stripNonExistingAnnotationsFromNode(node, state);
303
+ node.content.descendants(node => {
304
+ stripNonExistingAnnotationsFromNode(node, state);
305
+ return true;
306
+ });
307
+ });
308
+ }
309
+
310
+ /*
311
+ * remove annotations that dont exsist in plugin state
312
+ * from node
313
+ */
314
+ function stripNonExistingAnnotationsFromNode(node, state) {
315
+ if (hasAnnotationMark(node, state)) {
316
+ node.marks = node.marks.filter(mark => {
317
+ if (mark.type.name === 'annotation') {
318
+ return annotationExists(mark.attrs.id, state);
319
+ }
320
+ return true;
321
+ });
322
+ }
323
+ return node;
324
+ }
325
+
326
+ /**
327
+ * Compares two sets of annotationInfos to see if the annotations have changed
328
+ * This function assumes annotations will have unique id's for simplicity
329
+ */
330
+ export function isSelectedAnnotationsChanged(oldSelectedAnnotations, newSelectedAnnotations) {
331
+ return newSelectedAnnotations.length !== oldSelectedAnnotations.length ||
332
+ // assuming annotations have unique id's for simplicity
333
+ newSelectedAnnotations.some(annotation => !oldSelectedAnnotations.find(pluginStateAnnotation => annotation.id === pluginStateAnnotation.id && annotation.type === pluginStateAnnotation.type));
334
+ }
@@ -0,0 +1,143 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { AnnotationTypes } from '@atlaskit/adf-schema';
3
+ import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
4
+ import { createCommand } from '../pm-plugins/plugin-factory';
5
+ import { ACTIONS } from '../pm-plugins/types';
6
+ import { AnnotationSelectionType } from '../types';
7
+ import { getPluginState, isSelectionValid } from '../utils';
8
+ import transform from './transform';
9
+ export var updateInlineCommentResolvedState = function updateInlineCommentResolvedState(editorAnalyticsAPI) {
10
+ return function (partialNewState, resolveMethod) {
11
+ var command = {
12
+ type: ACTIONS.UPDATE_INLINE_COMMENT_STATE,
13
+ data: partialNewState
14
+ };
15
+ var allResolved = Object.values(partialNewState).every(function (state) {
16
+ return state;
17
+ });
18
+ if (resolveMethod && allResolved) {
19
+ return createCommand(command, transform.addResolveAnalytics(editorAnalyticsAPI)(resolveMethod));
20
+ }
21
+ return createCommand(command);
22
+ };
23
+ };
24
+ export var closeComponent = function closeComponent() {
25
+ return createCommand({
26
+ type: ACTIONS.CLOSE_COMPONENT
27
+ });
28
+ };
29
+ export var clearDirtyMark = function clearDirtyMark() {
30
+ return createCommand({
31
+ type: ACTIONS.INLINE_COMMENT_CLEAR_DIRTY_MARK
32
+ });
33
+ };
34
+ export var removeInlineCommentNearSelection = function removeInlineCommentNearSelection(id) {
35
+ return function (state, dispatch) {
36
+ var tr = state.tr,
37
+ $from = state.selection.$from;
38
+ var annotationMarkType = state.schema.marks.annotation;
39
+ var hasAnnotation = $from.marks().some(function (mark) {
40
+ return mark.type === annotationMarkType;
41
+ });
42
+ if (!hasAnnotation) {
43
+ return false;
44
+ }
45
+
46
+ // just remove entire mark from around the node
47
+ tr.removeMark($from.start(), $from.end(), annotationMarkType.create({
48
+ id: id,
49
+ type: AnnotationTypes.INLINE_COMMENT
50
+ }));
51
+ if (dispatch) {
52
+ dispatch(tr);
53
+ }
54
+ return true;
55
+ };
56
+ };
57
+ var getDraftCommandAction = function getDraftCommandAction(drafting) {
58
+ return function (editorState) {
59
+ // validate selection only when entering draft mode
60
+ if (drafting && isSelectionValid(editorState) !== AnnotationSelectionType.VALID) {
61
+ return false;
62
+ }
63
+ return {
64
+ type: ACTIONS.SET_INLINE_COMMENT_DRAFT_STATE,
65
+ data: {
66
+ drafting: drafting,
67
+ editorState: editorState
68
+ }
69
+ };
70
+ };
71
+ };
72
+ export var setInlineCommentDraftState = function setInlineCommentDraftState(editorAnalyticsAPI) {
73
+ return function (drafting) {
74
+ var inputMethod = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : INPUT_METHOD.TOOLBAR;
75
+ var commandAction = getDraftCommandAction(drafting);
76
+ return createCommand(commandAction, transform.addOpenCloseAnalytics(editorAnalyticsAPI)(drafting, inputMethod));
77
+ };
78
+ };
79
+ export var addInlineComment = function addInlineComment(editorAnalyticsAPI) {
80
+ return function (id) {
81
+ var commandAction = function commandAction(editorState) {
82
+ return {
83
+ type: ACTIONS.ADD_INLINE_COMMENT,
84
+ data: {
85
+ drafting: false,
86
+ inlineComments: _defineProperty({}, id, false),
87
+ // Auto make the newly inserted comment selected.
88
+ // We move the selection to the head of the comment selection.
89
+ selectedAnnotations: [{
90
+ id: id,
91
+ type: AnnotationTypes.INLINE_COMMENT
92
+ }],
93
+ editorState: editorState
94
+ }
95
+ };
96
+ };
97
+ return createCommand(commandAction, transform.addInlineComment(editorAnalyticsAPI)(id));
98
+ };
99
+ };
100
+ export var updateMouseState = function updateMouseState(mouseData) {
101
+ return createCommand({
102
+ type: ACTIONS.INLINE_COMMENT_UPDATE_MOUSE_STATE,
103
+ data: {
104
+ mouseData: mouseData
105
+ }
106
+ });
107
+ };
108
+ export var setSelectedAnnotation = function setSelectedAnnotation(id) {
109
+ return createCommand({
110
+ type: ACTIONS.SET_SELECTED_ANNOTATION,
111
+ data: {
112
+ selectedAnnotations: [{
113
+ id: id,
114
+ type: AnnotationTypes.INLINE_COMMENT
115
+ }]
116
+ }
117
+ });
118
+ };
119
+ export var createAnnotation = function createAnnotation(editorAnalyticsAPI) {
120
+ return function (id) {
121
+ var annotationType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : AnnotationTypes.INLINE_COMMENT;
122
+ return function (state, dispatch) {
123
+ // don't try to add if there are is no temp highlight bookmarked
124
+ var _ref = getPluginState(state) || {},
125
+ bookmark = _ref.bookmark;
126
+ if (!bookmark || !dispatch) {
127
+ return false;
128
+ }
129
+ if (annotationType === AnnotationTypes.INLINE_COMMENT) {
130
+ return addInlineComment(editorAnalyticsAPI)(id)(state, dispatch);
131
+ }
132
+ return false;
133
+ };
134
+ };
135
+ };
136
+ export var setInlineCommentsVisibility = function setInlineCommentsVisibility(isVisible) {
137
+ return createCommand({
138
+ type: ACTIONS.INLINE_COMMENT_SET_VISIBLE,
139
+ data: {
140
+ isVisible: isVisible
141
+ }
142
+ });
143
+ };