@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,64 @@
1
+ import { AnnotationTypes } from '@atlaskit/adf-schema';
2
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
3
+ import { applyMarkOnRange } from '@atlaskit/editor-common/mark';
4
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
5
+ import { getDraftCommandAnalyticsPayload, getPluginState, getSelectionPositions } from '../utils';
6
+ const addAnnotationMark = id => (transaction, state) => {
7
+ const inlineCommentState = getPluginState(state);
8
+ const {
9
+ from,
10
+ to,
11
+ head
12
+ } = getSelectionPositions(state, inlineCommentState);
13
+ const annotationMark = state.schema.marks.annotation.create({
14
+ id,
15
+ type: AnnotationTypes.INLINE_COMMENT
16
+ });
17
+ // Apply the mark only to text node in the range.
18
+ let tr = applyMarkOnRange(from, to, false, annotationMark, transaction);
19
+ // set selection back to the end of annotation once annotation mark is applied
20
+ tr.setSelection(TextSelection.create(tr.doc, head));
21
+ return tr;
22
+ };
23
+ const addInlineComment = editorAnalyticsAPI => id => (transaction, state) => {
24
+ let tr = addAnnotationMark(id)(transaction, state);
25
+ // add insert analytics step to transaction
26
+ tr = addInsertAnalytics(editorAnalyticsAPI)(tr, state);
27
+ // add close analytics step to transaction
28
+ tr = addOpenCloseAnalytics(editorAnalyticsAPI)(false, INPUT_METHOD.TOOLBAR)(tr, state);
29
+ return tr;
30
+ };
31
+ const addOpenCloseAnalytics = editorAnalyticsAPI => (drafting, method = INPUT_METHOD.TOOLBAR) => (transaction, state) => {
32
+ const draftingPayload = getDraftCommandAnalyticsPayload(drafting, method)(state);
33
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent(draftingPayload)(transaction);
34
+ return transaction;
35
+ };
36
+ const addInsertAnalytics = editorAnalyticsAPI => (transaction, state) => {
37
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
38
+ action: ACTION.INSERTED,
39
+ actionSubject: ACTION_SUBJECT.ANNOTATION,
40
+ eventType: EVENT_TYPE.TRACK,
41
+ actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT
42
+ })(transaction);
43
+ return transaction;
44
+ };
45
+ const addResolveAnalytics = editorAnalyticsAPI => method => (transaction, state) => {
46
+ const resolvedPayload = {
47
+ action: ACTION.RESOLVED,
48
+ actionSubject: ACTION_SUBJECT.ANNOTATION,
49
+ actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT,
50
+ eventType: EVENT_TYPE.TRACK,
51
+ attributes: {
52
+ method
53
+ }
54
+ };
55
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent(resolvedPayload)(transaction);
56
+ return transaction;
57
+ };
58
+ export default {
59
+ addAnnotationMark,
60
+ addInlineComment,
61
+ addOpenCloseAnalytics,
62
+ addInsertAnalytics,
63
+ addResolveAnalytics
64
+ };
@@ -0,0 +1 @@
1
+ export { annotationPlugin } from './plugin';
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import ReactNodeView from '@atlaskit/editor-common/react-node-view';
3
+ import { AnnotationSharedClassNames } from '@atlaskit/editor-common/styles';
4
+ export class AnnotationNodeView extends ReactNodeView {
5
+ createDomRef() {
6
+ return document.createElement('span');
7
+ }
8
+ getContentDOM() {
9
+ const dom = document.createElement('span');
10
+ dom.className = 'ak-editor-annotation';
11
+ return {
12
+ dom
13
+ };
14
+ }
15
+ render(_props, forwardRef) {
16
+ return (
17
+ /*#__PURE__*/
18
+ // all inline comment states are now set in decorations at ../pm-plugins/inline-comment.ts
19
+ React.createElement("span", {
20
+ "data-mark-type": "annotation",
21
+ ref: forwardRef
22
+ })
23
+ );
24
+ }
25
+ }
26
+ export const getAnnotationViewClassname = (isUnresolved, hasFocus) => {
27
+ if (!isUnresolved) {
28
+ return;
29
+ }
30
+ return hasFocus ? AnnotationSharedClassNames.focus : AnnotationSharedClassNames.blur;
31
+ };
@@ -0,0 +1,127 @@
1
+ import React from 'react';
2
+ import { annotation } from '@atlaskit/adf-schema';
3
+ import { AnnotationUpdateEmitter } from '@atlaskit/editor-common/annotation';
4
+ import { useSharedPluginState } from '@atlaskit/editor-common/hooks';
5
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
6
+ import { inlineCommentPlugin } from './pm-plugins/inline-comment';
7
+ import { keymapPlugin } from './pm-plugins/keymap';
8
+ import { buildToolbar } from './toolbar';
9
+ import { InlineCommentView } from './ui/InlineCommentView';
10
+ import { getPluginState, stripNonExistingAnnotations } from './utils';
11
+ export const annotationPlugin = ({
12
+ config: annotationProviders,
13
+ api
14
+ }) => {
15
+ return {
16
+ name: 'annotation',
17
+ marks() {
18
+ return [{
19
+ name: 'annotation',
20
+ mark: annotation
21
+ }];
22
+ },
23
+ actions: {
24
+ stripNonExistingAnnotations
25
+ },
26
+ getSharedState(editorState) {
27
+ if (!editorState) {
28
+ return undefined;
29
+ }
30
+ return getPluginState(editorState) || undefined;
31
+ },
32
+ pmPlugins: () => [{
33
+ name: 'annotation',
34
+ plugin: ({
35
+ dispatch,
36
+ portalProviderAPI,
37
+ eventDispatcher
38
+ }) => {
39
+ if (annotationProviders) {
40
+ var _api$analytics;
41
+ return inlineCommentPlugin({
42
+ dispatch,
43
+ portalProviderAPI,
44
+ eventDispatcher,
45
+ provider: annotationProviders.inlineComment,
46
+ editorAnalyticsAPI: api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions
47
+ });
48
+ }
49
+ return;
50
+ }
51
+ }, {
52
+ name: 'annotationKeymap',
53
+ plugin: () => {
54
+ if (annotationProviders) {
55
+ var _api$analytics2;
56
+ return keymapPlugin(api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions);
57
+ }
58
+ return;
59
+ }
60
+ }],
61
+ pluginsOptions: {
62
+ floatingToolbar(state, intl) {
63
+ if (getBooleanFF('platform.editor.enable-selection-toolbar_ucdwd') || !annotationProviders) {
64
+ return;
65
+ }
66
+ const pluginState = getPluginState(state);
67
+ if (pluginState && pluginState.isVisible && !pluginState.bookmark && !pluginState.mouseData.isSelecting) {
68
+ var _api$analytics3;
69
+ const {
70
+ isToolbarAbove
71
+ } = annotationProviders.inlineComment;
72
+ return buildToolbar(api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions)(state, intl, isToolbarAbove);
73
+ }
74
+ },
75
+ selectionToolbar(state, intl) {
76
+ if (!getBooleanFF('platform.editor.enable-selection-toolbar_ucdwd') || !annotationProviders) {
77
+ return;
78
+ }
79
+ const pluginState = getPluginState(state);
80
+ if (pluginState && pluginState.isVisible && !pluginState.bookmark && !pluginState.mouseData.isSelecting) {
81
+ var _api$analytics4;
82
+ const {
83
+ isToolbarAbove
84
+ } = annotationProviders.inlineComment;
85
+ return buildToolbar(api === null || api === void 0 ? void 0 : (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 ? void 0 : _api$analytics4.actions)(state, intl, isToolbarAbove);
86
+ }
87
+ }
88
+ },
89
+ contentComponent({
90
+ editorView,
91
+ dispatchAnalyticsEvent
92
+ }) {
93
+ if (!annotationProviders) {
94
+ return null;
95
+ }
96
+ return /*#__PURE__*/React.createElement(AnnotationContentComponent, {
97
+ api: api,
98
+ editorView: editorView,
99
+ annotationProviders: annotationProviders,
100
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent
101
+ });
102
+ }
103
+ };
104
+ };
105
+ function AnnotationContentComponent({
106
+ api,
107
+ editorView,
108
+ annotationProviders,
109
+ dispatchAnalyticsEvent
110
+ }) {
111
+ var _api$analytics5;
112
+ const {
113
+ annotationState: inlineCommentState
114
+ } = useSharedPluginState(api, ['annotation']);
115
+ if (inlineCommentState && !inlineCommentState.isVisible) {
116
+ return null;
117
+ }
118
+ return /*#__PURE__*/React.createElement("div", {
119
+ "data-editor-popup": "true"
120
+ }, /*#__PURE__*/React.createElement(InlineCommentView, {
121
+ providers: annotationProviders,
122
+ editorView: editorView,
123
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
124
+ editorAnalyticsAPI: api === null || api === void 0 ? void 0 : (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 ? void 0 : _api$analytics5.actions
125
+ }));
126
+ }
127
+ export { AnnotationUpdateEmitter };
@@ -0,0 +1,181 @@
1
+ import { AnnotationTypes } from '@atlaskit/adf-schema';
2
+ import { RESOLVE_METHOD } from '@atlaskit/editor-common/analytics';
3
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
4
+ import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
5
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
6
+ import { clearDirtyMark, closeComponent, setInlineCommentsVisibility, setSelectedAnnotation, updateInlineCommentResolvedState, updateMouseState } from '../commands';
7
+ import { AnnotationNodeView, getAnnotationViewClassname } from '../nodeviews';
8
+ import { getAllAnnotations, getPluginState, inlineCommentPluginKey } from '../utils';
9
+ import { createPluginState } from './plugin-factory';
10
+ const fetchProviderStates = async (provider, annotationIds) => {
11
+ const data = await provider.getState(annotationIds);
12
+ let result = {};
13
+ data.forEach(annotation => {
14
+ if (annotation.annotationType === AnnotationTypes.INLINE_COMMENT) {
15
+ result[annotation.id] = annotation.state.resolved;
16
+ }
17
+ });
18
+ return result;
19
+ };
20
+
21
+ // fetchState is unable to return a command as it's runs async and may dispatch at a later time
22
+ // Requires `editorView` instead of the decomposition as the async means state may end up stale
23
+ const fetchState = async (provider, annotationIds, editorView, editorAnalyticsAPI) => {
24
+ if (!annotationIds || !annotationIds.length) {
25
+ return;
26
+ }
27
+ const inlineCommentStates = await fetchProviderStates(provider, annotationIds);
28
+ if (editorView.dispatch) {
29
+ updateInlineCommentResolvedState(editorAnalyticsAPI)(inlineCommentStates)(editorView.state, editorView.dispatch);
30
+ }
31
+ };
32
+ const initialState = (disallowOnWhitespace = false) => {
33
+ return {
34
+ annotations: {},
35
+ selectedAnnotations: [],
36
+ mouseData: {
37
+ isSelecting: false
38
+ },
39
+ disallowOnWhitespace,
40
+ isInlineCommentViewClosed: false,
41
+ isVisible: true,
42
+ skipSelectionHandling: false
43
+ };
44
+ };
45
+ const hideToolbar = (state, dispatch) => () => {
46
+ updateMouseState({
47
+ isSelecting: true
48
+ })(state, dispatch);
49
+ };
50
+
51
+ // Subscribe to updates from consumer
52
+ const onResolve = editorAnalyticsAPI => (state, dispatch) => annotationId => {
53
+ updateInlineCommentResolvedState(editorAnalyticsAPI)({
54
+ [annotationId]: true
55
+ }, RESOLVE_METHOD.CONSUMER)(state, dispatch);
56
+ };
57
+ const onUnResolve = editorAnalyticsAPI => (state, dispatch) => annotationId => {
58
+ updateInlineCommentResolvedState(editorAnalyticsAPI)({
59
+ [annotationId]: false
60
+ })(state, dispatch);
61
+ };
62
+ const onMouseUp = (state, dispatch) => e => {
63
+ const {
64
+ mouseData
65
+ } = getPluginState(state) || {};
66
+ if (mouseData !== null && mouseData !== void 0 && mouseData.isSelecting) {
67
+ updateMouseState({
68
+ isSelecting: false
69
+ })(state, dispatch);
70
+ }
71
+ };
72
+ const onSetVisibility = view => isVisible => {
73
+ const {
74
+ state,
75
+ dispatch
76
+ } = view;
77
+ setInlineCommentsVisibility(isVisible)(state, dispatch);
78
+ if (isVisible) {
79
+ // PM retains focus when we click away from the editor.
80
+ // This will restore the visual aspect of the selection,
81
+ // otherwise it will seem a floating toolbar will appear
82
+ // for no reason.
83
+ view.focus();
84
+ }
85
+ };
86
+ export const inlineCommentPlugin = options => {
87
+ const {
88
+ provider,
89
+ portalProviderAPI,
90
+ eventDispatcher
91
+ } = options;
92
+ return new SafePlugin({
93
+ key: inlineCommentPluginKey,
94
+ state: createPluginState(options.dispatch, initialState(provider.disallowOnWhitespace)),
95
+ view(editorView) {
96
+ // Get initial state
97
+ // Need to pass `editorView` to mitigate editor state going stale
98
+ fetchState(provider, getAllAnnotations(editorView.state.doc), editorView, options.editorAnalyticsAPI);
99
+ const resolve = annotationId => onResolve(options.editorAnalyticsAPI)(editorView.state, editorView.dispatch)(annotationId);
100
+ const unResolve = annotationId => onUnResolve(options.editorAnalyticsAPI)(editorView.state, editorView.dispatch)(annotationId);
101
+ const mouseUp = event => onMouseUp(editorView.state, editorView.dispatch)(event);
102
+ const setVisibility = isVisible => onSetVisibility(editorView)(isVisible);
103
+ const setSelectedAnnotationFn = annotationId => {
104
+ if (!annotationId) {
105
+ closeComponent()(editorView.state, editorView.dispatch);
106
+ } else {
107
+ setSelectedAnnotation(annotationId)(editorView.state, editorView.dispatch);
108
+ }
109
+ };
110
+ const {
111
+ updateSubscriber
112
+ } = provider;
113
+ if (updateSubscriber) {
114
+ updateSubscriber.on('resolve', resolve).on('delete', resolve).on('unresolve', unResolve).on('create', unResolve).on('setvisibility', setVisibility).on('setselectedannotation', setSelectedAnnotationFn);
115
+ }
116
+
117
+ // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
118
+ editorView.root.addEventListener('mouseup', mouseUp);
119
+ return {
120
+ update(view, _prevState) {
121
+ const {
122
+ dirtyAnnotations
123
+ } = getPluginState(view.state) || {};
124
+ if (!dirtyAnnotations) {
125
+ return;
126
+ }
127
+ clearDirtyMark()(view.state, view.dispatch);
128
+ fetchState(provider, getAllAnnotations(view.state.doc), view, options.editorAnalyticsAPI);
129
+ },
130
+ destroy() {
131
+ // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
132
+ editorView.root.removeEventListener('mouseup', mouseUp);
133
+ if (updateSubscriber) {
134
+ updateSubscriber.off('resolve', resolve).off('delete', resolve).off('unresolve', unResolve).off('create', unResolve).off('setvisibility', setVisibility).off('setselectedannotation', setSelectedAnnotationFn);
135
+ }
136
+ }
137
+ };
138
+ },
139
+ props: {
140
+ nodeViews: {
141
+ annotation: (node, view, getPos) => new AnnotationNodeView(node, view, getPos, portalProviderAPI, eventDispatcher) // resolved
142
+ .init()
143
+ },
144
+ handleDOMEvents: {
145
+ mousedown: view => {
146
+ const pluginState = getPluginState(view.state);
147
+ if (!(pluginState !== null && pluginState !== void 0 && pluginState.mouseData.isSelecting)) {
148
+ hideToolbar(view.state, view.dispatch)();
149
+ }
150
+ return false;
151
+ }
152
+ },
153
+ decorations(state) {
154
+ // highlight comments, depending on state
155
+ const {
156
+ draftDecorationSet,
157
+ annotations,
158
+ selectedAnnotations,
159
+ isVisible,
160
+ isInlineCommentViewClosed
161
+ } = getPluginState(state) || {};
162
+ let decorations = draftDecorationSet !== null && draftDecorationSet !== void 0 ? draftDecorationSet : DecorationSet.empty;
163
+ const focusDecorations = [];
164
+ state.doc.descendants((node, pos) => {
165
+ node.marks.filter(mark => mark.type === state.schema.marks.annotation).forEach(mark => {
166
+ const isSelected = getBooleanFF('platform.editor.annotation.decouple-inline-comment-closed_flmox') ? !isInlineCommentViewClosed && !!(selectedAnnotations !== null && selectedAnnotations !== void 0 && selectedAnnotations.some(selectedAnnotation => selectedAnnotation.id === mark.attrs.id)) : !!(selectedAnnotations !== null && selectedAnnotations !== void 0 && selectedAnnotations.some(selectedAnnotation => selectedAnnotation.id === mark.attrs.id));
167
+ const isUnresolved = !!annotations && annotations[mark.attrs.id] === false;
168
+ if (isVisible) {
169
+ focusDecorations.push(Decoration.inline(pos, pos + node.nodeSize, {
170
+ class: `${getAnnotationViewClassname(isUnresolved, isSelected)} ${isUnresolved}`,
171
+ nodeName: 'span'
172
+ }));
173
+ }
174
+ });
175
+ });
176
+ decorations = decorations.add(state.doc, focusDecorations);
177
+ return decorations;
178
+ }
179
+ }
180
+ });
181
+ };
@@ -0,0 +1,9 @@
1
+ import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
+ import { addInlineComment, bindKeymapWithCommand } from '@atlaskit/editor-common/keymaps';
3
+ import { keymap } from '@atlaskit/editor-prosemirror/keymap';
4
+ import { setInlineCommentDraftState } from '../commands';
5
+ export function keymapPlugin(editorAnalyticsAPI) {
6
+ const list = {};
7
+ bindKeymapWithCommand(addInlineComment.common, setInlineCommentDraftState(editorAnalyticsAPI)(true, INPUT_METHOD.SHORTCUT), list);
8
+ return keymap(list);
9
+ }
@@ -0,0 +1,108 @@
1
+ import { pluginFactory } from '@atlaskit/editor-common/utils';
2
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
3
+ import { findAnnotationsInSelection, inlineCommentPluginKey, isSelectedAnnotationsChanged } from '../utils';
4
+ import reducer from './reducer';
5
+ const handleDocChanged = (tr, prevPluginState) => {
6
+ if (!tr.getMeta('replaceDocument')) {
7
+ return getBooleanFF('platform.editor.annotation.decouple-inline-comment-closed_flmox') ? getSelectionChangedHandler(false)(tr, prevPluginState) : handleSelectionChanged(tr, prevPluginState);
8
+ }
9
+ return {
10
+ ...prevPluginState,
11
+ dirtyAnnotations: true
12
+ };
13
+ };
14
+ const handleSelectionChanged = (tr, pluginState) => {
15
+ if (pluginState.skipSelectionHandling) {
16
+ return {
17
+ ...pluginState,
18
+ skipSelectionHandling: false
19
+ };
20
+ }
21
+ const selectedAnnotations = findAnnotationsInSelection(tr.selection, tr.doc);
22
+ const changed = selectedAnnotations.length !== pluginState.selectedAnnotations.length || selectedAnnotations.some(annotationInfo => {
23
+ return !pluginState.selectedAnnotations.some(aInfo => aInfo.type === annotationInfo.id);
24
+ });
25
+ if (changed) {
26
+ return {
27
+ ...pluginState,
28
+ selectedAnnotations
29
+ };
30
+ }
31
+ return pluginState;
32
+ };
33
+ const getSelectionChangedHandler = reopenCommentView => (tr, pluginState) => {
34
+ /**
35
+ * If feature flag is **OFF** we want to keep the old behavior. Note that
36
+ * reopenCommentView is not relevant here when using old behaviour.
37
+ *
38
+ * Feature flag is evaluated here rather than directly in onSelectionChanged where it is assigned
39
+ * to prevent the plugin from setting up the handler before the feature flag is evaluated.
40
+ *
41
+ * This comment / logic can be cleaned up once the feature flag is removed.
42
+ */
43
+ if (!getBooleanFF('platform.editor.annotation.decouple-inline-comment-closed_flmox')) {
44
+ return handleSelectionChanged(tr, pluginState);
45
+ }
46
+ if (pluginState.skipSelectionHandling) {
47
+ return {
48
+ ...pluginState,
49
+ skipSelectionHandling: false,
50
+ ...(reopenCommentView && {
51
+ isInlineCommentViewClosed: false
52
+ })
53
+ };
54
+ }
55
+ const selectedAnnotations = findAnnotationsInSelection(tr.selection, tr.doc);
56
+ if (selectedAnnotations.length === 0) {
57
+ return {
58
+ ...pluginState,
59
+ selectedAnnotations,
60
+ isInlineCommentViewClosed: true
61
+ };
62
+ }
63
+ if (isSelectedAnnotationsChanged(selectedAnnotations, pluginState.selectedAnnotations)) {
64
+ return {
65
+ ...pluginState,
66
+ selectedAnnotations,
67
+ ...(reopenCommentView && {
68
+ isInlineCommentViewClosed: false
69
+ })
70
+ };
71
+ }
72
+ return {
73
+ ...pluginState,
74
+ ...(reopenCommentView && {
75
+ isInlineCommentViewClosed: false
76
+ })
77
+ };
78
+ };
79
+ export const {
80
+ createPluginState,
81
+ createCommand
82
+ } = pluginFactory(inlineCommentPluginKey, reducer, {
83
+ onSelectionChanged: getSelectionChangedHandler(true),
84
+ onDocChanged: handleDocChanged,
85
+ mapping: (tr, pluginState) => {
86
+ let {
87
+ draftDecorationSet,
88
+ bookmark
89
+ } = pluginState;
90
+ let mappedDecorationSet, mappedBookmark;
91
+ if (draftDecorationSet) {
92
+ mappedDecorationSet = draftDecorationSet.map(tr.mapping, tr.doc);
93
+ }
94
+ if (bookmark) {
95
+ mappedBookmark = bookmark.map(tr.mapping);
96
+ }
97
+
98
+ // return same pluginState if mappings did not change
99
+ if (mappedBookmark === bookmark && mappedDecorationSet === draftDecorationSet) {
100
+ return pluginState;
101
+ }
102
+ return {
103
+ ...pluginState,
104
+ draftDecorationSet: mappedDecorationSet,
105
+ bookmark: mappedBookmark
106
+ };
107
+ }
108
+ });
@@ -0,0 +1,94 @@
1
+ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
2
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
3
+ import { addDraftDecoration } from '../utils';
4
+ import { ACTIONS } from './types';
5
+ export default ((pluginState, action) => {
6
+ switch (action.type) {
7
+ case ACTIONS.UPDATE_INLINE_COMMENT_STATE:
8
+ return {
9
+ ...pluginState,
10
+ annotations: {
11
+ ...pluginState.annotations,
12
+ ...action.data
13
+ }
14
+ };
15
+ case ACTIONS.INLINE_COMMENT_UPDATE_MOUSE_STATE:
16
+ const mouseData = Object.assign({}, pluginState.mouseData, action.data.mouseData);
17
+ return {
18
+ ...pluginState,
19
+ mouseData
20
+ };
21
+ case ACTIONS.SET_INLINE_COMMENT_DRAFT_STATE:
22
+ return getNewDraftState(pluginState, action.data.drafting, action.data.editorState);
23
+ case ACTIONS.INLINE_COMMENT_CLEAR_DIRTY_MARK:
24
+ return {
25
+ ...pluginState,
26
+ dirtyAnnotations: false,
27
+ annotations: {}
28
+ };
29
+ case ACTIONS.CLOSE_COMPONENT:
30
+ return getBooleanFF('platform.editor.annotation.decouple-inline-comment-closed_flmox') ? {
31
+ ...pluginState,
32
+ isInlineCommentViewClosed: true
33
+ } : {
34
+ ...pluginState,
35
+ selectedAnnotations: []
36
+ };
37
+ case ACTIONS.ADD_INLINE_COMMENT:
38
+ const updatedPluginState = getNewDraftState(pluginState, action.data.drafting, action.data.editorState);
39
+ return {
40
+ ...updatedPluginState,
41
+ selectedAnnotations: [...updatedPluginState.selectedAnnotations, ...action.data.selectedAnnotations],
42
+ annotations: {
43
+ ...pluginState.annotations,
44
+ ...action.data.inlineComments
45
+ },
46
+ ...(getBooleanFF('platform.editor.annotation.decouple-inline-comment-closed_flmox') && {
47
+ isInlineCommentViewClosed: false
48
+ })
49
+ };
50
+ case ACTIONS.INLINE_COMMENT_SET_VISIBLE:
51
+ const {
52
+ isVisible
53
+ } = action.data;
54
+ if (isVisible === pluginState.isVisible) {
55
+ return pluginState;
56
+ }
57
+ return {
58
+ ...(isVisible ? pluginState : getNewDraftState(pluginState, false)),
59
+ isVisible
60
+ };
61
+ case ACTIONS.SET_SELECTED_ANNOTATION:
62
+ return {
63
+ ...pluginState,
64
+ selectedAnnotations: [...action.data.selectedAnnotations],
65
+ skipSelectionHandling: true,
66
+ ...(getBooleanFF('platform.editor.annotation.decouple-inline-comment-closed_flmox') && {
67
+ // if selecting annotation explicitly, reopen the comment view
68
+ isInlineCommentViewClosed: false
69
+ })
70
+ };
71
+ default:
72
+ return pluginState;
73
+ }
74
+ });
75
+ function getNewDraftState(pluginState, drafting, editorState) {
76
+ let {
77
+ draftDecorationSet
78
+ } = pluginState;
79
+ if (!draftDecorationSet || !drafting) {
80
+ draftDecorationSet = DecorationSet.empty;
81
+ }
82
+ let newState = {
83
+ ...pluginState,
84
+ draftDecorationSet
85
+ };
86
+ newState.bookmark = undefined;
87
+ if (drafting && editorState) {
88
+ newState.bookmark = editorState.selection.getBookmark();
89
+ const resolvedBookmark = newState.bookmark.resolve(editorState.doc);
90
+ const draftDecoration = addDraftDecoration(resolvedBookmark.from, resolvedBookmark.to);
91
+ newState.draftDecorationSet = draftDecorationSet.add(editorState.doc, [draftDecoration]);
92
+ }
93
+ return newState;
94
+ }
@@ -0,0 +1,11 @@
1
+ export let ACTIONS = /*#__PURE__*/function (ACTIONS) {
2
+ ACTIONS[ACTIONS["UPDATE_INLINE_COMMENT_STATE"] = 0] = "UPDATE_INLINE_COMMENT_STATE";
3
+ ACTIONS[ACTIONS["SET_INLINE_COMMENT_DRAFT_STATE"] = 1] = "SET_INLINE_COMMENT_DRAFT_STATE";
4
+ ACTIONS[ACTIONS["INLINE_COMMENT_UPDATE_MOUSE_STATE"] = 2] = "INLINE_COMMENT_UPDATE_MOUSE_STATE";
5
+ ACTIONS[ACTIONS["INLINE_COMMENT_CLEAR_DIRTY_MARK"] = 3] = "INLINE_COMMENT_CLEAR_DIRTY_MARK";
6
+ ACTIONS[ACTIONS["ADD_INLINE_COMMENT"] = 4] = "ADD_INLINE_COMMENT";
7
+ ACTIONS[ACTIONS["INLINE_COMMENT_SET_VISIBLE"] = 5] = "INLINE_COMMENT_SET_VISIBLE";
8
+ ACTIONS[ACTIONS["CLOSE_COMPONENT"] = 6] = "CLOSE_COMPONENT";
9
+ ACTIONS[ACTIONS["SET_SELECTED_ANNOTATION"] = 7] = "SET_SELECTED_ANNOTATION";
10
+ return ACTIONS;
11
+ }({});
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { addInlineComment, ToolTipContent } from '@atlaskit/editor-common/keymaps';
3
+ import { annotationMessages } from '@atlaskit/editor-common/messages';
4
+ import { calculateToolbarPositionAboveSelection, calculateToolbarPositionTrackHead } from '@atlaskit/editor-common/utils';
5
+ import CommentIcon from '@atlaskit/icon/glyph/comment';
6
+ import { setInlineCommentDraftState } from './commands';
7
+ import { AnnotationSelectionType, AnnotationTestIds } from './types';
8
+ import { isSelectionValid } from './utils';
9
+ export const buildToolbar = editorAnalyticsAPI => (state, intl, isToolbarAbove = false) => {
10
+ const {
11
+ schema
12
+ } = state;
13
+ const selectionValid = isSelectionValid(state);
14
+ if (selectionValid === AnnotationSelectionType.INVALID) {
15
+ return undefined;
16
+ }
17
+ const createCommentMessage = intl.formatMessage(annotationMessages.createComment);
18
+ const commentDisabledMessage = intl.formatMessage(annotationMessages.createCommentInvalid);
19
+ const createComment = {
20
+ type: 'button',
21
+ showTitle: true,
22
+ disabled: selectionValid === AnnotationSelectionType.DISABLED,
23
+ testId: AnnotationTestIds.floatingToolbarCreateButton,
24
+ icon: CommentIcon,
25
+ tooltipContent: selectionValid === AnnotationSelectionType.DISABLED ? commentDisabledMessage : /*#__PURE__*/React.createElement(ToolTipContent, {
26
+ description: createCommentMessage,
27
+ keymap: addInlineComment
28
+ }),
29
+ title: createCommentMessage,
30
+ onClick: (state, dispatch) => {
31
+ return setInlineCommentDraftState(editorAnalyticsAPI)(true)(state, dispatch);
32
+ }
33
+ };
34
+ const {
35
+ annotation
36
+ } = schema.marks;
37
+ const validNodes = Object.keys(schema.nodes).reduce((acc, current) => {
38
+ const type = schema.nodes[current];
39
+ if (type.allowsMarkType(annotation)) {
40
+ acc.push(type);
41
+ }
42
+ return acc;
43
+ }, []);
44
+ const toolbarTitle = intl.formatMessage(annotationMessages.toolbar);
45
+ const calcToolbarPosition = isToolbarAbove ? calculateToolbarPositionAboveSelection : calculateToolbarPositionTrackHead;
46
+ const onPositionCalculated = calcToolbarPosition(toolbarTitle);
47
+ return {
48
+ title: toolbarTitle,
49
+ nodeType: validNodes,
50
+ items: [createComment],
51
+ onPositionCalculated
52
+ };
53
+ };