@atlaskit/editor-plugin-mentions 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 +7 -0
- package/CHANGELOG.md +1 -0
- package/LICENSE.md +13 -0
- package/README.md +9 -0
- package/dist/cjs/analytics.js +157 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/messages.js +29 -0
- package/dist/cjs/nodeviews/mention.js +117 -0
- package/dist/cjs/plugin.js +135 -0
- package/dist/cjs/pm-plugins/key.js +8 -0
- package/dist/cjs/pm-plugins/main.js +156 -0
- package/dist/cjs/pm-plugins/utils.js +23 -0
- package/dist/cjs/type-ahead/index.js +362 -0
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/ui/InviteItem/index.js +76 -0
- package/dist/cjs/ui/InviteItem/styles.js +19 -0
- package/dist/cjs/ui/Mention/index.js +98 -0
- package/dist/cjs/ui/ToolbarMention/index.js +63 -0
- package/dist/cjs/utils.js +32 -0
- package/dist/es2019/analytics.js +147 -0
- package/dist/es2019/index.js +1 -0
- package/dist/es2019/messages.js +23 -0
- package/dist/es2019/nodeviews/mention.js +80 -0
- package/dist/es2019/plugin.js +123 -0
- package/dist/es2019/pm-plugins/key.js +2 -0
- package/dist/es2019/pm-plugins/main.js +143 -0
- package/dist/es2019/pm-plugins/utils.js +14 -0
- package/dist/es2019/type-ahead/index.js +338 -0
- package/dist/es2019/types.js +1 -0
- package/dist/es2019/ui/InviteItem/index.js +67 -0
- package/dist/es2019/ui/InviteItem/styles.js +47 -0
- package/dist/es2019/ui/Mention/index.js +70 -0
- package/dist/es2019/ui/ToolbarMention/index.js +33 -0
- package/dist/es2019/utils.js +20 -0
- package/dist/esm/analytics.js +150 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/messages.js +23 -0
- package/dist/esm/nodeviews/mention.js +107 -0
- package/dist/esm/plugin.js +129 -0
- package/dist/esm/pm-plugins/key.js +2 -0
- package/dist/esm/pm-plugins/main.js +148 -0
- package/dist/esm/pm-plugins/utils.js +16 -0
- package/dist/esm/type-ahead/index.js +350 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/ui/InviteItem/index.js +66 -0
- package/dist/esm/ui/InviteItem/styles.js +12 -0
- package/dist/esm/ui/Mention/index.js +90 -0
- package/dist/esm/ui/ToolbarMention/index.js +53 -0
- package/dist/esm/utils.js +26 -0
- package/dist/types/analytics.d.ts +13 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/messages.d.ts +22 -0
- package/dist/types/nodeviews/mention.d.ts +9 -0
- package/dist/types/plugin.d.ts +3 -0
- package/dist/types/pm-plugins/key.d.ts +3 -0
- package/dist/types/pm-plugins/main.d.ts +6 -0
- package/dist/types/pm-plugins/utils.d.ts +4 -0
- package/dist/types/type-ahead/index.d.ts +17 -0
- package/dist/types/types.d.ts +38 -0
- package/dist/types/ui/InviteItem/index.d.ts +24 -0
- package/dist/types/ui/InviteItem/styles.d.ts +8 -0
- package/dist/types/ui/Mention/index.d.ts +19 -0
- package/dist/types/ui/ToolbarMention/index.d.ts +13 -0
- package/dist/types/utils.d.ts +8 -0
- package/dist/types-ts4.5/analytics.d.ts +13 -0
- package/dist/types-ts4.5/index.d.ts +2 -0
- package/dist/types-ts4.5/messages.d.ts +22 -0
- package/dist/types-ts4.5/nodeviews/mention.d.ts +9 -0
- package/dist/types-ts4.5/plugin.d.ts +3 -0
- package/dist/types-ts4.5/pm-plugins/key.d.ts +3 -0
- package/dist/types-ts4.5/pm-plugins/main.d.ts +6 -0
- package/dist/types-ts4.5/pm-plugins/utils.d.ts +4 -0
- package/dist/types-ts4.5/type-ahead/index.d.ts +17 -0
- package/dist/types-ts4.5/types.d.ts +41 -0
- package/dist/types-ts4.5/ui/InviteItem/index.d.ts +24 -0
- package/dist/types-ts4.5/ui/InviteItem/styles.d.ts +8 -0
- package/dist/types-ts4.5/ui/Mention/index.d.ts +19 -0
- package/dist/types-ts4.5/ui/ToolbarMention/index.d.ts +13 -0
- package/dist/types-ts4.5/utils.d.ts +8 -0
- package/package.json +112 -0
- package/report.api.md +92 -0
- package/tmp/api-report-tmp.d.ts +63 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import uuid from 'uuid';
|
|
3
|
+
import { TypeAheadAvailableNodes } from '@atlaskit/editor-common/type-ahead';
|
|
4
|
+
import { Fragment } from '@atlaskit/editor-prosemirror/model';
|
|
5
|
+
import { MENTION_ITEM_HEIGHT, MentionItem } from '@atlaskit/mention/item';
|
|
6
|
+
import { isResolvingMentionProvider } from '@atlaskit/mention/resource';
|
|
7
|
+
import { buildTypeAheadCancelPayload, buildTypeAheadInsertedPayload, buildTypeAheadInviteExposurePayload, buildTypeAheadInviteItemClickedPayload, buildTypeAheadInviteItemViewedPayload, buildTypeAheadRenderedPayload } from '../analytics';
|
|
8
|
+
import { getMentionPluginState } from '../pm-plugins/utils';
|
|
9
|
+
import InviteItem, { INVITE_ITEM_DESCRIPTION } from '../ui/InviteItem';
|
|
10
|
+
import { isInviteItem, isTeamStats, isTeamType, shouldKeepInviteItem } from '../utils';
|
|
11
|
+
const createInviteItem = ({
|
|
12
|
+
mentionProvider,
|
|
13
|
+
onInviteItemMount
|
|
14
|
+
}) => ({
|
|
15
|
+
title: INVITE_ITEM_DESCRIPTION.id,
|
|
16
|
+
render: ({
|
|
17
|
+
isSelected,
|
|
18
|
+
onClick,
|
|
19
|
+
onHover
|
|
20
|
+
}) => /*#__PURE__*/React.createElement(InviteItem, {
|
|
21
|
+
productName: mentionProvider ? mentionProvider.productName : undefined,
|
|
22
|
+
selected: isSelected,
|
|
23
|
+
onMount: onInviteItemMount,
|
|
24
|
+
onMouseEnter: onHover,
|
|
25
|
+
onSelection: onClick,
|
|
26
|
+
userRole: mentionProvider.userRole
|
|
27
|
+
}),
|
|
28
|
+
mention: INVITE_ITEM_DESCRIPTION
|
|
29
|
+
});
|
|
30
|
+
const withInviteItem = ({
|
|
31
|
+
mentionProvider,
|
|
32
|
+
firstQueryWithoutResults,
|
|
33
|
+
currentQuery,
|
|
34
|
+
onInviteItemMount
|
|
35
|
+
}) => mentionItems => {
|
|
36
|
+
const inviteItem = createInviteItem({
|
|
37
|
+
mentionProvider,
|
|
38
|
+
onInviteItemMount
|
|
39
|
+
});
|
|
40
|
+
const keepInviteItem = shouldKeepInviteItem(currentQuery, firstQueryWithoutResults);
|
|
41
|
+
if (mentionItems.length === 0) {
|
|
42
|
+
return keepInviteItem ? [inviteItem] : [];
|
|
43
|
+
}
|
|
44
|
+
return [...mentionItems,
|
|
45
|
+
// invite item should be shown at the bottom
|
|
46
|
+
inviteItem];
|
|
47
|
+
};
|
|
48
|
+
export const mentionToTypeaheadItem = mention => {
|
|
49
|
+
return {
|
|
50
|
+
title: mention.id,
|
|
51
|
+
render: ({
|
|
52
|
+
isSelected,
|
|
53
|
+
onClick,
|
|
54
|
+
onHover
|
|
55
|
+
}) => /*#__PURE__*/React.createElement(MentionItem, {
|
|
56
|
+
mention: mention,
|
|
57
|
+
selected: isSelected,
|
|
58
|
+
onMouseEnter: onHover,
|
|
59
|
+
onSelection: onClick
|
|
60
|
+
}),
|
|
61
|
+
getCustomComponentHeight: () => {
|
|
62
|
+
return MENTION_ITEM_HEIGHT;
|
|
63
|
+
},
|
|
64
|
+
mention
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
export function memoize(fn) {
|
|
68
|
+
// Cache results here
|
|
69
|
+
const seen = new Map();
|
|
70
|
+
function memoized(mention) {
|
|
71
|
+
// Check cache for hits
|
|
72
|
+
const hit = seen.get(mention.id);
|
|
73
|
+
if (hit) {
|
|
74
|
+
return hit;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Generate new result and cache it
|
|
78
|
+
const result = fn(mention);
|
|
79
|
+
seen.set(mention.id, result);
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
call: memoized,
|
|
84
|
+
clear: seen.clear.bind(seen)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const memoizedToItem = memoize(mentionToTypeaheadItem);
|
|
88
|
+
const buildAndSendElementsTypeAheadAnalytics = fireEvent => ({
|
|
89
|
+
query,
|
|
90
|
+
mentions,
|
|
91
|
+
stats
|
|
92
|
+
}) => {
|
|
93
|
+
let duration = 0;
|
|
94
|
+
let userOrTeamIds = null;
|
|
95
|
+
let teams = null;
|
|
96
|
+
if (!isTeamStats(stats)) {
|
|
97
|
+
// is from primary mention endpoint which could be just user mentions or user/team mentions
|
|
98
|
+
duration = stats && stats.duration;
|
|
99
|
+
teams = null;
|
|
100
|
+
userOrTeamIds = mentions.map(mention => mention.id);
|
|
101
|
+
} else {
|
|
102
|
+
// is from dedicated team-only mention endpoint
|
|
103
|
+
duration = stats && stats.teamMentionDuration;
|
|
104
|
+
userOrTeamIds = null;
|
|
105
|
+
teams = mentions.map(mention => isTeamType(mention.userType) ? {
|
|
106
|
+
teamId: mention.id,
|
|
107
|
+
includesYou: mention.context.includesYou,
|
|
108
|
+
memberCount: mention.context.memberCount
|
|
109
|
+
} : null).filter(m => !!m);
|
|
110
|
+
}
|
|
111
|
+
const payload = buildTypeAheadRenderedPayload(duration, userOrTeamIds, query, teams);
|
|
112
|
+
fireEvent(payload);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* When a team mention is selected, we render a team link and list of member/user mentions
|
|
117
|
+
* in editor content
|
|
118
|
+
*/
|
|
119
|
+
const buildNodesForTeamMention = (schema, selectedMention, mentionProvider, sanitizePrivateContent) => {
|
|
120
|
+
const {
|
|
121
|
+
nodes,
|
|
122
|
+
marks
|
|
123
|
+
} = schema;
|
|
124
|
+
const {
|
|
125
|
+
name,
|
|
126
|
+
id: teamId,
|
|
127
|
+
accessLevel,
|
|
128
|
+
context
|
|
129
|
+
} = selectedMention;
|
|
130
|
+
|
|
131
|
+
// build team link
|
|
132
|
+
const defaultTeamLink = `${window.location.origin}/people/team/${teamId}`;
|
|
133
|
+
const teamLink = context && context.teamLink ? context.teamLink : defaultTeamLink;
|
|
134
|
+
const teamLinkNode = schema.text(name, [marks.link.create({
|
|
135
|
+
href: teamLink
|
|
136
|
+
})]);
|
|
137
|
+
const openBracketText = schema.text('(');
|
|
138
|
+
const closeBracketText = schema.text(')');
|
|
139
|
+
const emptySpaceText = schema.text(' ');
|
|
140
|
+
const inlineNodes = [teamLinkNode, emptySpaceText, openBracketText];
|
|
141
|
+
const members = context && context.members ? context.members : [];
|
|
142
|
+
members.forEach((member, index) => {
|
|
143
|
+
const {
|
|
144
|
+
name,
|
|
145
|
+
id
|
|
146
|
+
} = member;
|
|
147
|
+
const mentionName = `@${name}`;
|
|
148
|
+
const text = sanitizePrivateContent ? '' : mentionName;
|
|
149
|
+
if (sanitizePrivateContent && isResolvingMentionProvider(mentionProvider)) {
|
|
150
|
+
mentionProvider.cacheMentionName(id, name);
|
|
151
|
+
}
|
|
152
|
+
const userMentionNode = nodes.mention.createChecked({
|
|
153
|
+
text,
|
|
154
|
+
id: member.id,
|
|
155
|
+
accessLevel,
|
|
156
|
+
userType: 'DEFAULT'
|
|
157
|
+
});
|
|
158
|
+
inlineNodes.push(userMentionNode);
|
|
159
|
+
// should not add empty space after the last user mention.
|
|
160
|
+
if (index !== members.length - 1) {
|
|
161
|
+
inlineNodes.push(emptySpaceText);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
inlineNodes.push(closeBracketText);
|
|
165
|
+
return Fragment.fromArray(inlineNodes);
|
|
166
|
+
};
|
|
167
|
+
export const createTypeAheadConfig = ({
|
|
168
|
+
sanitizePrivateContent,
|
|
169
|
+
mentionInsertDisplayName,
|
|
170
|
+
fireEvent,
|
|
171
|
+
HighlightComponent
|
|
172
|
+
}) => {
|
|
173
|
+
let sessionId = uuid();
|
|
174
|
+
let firstQueryWithoutResults = null;
|
|
175
|
+
const subscriptionKeys = new Set();
|
|
176
|
+
const typeAhead = {
|
|
177
|
+
id: TypeAheadAvailableNodes.MENTION,
|
|
178
|
+
trigger: '@',
|
|
179
|
+
// Custom regex must have a capture group around trigger
|
|
180
|
+
// so it's possible to use it without needing to scan through all triggers again
|
|
181
|
+
customRegex: '\\(?(@)',
|
|
182
|
+
getHighlight: state => {
|
|
183
|
+
const CustomHighlightComponent = HighlightComponent;
|
|
184
|
+
if (CustomHighlightComponent) {
|
|
185
|
+
return /*#__PURE__*/React.createElement(CustomHighlightComponent, null);
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
},
|
|
189
|
+
getItems({
|
|
190
|
+
query,
|
|
191
|
+
editorState
|
|
192
|
+
}) {
|
|
193
|
+
const pluginState = getMentionPluginState(editorState);
|
|
194
|
+
if (!(pluginState !== null && pluginState !== void 0 && pluginState.mentionProvider)) {
|
|
195
|
+
return Promise.resolve([]);
|
|
196
|
+
}
|
|
197
|
+
const {
|
|
198
|
+
mentionProvider,
|
|
199
|
+
contextIdentifierProvider
|
|
200
|
+
} = pluginState;
|
|
201
|
+
return new Promise(resolve => {
|
|
202
|
+
const key = `loadingMentionsForTypeAhead_${uuid()}`;
|
|
203
|
+
const mentionsSubscribeCallback = (mentions, resultQuery = '', stats) => {
|
|
204
|
+
if (query !== resultQuery) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
mentionProvider.unsubscribe(key);
|
|
208
|
+
subscriptionKeys.delete(key);
|
|
209
|
+
const mentionItems = mentions.map(mention => memoizedToItem.call(mention));
|
|
210
|
+
buildAndSendElementsTypeAheadAnalytics(fireEvent)({
|
|
211
|
+
query,
|
|
212
|
+
mentions,
|
|
213
|
+
stats
|
|
214
|
+
});
|
|
215
|
+
if (mentions.length === 0 && firstQueryWithoutResults === null) {
|
|
216
|
+
firstQueryWithoutResults = query;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Growth (El-dorado) experiment design hard requirement
|
|
220
|
+
if (mentionItems.length <= 2) {
|
|
221
|
+
const {
|
|
222
|
+
inviteExperimentCohort,
|
|
223
|
+
userRole
|
|
224
|
+
} = mentionProvider;
|
|
225
|
+
fireEvent(buildTypeAheadInviteExposurePayload(sessionId, contextIdentifierProvider, inviteExperimentCohort, userRole));
|
|
226
|
+
}
|
|
227
|
+
if (!mentionProvider.shouldEnableInvite || mentionItems.length > 2) {
|
|
228
|
+
resolve(mentionItems);
|
|
229
|
+
} else {
|
|
230
|
+
const items = withInviteItem({
|
|
231
|
+
mentionProvider,
|
|
232
|
+
firstQueryWithoutResults: firstQueryWithoutResults || '',
|
|
233
|
+
currentQuery: query,
|
|
234
|
+
onInviteItemMount: () => {
|
|
235
|
+
fireEvent(buildTypeAheadInviteItemViewedPayload(sessionId, contextIdentifierProvider, mentionProvider.userRole));
|
|
236
|
+
}
|
|
237
|
+
})(mentionItems);
|
|
238
|
+
resolve(items);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
subscriptionKeys.add(key);
|
|
242
|
+
mentionProvider.subscribe(key, mentionsSubscribeCallback);
|
|
243
|
+
mentionProvider.filter(query || '', {
|
|
244
|
+
...contextIdentifierProvider,
|
|
245
|
+
sessionId
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
onOpen: () => {
|
|
250
|
+
firstQueryWithoutResults = null;
|
|
251
|
+
},
|
|
252
|
+
selectItem(state, item, insert, {
|
|
253
|
+
mode,
|
|
254
|
+
stats,
|
|
255
|
+
query,
|
|
256
|
+
sourceListItem
|
|
257
|
+
}) {
|
|
258
|
+
const {
|
|
259
|
+
schema
|
|
260
|
+
} = state;
|
|
261
|
+
const pluginState = getMentionPluginState(state);
|
|
262
|
+
const {
|
|
263
|
+
mentionProvider
|
|
264
|
+
} = pluginState;
|
|
265
|
+
const {
|
|
266
|
+
id,
|
|
267
|
+
name,
|
|
268
|
+
nickname,
|
|
269
|
+
accessLevel,
|
|
270
|
+
userType
|
|
271
|
+
} = item.mention;
|
|
272
|
+
const trimmedNickname = nickname && nickname.startsWith('@') ? nickname.slice(1) : nickname;
|
|
273
|
+
const renderName = mentionInsertDisplayName || !trimmedNickname ? name : trimmedNickname;
|
|
274
|
+
const mentionContext = {
|
|
275
|
+
...pluginState.contextIdentifierProvider,
|
|
276
|
+
sessionId
|
|
277
|
+
};
|
|
278
|
+
if (mentionProvider && !isInviteItem(item.mention)) {
|
|
279
|
+
mentionProvider.recordMentionSelection(item.mention, mentionContext);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// use same timer as StatsModifier
|
|
283
|
+
const pickerElapsedTime = stats.startedAt ? performance.now() - stats.startedAt : 0;
|
|
284
|
+
if (mentionProvider && mentionProvider.shouldEnableInvite && isInviteItem(item.mention)) {
|
|
285
|
+
// Don't fire event and the callback with selection by space press
|
|
286
|
+
if (mode !== 'space') {
|
|
287
|
+
fireEvent(buildTypeAheadInviteItemClickedPayload(pickerElapsedTime, stats.keyCount.arrowUp, stats.keyCount.arrowDown, sessionId, mode, query, pluginState.contextIdentifierProvider, mentionProvider.userRole));
|
|
288
|
+
if (mentionProvider.onInviteItemClick) {
|
|
289
|
+
mentionProvider.onInviteItemClick('mention');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return state.tr;
|
|
293
|
+
}
|
|
294
|
+
fireEvent(buildTypeAheadInsertedPayload(pickerElapsedTime, stats.keyCount.arrowUp, stats.keyCount.arrowDown, sessionId, mode, item.mention, sourceListItem.map(x => x.mention), query, pluginState.contextIdentifierProvider));
|
|
295
|
+
sessionId = uuid();
|
|
296
|
+
if (mentionProvider && isTeamType(userType)) {
|
|
297
|
+
return insert(buildNodesForTeamMention(schema, item.mention, mentionProvider, sanitizePrivateContent));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Don't insert into document if document data is sanitized.
|
|
301
|
+
const text = sanitizePrivateContent ? '' : `@${renderName}`;
|
|
302
|
+
if (sanitizePrivateContent && isResolvingMentionProvider(mentionProvider)) {
|
|
303
|
+
// Cache (locally) for later rendering
|
|
304
|
+
mentionProvider.cacheMentionName(id, renderName);
|
|
305
|
+
}
|
|
306
|
+
const mentionNode = schema.nodes.mention.createChecked({
|
|
307
|
+
text,
|
|
308
|
+
id,
|
|
309
|
+
accessLevel,
|
|
310
|
+
userType: userType === 'DEFAULT' ? null : userType
|
|
311
|
+
});
|
|
312
|
+
const space = schema.text(' ');
|
|
313
|
+
return insert(Fragment.from([mentionNode, space]));
|
|
314
|
+
},
|
|
315
|
+
dismiss({
|
|
316
|
+
editorState,
|
|
317
|
+
query,
|
|
318
|
+
stats,
|
|
319
|
+
wasItemInserted
|
|
320
|
+
}) {
|
|
321
|
+
firstQueryWithoutResults = null;
|
|
322
|
+
const pickerElapsedTime = stats.startedAt ? performance.now() - stats.startedAt : 0;
|
|
323
|
+
if (!wasItemInserted) {
|
|
324
|
+
fireEvent(buildTypeAheadCancelPayload(pickerElapsedTime, stats.keyCount.arrowUp, stats.keyCount.arrowDown, sessionId, query || ''));
|
|
325
|
+
}
|
|
326
|
+
const pluginState = getMentionPluginState(editorState);
|
|
327
|
+
if (pluginState !== null && pluginState !== void 0 && pluginState.mentionProvider) {
|
|
328
|
+
const mentionProvider = pluginState.mentionProvider;
|
|
329
|
+
for (let key of subscriptionKeys) {
|
|
330
|
+
mentionProvider.unsubscribe(key);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
subscriptionKeys.clear();
|
|
334
|
+
sessionId = uuid();
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
return typeAhead;
|
|
338
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
/** @jsx jsx */
|
|
3
|
+
|
|
4
|
+
import React, { useCallback, useEffect } from 'react';
|
|
5
|
+
import { jsx } from '@emotion/react';
|
|
6
|
+
import { FormattedMessage, injectIntl } from 'react-intl-next';
|
|
7
|
+
import AddIcon from '@atlaskit/icon/glyph/add';
|
|
8
|
+
import { N300 } from '@atlaskit/theme/colors';
|
|
9
|
+
import { messages } from '../../messages';
|
|
10
|
+
import { avatarStyle, capitalizedStyle, mentionItemSelectedStyle, mentionItemStyle, nameSectionStyle, rowStyle } from './styles';
|
|
11
|
+
export const INVITE_ITEM_DESCRIPTION = {
|
|
12
|
+
id: 'invite-teammate'
|
|
13
|
+
};
|
|
14
|
+
const leftClick = event => {
|
|
15
|
+
return event.button === 0 && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey;
|
|
16
|
+
};
|
|
17
|
+
const InviteItem = ({
|
|
18
|
+
productName,
|
|
19
|
+
onMount,
|
|
20
|
+
onMouseEnter,
|
|
21
|
+
onSelection,
|
|
22
|
+
selected,
|
|
23
|
+
userRole,
|
|
24
|
+
intl
|
|
25
|
+
}) => {
|
|
26
|
+
const onSelected = useCallback(event => {
|
|
27
|
+
if (leftClick(event) && onSelection) {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
onSelection(INVITE_ITEM_DESCRIPTION, event);
|
|
30
|
+
}
|
|
31
|
+
}, [onSelection]);
|
|
32
|
+
const onItemMouseEnter = useCallback(event => {
|
|
33
|
+
if (onMouseEnter) {
|
|
34
|
+
onMouseEnter(INVITE_ITEM_DESCRIPTION, event);
|
|
35
|
+
}
|
|
36
|
+
}, [onMouseEnter]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (onMount) {
|
|
39
|
+
onMount();
|
|
40
|
+
}
|
|
41
|
+
}, [onMount]);
|
|
42
|
+
return jsx("div", {
|
|
43
|
+
css: [mentionItemStyle, selected && mentionItemSelectedStyle],
|
|
44
|
+
onMouseDown: onSelected,
|
|
45
|
+
onMouseEnter: onItemMouseEnter,
|
|
46
|
+
"data-id": INVITE_ITEM_DESCRIPTION.id
|
|
47
|
+
}, jsx("div", {
|
|
48
|
+
css: rowStyle
|
|
49
|
+
}, jsx("span", {
|
|
50
|
+
css: avatarStyle
|
|
51
|
+
}, jsx(AddIcon, {
|
|
52
|
+
label: intl.formatMessage(messages.mentionsAddLabel),
|
|
53
|
+
primaryColor: `var(--ds-icon-subtle, ${N300})`
|
|
54
|
+
})), jsx("div", {
|
|
55
|
+
css: nameSectionStyle,
|
|
56
|
+
"data-testid": "name-section"
|
|
57
|
+
}, jsx(FormattedMessage, _extends({}, messages.inviteItemTitle, {
|
|
58
|
+
values: {
|
|
59
|
+
userRole: userRole || 'basic',
|
|
60
|
+
productName: jsx("span", {
|
|
61
|
+
css: capitalizedStyle,
|
|
62
|
+
"data-testid": "capitalized-message"
|
|
63
|
+
}, productName)
|
|
64
|
+
}
|
|
65
|
+
})))));
|
|
66
|
+
};
|
|
67
|
+
export default injectIntl(InviteItem);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { css } from '@emotion/react';
|
|
2
|
+
import { N30, N300 } from '@atlaskit/theme/colors';
|
|
3
|
+
export const ROW_SIDE_PADDING = 14;
|
|
4
|
+
export const rowStyle = css`
|
|
5
|
+
align-items: center;
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: row;
|
|
8
|
+
flex-wrap: wrap;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
padding: ${"var(--ds-space-075, 6px)"} ${ROW_SIDE_PADDING}px;
|
|
11
|
+
text-overflow: ellipsis;
|
|
12
|
+
vertical-align: middle;
|
|
13
|
+
`;
|
|
14
|
+
export const AVATAR_HEIGHT = 36;
|
|
15
|
+
export const avatarStyle = css`
|
|
16
|
+
position: relative;
|
|
17
|
+
flex: initial;
|
|
18
|
+
opacity: inherit;
|
|
19
|
+
width: 36px;
|
|
20
|
+
height: ${AVATAR_HEIGHT}px;
|
|
21
|
+
|
|
22
|
+
> span {
|
|
23
|
+
width: 24px;
|
|
24
|
+
height: 24px;
|
|
25
|
+
padding: ${"var(--ds-space-075, 6px)"};
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
export const nameSectionStyle = css`
|
|
29
|
+
flex: 1;
|
|
30
|
+
min-width: 0;
|
|
31
|
+
margin-left: 14px;
|
|
32
|
+
color: ${`var(--ds-text-subtle, ${N300})`};
|
|
33
|
+
opacity: inherit;
|
|
34
|
+
`;
|
|
35
|
+
export const mentionItemStyle = css`
|
|
36
|
+
background-color: transparent;
|
|
37
|
+
display: block;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
list-style-type: none;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
`;
|
|
42
|
+
export const mentionItemSelectedStyle = css`
|
|
43
|
+
background-color: ${`var(--ds-background-neutral-subtle-hovered, ${N30})`};
|
|
44
|
+
`;
|
|
45
|
+
export const capitalizedStyle = css`
|
|
46
|
+
text-transform: capitalize;
|
|
47
|
+
`;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { PureComponent } from 'react';
|
|
5
|
+
import { ProviderFactory, WithProviders } from '@atlaskit/editor-common/provider-factory';
|
|
6
|
+
import { browser } from '@atlaskit/editor-common/utils';
|
|
7
|
+
import { ResourcedMention } from '@atlaskit/mention/element';
|
|
8
|
+
// Workaround for a firefox issue where dom selection is off sync
|
|
9
|
+
// https://product-fabric.atlassian.net/browse/ED-12442
|
|
10
|
+
const refreshBrowserSelection = () => {
|
|
11
|
+
const domSelection = window.getSelection();
|
|
12
|
+
if (domSelection) {
|
|
13
|
+
const domRange = domSelection && domSelection.rangeCount === 1 && domSelection.getRangeAt(0).cloneRange();
|
|
14
|
+
if (domRange) {
|
|
15
|
+
domSelection.removeAllRanges();
|
|
16
|
+
domSelection.addRange(domRange);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export default class Mention extends PureComponent {
|
|
21
|
+
constructor(props) {
|
|
22
|
+
super(props);
|
|
23
|
+
_defineProperty(this, "renderWithProvider", providers => {
|
|
24
|
+
const {
|
|
25
|
+
accessLevel,
|
|
26
|
+
eventHandlers,
|
|
27
|
+
id,
|
|
28
|
+
text
|
|
29
|
+
} = this.props;
|
|
30
|
+
const {
|
|
31
|
+
mentionProvider
|
|
32
|
+
} = providers;
|
|
33
|
+
const actionHandlers = {};
|
|
34
|
+
['onClick', 'onMouseEnter', 'onMouseLeave'].forEach(handler => {
|
|
35
|
+
actionHandlers[handler] = eventHandlers && eventHandlers[handler] || (() => {});
|
|
36
|
+
});
|
|
37
|
+
return /*#__PURE__*/React.createElement(ResourcedMention, _extends({
|
|
38
|
+
id: id,
|
|
39
|
+
text: text,
|
|
40
|
+
accessLevel: accessLevel,
|
|
41
|
+
mentionProvider: mentionProvider
|
|
42
|
+
}, actionHandlers));
|
|
43
|
+
});
|
|
44
|
+
this.providerFactory = props.providers || new ProviderFactory();
|
|
45
|
+
}
|
|
46
|
+
componentDidMount() {
|
|
47
|
+
// Workaround an issue where the selection is not updated immediately after adding
|
|
48
|
+
// a mention when "sanitizePrivateContent" is enabled in the editor on safari.
|
|
49
|
+
// This affects both insertion and paste behaviour it is applied to the component.
|
|
50
|
+
// https://product-fabric.atlassian.net/browse/ED-14859
|
|
51
|
+
if (browser.safari) {
|
|
52
|
+
setTimeout(refreshBrowserSelection, 0);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
componentWillUnmount() {
|
|
56
|
+
if (!this.props.providers) {
|
|
57
|
+
// new ProviderFactory is created if no `providers` has been set
|
|
58
|
+
// in this case when component is unmounted it's safe to destroy this providerFactory
|
|
59
|
+
this.providerFactory.destroy();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
render() {
|
|
63
|
+
return /*#__PURE__*/React.createElement(WithProviders, {
|
|
64
|
+
providers: ['mentionProvider', 'profilecardProvider'],
|
|
65
|
+
providerFactory: this.providerFactory,
|
|
66
|
+
renderNode: this.renderWithProvider
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
_defineProperty(Mention, "displayName", 'Mention');
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import React, { PureComponent } from 'react';
|
|
3
|
+
import { injectIntl } from 'react-intl-next';
|
|
4
|
+
import { TOOLBAR_BUTTON, ToolbarButton } from '@atlaskit/editor-common/ui-menu';
|
|
5
|
+
import MentionIcon from '@atlaskit/icon/glyph/editor/mention';
|
|
6
|
+
import { messages } from '../../messages';
|
|
7
|
+
class ToolbarMention extends PureComponent {
|
|
8
|
+
constructor(...args) {
|
|
9
|
+
super(...args);
|
|
10
|
+
_defineProperty(this, "handleInsertMention", () => {
|
|
11
|
+
if (!this.props.editorView) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
this.props.onInsertMention();
|
|
15
|
+
return true;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
render() {
|
|
19
|
+
const mentionStringTranslated = this.props.intl.formatMessage(messages.mentionsIconLabel);
|
|
20
|
+
return /*#__PURE__*/React.createElement(ToolbarButton, {
|
|
21
|
+
testId: this.props.testId,
|
|
22
|
+
buttonId: TOOLBAR_BUTTON.MENTION,
|
|
23
|
+
spacing: "none",
|
|
24
|
+
onClick: this.handleInsertMention,
|
|
25
|
+
disabled: this.props.isDisabled,
|
|
26
|
+
title: mentionStringTranslated + '@',
|
|
27
|
+
iconBefore: /*#__PURE__*/React.createElement(MentionIcon, {
|
|
28
|
+
label: mentionStringTranslated
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export default injectIntl(ToolbarMention);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { INVITE_ITEM_DESCRIPTION } from './ui/InviteItem';
|
|
2
|
+
export const isTeamType = userType => userType === 'TEAM';
|
|
3
|
+
export const isTeamStats = stat => stat && !isNaN(stat.teamMentionDuration);
|
|
4
|
+
export const isInviteItem = mention => mention && mention.id === INVITE_ITEM_DESCRIPTION.id;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Actions
|
|
8
|
+
*/
|
|
9
|
+
export const shouldKeepInviteItem = (query, firstQueryWithoutResults) => {
|
|
10
|
+
if (!firstQueryWithoutResults) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
let lastIndexWithResults = firstQueryWithoutResults.length - 1;
|
|
14
|
+
let suffix = query.slice(lastIndexWithResults);
|
|
15
|
+
if (query[lastIndexWithResults - 1] === ' ') {
|
|
16
|
+
suffix = ' ' + suffix;
|
|
17
|
+
}
|
|
18
|
+
const depletedExtraWords = /\s[^\s]+\s/.test(suffix);
|
|
19
|
+
return !depletedExtraWords;
|
|
20
|
+
};
|