@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.
- package/.eslintrc.js +18 -0
- package/CHANGELOG.md +1 -0
- package/LICENSE.md +13 -0
- package/README.md +30 -0
- package/dist/cjs/commands/index.js +150 -0
- package/dist/cjs/commands/transform.js +86 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/nodeviews/index.js +59 -0
- package/dist/cjs/plugin.js +132 -0
- package/dist/cjs/pm-plugins/inline-comment.js +246 -0
- package/dist/cjs/pm-plugins/keymap.js +15 -0
- package/dist/cjs/pm-plugins/plugin-factory.js +107 -0
- package/dist/cjs/pm-plugins/reducer.js +84 -0
- package/dist/cjs/pm-plugins/types.js +17 -0
- package/dist/cjs/toolbar.js +59 -0
- package/dist/cjs/types.js +20 -0
- package/dist/cjs/ui/AnnotationViewWrapper.js +39 -0
- package/dist/cjs/ui/InlineCommentView.js +149 -0
- package/dist/cjs/utils.js +372 -0
- package/dist/es2019/commands/index.js +123 -0
- package/dist/es2019/commands/transform.js +64 -0
- package/dist/es2019/index.js +1 -0
- package/dist/es2019/nodeviews/index.js +31 -0
- package/dist/es2019/plugin.js +127 -0
- package/dist/es2019/pm-plugins/inline-comment.js +181 -0
- package/dist/es2019/pm-plugins/keymap.js +9 -0
- package/dist/es2019/pm-plugins/plugin-factory.js +108 -0
- package/dist/es2019/pm-plugins/reducer.js +94 -0
- package/dist/es2019/pm-plugins/types.js +11 -0
- package/dist/es2019/toolbar.js +53 -0
- package/dist/es2019/types.js +14 -0
- package/dist/es2019/ui/AnnotationViewWrapper.js +15 -0
- package/dist/es2019/ui/InlineCommentView.js +145 -0
- package/dist/es2019/utils.js +334 -0
- package/dist/esm/commands/index.js +143 -0
- package/dist/esm/commands/transform.js +80 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/nodeviews/index.js +52 -0
- package/dist/esm/plugin.js +120 -0
- package/dist/esm/pm-plugins/inline-comment.js +239 -0
- package/dist/esm/pm-plugins/keymap.js +9 -0
- package/dist/esm/pm-plugins/plugin-factory.js +101 -0
- package/dist/esm/pm-plugins/reducer.js +77 -0
- package/dist/esm/pm-plugins/types.js +11 -0
- package/dist/esm/toolbar.js +52 -0
- package/dist/esm/types.js +14 -0
- package/dist/esm/ui/AnnotationViewWrapper.js +32 -0
- package/dist/esm/ui/InlineCommentView.js +142 -0
- package/dist/esm/utils.js +345 -0
- package/dist/types/commands/index.d.ts +15 -0
- package/dist/types/commands/transform.d.ts +11 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/nodeviews/index.d.ts +11 -0
- package/dist/types/plugin.d.ts +6 -0
- package/dist/types/pm-plugins/inline-comment.d.ts +3 -0
- package/dist/types/pm-plugins/keymap.d.ts +3 -0
- package/dist/types/pm-plugins/plugin-factory.d.ts +2 -0
- package/dist/types/pm-plugins/reducer.d.ts +3 -0
- package/dist/types/pm-plugins/types.d.ts +78 -0
- package/dist/types/toolbar.d.ts +5 -0
- package/dist/types/types.d.ts +100 -0
- package/dist/types/ui/AnnotationViewWrapper.d.ts +10 -0
- package/dist/types/ui/InlineCommentView.d.ts +12 -0
- package/dist/types/utils.d.ts +44 -0
- package/dist/types-ts4.5/commands/index.d.ts +15 -0
- package/dist/types-ts4.5/commands/transform.d.ts +11 -0
- package/dist/types-ts4.5/index.d.ts +3 -0
- package/dist/types-ts4.5/nodeviews/index.d.ts +11 -0
- package/dist/types-ts4.5/plugin.d.ts +6 -0
- package/dist/types-ts4.5/pm-plugins/inline-comment.d.ts +3 -0
- package/dist/types-ts4.5/pm-plugins/keymap.d.ts +3 -0
- package/dist/types-ts4.5/pm-plugins/plugin-factory.d.ts +2 -0
- package/dist/types-ts4.5/pm-plugins/reducer.d.ts +3 -0
- package/dist/types-ts4.5/pm-plugins/types.d.ts +78 -0
- package/dist/types-ts4.5/toolbar.d.ts +5 -0
- package/dist/types-ts4.5/types.d.ts +102 -0
- package/dist/types-ts4.5/ui/AnnotationViewWrapper.d.ts +10 -0
- package/dist/types-ts4.5/ui/InlineCommentView.d.ts +12 -0
- package/dist/types-ts4.5/utils.d.ts +44 -0
- 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
|
+
};
|