@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.
Files changed (82) hide show
  1. package/.eslintrc.js +7 -0
  2. package/CHANGELOG.md +1 -0
  3. package/LICENSE.md +13 -0
  4. package/README.md +9 -0
  5. package/dist/cjs/analytics.js +157 -0
  6. package/dist/cjs/index.js +12 -0
  7. package/dist/cjs/messages.js +29 -0
  8. package/dist/cjs/nodeviews/mention.js +117 -0
  9. package/dist/cjs/plugin.js +135 -0
  10. package/dist/cjs/pm-plugins/key.js +8 -0
  11. package/dist/cjs/pm-plugins/main.js +156 -0
  12. package/dist/cjs/pm-plugins/utils.js +23 -0
  13. package/dist/cjs/type-ahead/index.js +362 -0
  14. package/dist/cjs/types.js +5 -0
  15. package/dist/cjs/ui/InviteItem/index.js +76 -0
  16. package/dist/cjs/ui/InviteItem/styles.js +19 -0
  17. package/dist/cjs/ui/Mention/index.js +98 -0
  18. package/dist/cjs/ui/ToolbarMention/index.js +63 -0
  19. package/dist/cjs/utils.js +32 -0
  20. package/dist/es2019/analytics.js +147 -0
  21. package/dist/es2019/index.js +1 -0
  22. package/dist/es2019/messages.js +23 -0
  23. package/dist/es2019/nodeviews/mention.js +80 -0
  24. package/dist/es2019/plugin.js +123 -0
  25. package/dist/es2019/pm-plugins/key.js +2 -0
  26. package/dist/es2019/pm-plugins/main.js +143 -0
  27. package/dist/es2019/pm-plugins/utils.js +14 -0
  28. package/dist/es2019/type-ahead/index.js +338 -0
  29. package/dist/es2019/types.js +1 -0
  30. package/dist/es2019/ui/InviteItem/index.js +67 -0
  31. package/dist/es2019/ui/InviteItem/styles.js +47 -0
  32. package/dist/es2019/ui/Mention/index.js +70 -0
  33. package/dist/es2019/ui/ToolbarMention/index.js +33 -0
  34. package/dist/es2019/utils.js +20 -0
  35. package/dist/esm/analytics.js +150 -0
  36. package/dist/esm/index.js +1 -0
  37. package/dist/esm/messages.js +23 -0
  38. package/dist/esm/nodeviews/mention.js +107 -0
  39. package/dist/esm/plugin.js +129 -0
  40. package/dist/esm/pm-plugins/key.js +2 -0
  41. package/dist/esm/pm-plugins/main.js +148 -0
  42. package/dist/esm/pm-plugins/utils.js +16 -0
  43. package/dist/esm/type-ahead/index.js +350 -0
  44. package/dist/esm/types.js +1 -0
  45. package/dist/esm/ui/InviteItem/index.js +66 -0
  46. package/dist/esm/ui/InviteItem/styles.js +12 -0
  47. package/dist/esm/ui/Mention/index.js +90 -0
  48. package/dist/esm/ui/ToolbarMention/index.js +53 -0
  49. package/dist/esm/utils.js +26 -0
  50. package/dist/types/analytics.d.ts +13 -0
  51. package/dist/types/index.d.ts +2 -0
  52. package/dist/types/messages.d.ts +22 -0
  53. package/dist/types/nodeviews/mention.d.ts +9 -0
  54. package/dist/types/plugin.d.ts +3 -0
  55. package/dist/types/pm-plugins/key.d.ts +3 -0
  56. package/dist/types/pm-plugins/main.d.ts +6 -0
  57. package/dist/types/pm-plugins/utils.d.ts +4 -0
  58. package/dist/types/type-ahead/index.d.ts +17 -0
  59. package/dist/types/types.d.ts +38 -0
  60. package/dist/types/ui/InviteItem/index.d.ts +24 -0
  61. package/dist/types/ui/InviteItem/styles.d.ts +8 -0
  62. package/dist/types/ui/Mention/index.d.ts +19 -0
  63. package/dist/types/ui/ToolbarMention/index.d.ts +13 -0
  64. package/dist/types/utils.d.ts +8 -0
  65. package/dist/types-ts4.5/analytics.d.ts +13 -0
  66. package/dist/types-ts4.5/index.d.ts +2 -0
  67. package/dist/types-ts4.5/messages.d.ts +22 -0
  68. package/dist/types-ts4.5/nodeviews/mention.d.ts +9 -0
  69. package/dist/types-ts4.5/plugin.d.ts +3 -0
  70. package/dist/types-ts4.5/pm-plugins/key.d.ts +3 -0
  71. package/dist/types-ts4.5/pm-plugins/main.d.ts +6 -0
  72. package/dist/types-ts4.5/pm-plugins/utils.d.ts +4 -0
  73. package/dist/types-ts4.5/type-ahead/index.d.ts +17 -0
  74. package/dist/types-ts4.5/types.d.ts +41 -0
  75. package/dist/types-ts4.5/ui/InviteItem/index.d.ts +24 -0
  76. package/dist/types-ts4.5/ui/InviteItem/styles.d.ts +8 -0
  77. package/dist/types-ts4.5/ui/Mention/index.d.ts +19 -0
  78. package/dist/types-ts4.5/ui/ToolbarMention/index.d.ts +13 -0
  79. package/dist/types-ts4.5/utils.d.ts +8 -0
  80. package/package.json +112 -0
  81. package/report.api.md +92 -0
  82. 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
+ };