@solidxai/core-ui 0.1.13-beta.1 → 0.1.13-beta.3

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 (94) hide show
  1. package/dist/components/common/SolidFieldTooltip.d.ts.map +1 -1
  2. package/dist/components/common/SolidFieldTooltip.js +33 -1
  3. package/dist/components/common/SolidFieldTooltip.js.map +1 -1
  4. package/dist/components/common/SolidFieldTooltip.tsx +64 -8
  5. package/dist/components/core/chatter/SolidChatter.d.ts.map +1 -1
  6. package/dist/components/core/chatter/SolidChatter.js +4 -2
  7. package/dist/components/core/chatter/SolidChatter.js.map +1 -1
  8. package/dist/components/core/chatter/SolidChatter.tsx +4 -1
  9. package/dist/components/core/chatter/SolidChatterMentionMenu.d.ts +9 -0
  10. package/dist/components/core/chatter/SolidChatterMentionMenu.d.ts.map +1 -0
  11. package/dist/components/core/chatter/SolidChatterMentionMenu.js +18 -0
  12. package/dist/components/core/chatter/SolidChatterMentionMenu.js.map +1 -0
  13. package/dist/components/core/chatter/SolidChatterMentionMenu.tsx +45 -0
  14. package/dist/components/core/chatter/SolidChatterMessageBox.d.ts +1 -0
  15. package/dist/components/core/chatter/SolidChatterMessageBox.d.ts.map +1 -1
  16. package/dist/components/core/chatter/SolidChatterMessageBox.js +29 -5
  17. package/dist/components/core/chatter/SolidChatterMessageBox.js.map +1 -1
  18. package/dist/components/core/chatter/SolidChatterMessageBox.tsx +57 -4
  19. package/dist/components/core/chatter/SolidMessageComposer.d.ts.map +1 -1
  20. package/dist/components/core/chatter/SolidMessageComposer.js +33 -7
  21. package/dist/components/core/chatter/SolidMessageComposer.js.map +1 -1
  22. package/dist/components/core/chatter/SolidMessageComposer.tsx +61 -7
  23. package/dist/components/core/chatter/chatter.module.css +63 -0
  24. package/dist/components/core/chatter/chatterMentions.d.ts +17 -0
  25. package/dist/components/core/chatter/chatterMentions.d.ts.map +1 -0
  26. package/dist/components/core/chatter/chatterMentions.js +50 -0
  27. package/dist/components/core/chatter/chatterMentions.js.map +1 -0
  28. package/dist/components/core/chatter/chatterMentions.ts +59 -0
  29. package/dist/components/core/chatter/useChatterMentions.d.ts +26 -0
  30. package/dist/components/core/chatter/useChatterMentions.d.ts.map +1 -0
  31. package/dist/components/core/chatter/useChatterMentions.js +204 -0
  32. package/dist/components/core/chatter/useChatterMentions.js.map +1 -0
  33. package/dist/components/core/chatter/useChatterMentions.ts +160 -0
  34. package/dist/components/core/form/fields/SolidMediaMultipleField.d.ts.map +1 -1
  35. package/dist/components/core/form/fields/SolidMediaMultipleField.js +111 -106
  36. package/dist/components/core/form/fields/SolidMediaMultipleField.js.map +1 -1
  37. package/dist/components/core/form/fields/SolidMediaMultipleField.tsx +165 -134
  38. package/dist/components/core/form/fields/SolidMediaSingleField.d.ts.map +1 -1
  39. package/dist/components/core/form/fields/SolidMediaSingleField.js +81 -84
  40. package/dist/components/core/form/fields/SolidMediaSingleField.js.map +1 -1
  41. package/dist/components/core/form/fields/SolidMediaSingleField.tsx +97 -86
  42. package/dist/components/core/form/fields/relations/SolidRelationManyToOneField.d.ts +1 -1
  43. package/dist/components/core/form/fields/relations/SolidRelationManyToOneField.d.ts.map +1 -1
  44. package/dist/components/core/form/fields/relations/SolidRelationManyToOneField.js +4 -0
  45. package/dist/components/core/form/fields/relations/SolidRelationManyToOneField.js.map +1 -1
  46. package/dist/components/core/form/fields/relations/SolidRelationManyToOneField.tsx +6 -0
  47. package/dist/components/core/list/SolidListView.d.ts.map +1 -1
  48. package/dist/components/core/list/SolidListView.js +4 -3
  49. package/dist/components/core/list/SolidListView.js.map +1 -1
  50. package/dist/components/core/list/SolidListView.tsx +4 -3
  51. package/dist/components/core/list/columns/SolidMediaMultipleColumn.d.ts.map +1 -1
  52. package/dist/components/core/list/columns/SolidMediaMultipleColumn.js +42 -81
  53. package/dist/components/core/list/columns/SolidMediaMultipleColumn.js.map +1 -1
  54. package/dist/components/core/list/columns/SolidMediaMultipleColumn.tsx +90 -120
  55. package/dist/components/core/list/columns/SolidMediaSingleColumn.d.ts.map +1 -1
  56. package/dist/components/core/list/columns/SolidMediaSingleColumn.js +34 -42
  57. package/dist/components/core/list/columns/SolidMediaSingleColumn.js.map +1 -1
  58. package/dist/components/core/list/columns/SolidMediaSingleColumn.tsx +51 -46
  59. package/dist/components/core/model/CreateModel.d.ts.map +1 -1
  60. package/dist/components/core/model/CreateModel.js +3 -2
  61. package/dist/components/core/model/CreateModel.js.map +1 -1
  62. package/dist/components/core/model/CreateModel.tsx +4 -1
  63. package/dist/components/core/model/FieldMetaData.d.ts +1 -1
  64. package/dist/components/core/model/FieldMetaData.d.ts.map +1 -1
  65. package/dist/components/core/model/FieldMetaData.js +17 -17
  66. package/dist/components/core/model/FieldMetaData.js.map +1 -1
  67. package/dist/components/core/model/FieldMetaData.tsx +10 -2
  68. package/dist/components/shad-cn-ui/SolidTextarea.d.ts +1 -1
  69. package/dist/components/shad-cn-ui/SolidTextarea.d.ts.map +1 -1
  70. package/dist/components/shad-cn-ui/SolidTextarea.js +4 -3
  71. package/dist/components/shad-cn-ui/SolidTextarea.js.map +1 -1
  72. package/dist/components/shad-cn-ui/SolidTextarea.tsx +4 -2
  73. package/dist/helpers/downloadMediaFile.d.ts.map +1 -1
  74. package/dist/helpers/downloadMediaFile.js +10 -3
  75. package/dist/helpers/downloadMediaFile.js.map +1 -1
  76. package/dist/helpers/downloadMediaFile.tsx +13 -7
  77. package/dist/helpers/fileTypeIcon.d.ts +3 -2
  78. package/dist/helpers/fileTypeIcon.d.ts.map +1 -1
  79. package/dist/helpers/fileTypeIcon.js +11 -7
  80. package/dist/helpers/fileTypeIcon.js.map +1 -1
  81. package/dist/helpers/fileTypeIcon.tsx +12 -7
  82. package/dist/helpers/mediaType.d.ts +7 -0
  83. package/dist/helpers/mediaType.d.ts.map +1 -1
  84. package/dist/helpers/mediaType.js +39 -8
  85. package/dist/helpers/mediaType.js.map +1 -1
  86. package/dist/helpers/mediaType.ts +46 -7
  87. package/dist/helpers/mediaUrl.d.ts +2 -0
  88. package/dist/helpers/mediaUrl.d.ts.map +1 -0
  89. package/dist/helpers/mediaUrl.js +10 -0
  90. package/dist/helpers/mediaUrl.js.map +1 -0
  91. package/dist/helpers/mediaUrl.ts +11 -0
  92. package/dist/resources/globals.css +14 -0
  93. package/dist/resources/solid-tailwind-generated.css +1 -1
  94. package/package.json +1 -1
@@ -4,6 +4,9 @@ import { useEffect, useState, useRef } from 'react'
4
4
  import { ERROR_MESSAGES } from '../../../constants/error-messages'
5
5
  import { SolidButton, SolidTextarea } from '../../shad-cn-ui'
6
6
  import { FileText, Paperclip, X } from 'lucide-react'
7
+ import { buildMessageBodyWithMentionTokens } from './chatterMentions'
8
+ import { useChatterMentions } from './useChatterMentions'
9
+ import { SolidChatterMentionMenu } from './SolidChatterMentionMenu'
7
10
 
8
11
  interface SolidMessageComposerProps {
9
12
  type?: string;
@@ -19,9 +22,27 @@ export const SolidMessageComposer = ({ type, modelSingularName, refetch, id, onC
19
22
  const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
20
23
 
21
24
  const fileInputRef = useRef<HTMLInputElement>(null);
25
+ const textareaRef = useRef<HTMLTextAreaElement | null>(null);
22
26
 
23
27
  // const { data: viewLayoutData } = useGetSolidViewLayoutQuery(null);
24
28
  const [postChatterMessage, { isLoading, isSuccess }] = usePostChatterMessageMutation();
29
+ const {
30
+ mentions,
31
+ mentionRange,
32
+ mentionSuggestions,
33
+ activeMentionIndex,
34
+ isFetchingMentionUsers,
35
+ handleMentionTextChange,
36
+ handleMentionKeyDown,
37
+ handleMentionKeyUp,
38
+ handleMentionClick,
39
+ handleSelectMention,
40
+ resetMentions,
41
+ } = useChatterMentions({
42
+ value: message,
43
+ onChange: setMessage,
44
+ textareaRef,
45
+ });
25
46
 
26
47
  const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
27
48
  if (event.target.files) {
@@ -35,11 +56,15 @@ export const SolidMessageComposer = ({ type, modelSingularName, refetch, id, onC
35
56
  if (!message.trim() && selectedFiles.length === 0) return;
36
57
 
37
58
  try {
59
+ const messageBody = buildMessageBodyWithMentionTokens(message, mentions);
38
60
  const formData = new FormData();
39
61
  formData.append('messageSubType', "note");
40
- formData.append('messageBody', message);
62
+ formData.append('messageBody', messageBody.body);
41
63
  formData.append('coModelEntityId', id);
42
64
  formData.append('coModelName', modelSingularName);
65
+ if (messageBody.mentions.length > 0) {
66
+ formData.append('messageBodyMentions', JSON.stringify(messageBody.mentions));
67
+ }
43
68
 
44
69
  if (modelUserKey) formData.append('modelUserKey', modelUserKey);
45
70
  selectedFiles.forEach((file) => {
@@ -49,12 +74,30 @@ export const SolidMessageComposer = ({ type, modelSingularName, refetch, id, onC
49
74
  await postChatterMessage(formData).unwrap();
50
75
  setMessage('');
51
76
  setSelectedFiles([]);
77
+ resetMentions();
52
78
  onCancel?.();
53
79
  } catch (error) {
54
80
  console.error(ERROR_MESSAGES.FETCHING_MESSAGE, error);
55
81
  }
56
82
  };
57
83
 
84
+ const handleComposerKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
85
+ handleMentionKeyDown(event);
86
+ if (event.defaultPrevented) return;
87
+
88
+ if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
89
+ event.preventDefault();
90
+ event.currentTarget.form?.requestSubmit();
91
+ }
92
+ };
93
+
94
+ const resetComposer = () => {
95
+ setMessage('');
96
+ setSelectedFiles([]);
97
+ resetMentions();
98
+ onCancel?.();
99
+ };
100
+
58
101
  useEffect(() => {
59
102
  if (isSuccess) {
60
103
  refetch()
@@ -98,12 +141,27 @@ export const SolidMessageComposer = ({ type, modelSingularName, refetch, id, onC
98
141
  </p>
99
142
  </div>
100
143
  <SolidTextarea
144
+ ref={textareaRef}
101
145
  value={message}
102
- onChange={(e) => setMessage(e.target.value)}
146
+ onChange={handleMentionTextChange}
147
+ onKeyDown={handleComposerKeyDown}
148
+ onKeyUp={handleMentionKeyUp}
149
+ onClick={handleMentionClick}
103
150
  placeholder={type === 'email' ? 'Send a message to followers' : 'Log an internal note.'}
104
151
  className="w-full p-2"
105
152
  rows={4}
106
153
  />
154
+ <p className={styles.chatterComposerHelp}>
155
+ Type @ to mention a user. Press Ctrl+Enter to save.
156
+ </p>
157
+ {mentionRange && (
158
+ <SolidChatterMentionMenu
159
+ isLoading={isFetchingMentionUsers}
160
+ users={mentionSuggestions}
161
+ activeIndex={activeMentionIndex}
162
+ onSelect={handleSelectMention}
163
+ />
164
+ )}
107
165
  <div className='flex items-center justify-between flex-wrap gap-2'>
108
166
  <div className='flex items-center gap-2'>
109
167
  <input
@@ -140,11 +198,7 @@ export const SolidMessageComposer = ({ type, modelSingularName, refetch, id, onC
140
198
  type='button'
141
199
  size='sm'
142
200
  variant='ghost'
143
- onClick={() => {
144
- setMessage('');
145
- setSelectedFiles([]);
146
- onCancel?.();
147
- }}
201
+ onClick={resetComposer}
148
202
  >
149
203
  Cancel
150
204
  </SolidButton>
@@ -13,6 +13,69 @@
13
13
  color: color-mix(in srgb, var(--muted-foreground) 52%, transparent);
14
14
  }
15
15
 
16
+ .chatterMentionMenu {
17
+ width: min(22rem, 100%);
18
+ max-height: 13rem;
19
+ overflow-y: auto;
20
+ border: 1px solid color-mix(in srgb, var(--border) 88%, transparent);
21
+ border-radius: 0.5rem;
22
+ background: var(--popover, var(--card));
23
+ box-shadow: 0 12px 28px rgba(15, 23, 42, 0.16);
24
+ padding: 0.25rem;
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: 0.125rem;
28
+ z-index: 20;
29
+ }
30
+
31
+ .chatterMentionOption {
32
+ appearance: none;
33
+ border: 0;
34
+ background: transparent;
35
+ color: var(--foreground);
36
+ cursor: pointer;
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: 0.1rem;
40
+ padding: 0.45rem 0.55rem;
41
+ text-align: left;
42
+ border-radius: 0.35rem;
43
+ }
44
+
45
+ .chatterMentionOption:hover,
46
+ .chatterMentionOption:focus {
47
+ background: color-mix(in srgb, var(--primary-color, var(--primary)) 10%, transparent);
48
+ outline: none;
49
+ }
50
+
51
+ .chatterMentionOptionActive {
52
+ background: color-mix(in srgb, var(--primary-color, var(--primary)) 12%, transparent);
53
+ }
54
+
55
+ .chatterMentionName {
56
+ font-size: 0.78rem;
57
+ font-weight: 600;
58
+ line-height: 1.2;
59
+ }
60
+
61
+ .chatterMentionUsername,
62
+ .chatterMentionEmpty {
63
+ font-size: 0.72rem;
64
+ color: var(--muted-foreground);
65
+ }
66
+
67
+ .chatterMentionEmpty {
68
+ padding: 0.5rem 0.55rem;
69
+ }
70
+
71
+ .chatterComposerHelp {
72
+ margin: -0.2rem 0 0;
73
+ font-size: 0.68rem;
74
+ font-style: italic;
75
+ line-height: 1.3;
76
+ color: var(--muted-foreground);
77
+ }
78
+
16
79
  .chatterEmails {
17
80
  max-width: 80%;
18
81
  overflow: hidden;
@@ -0,0 +1,17 @@
1
+ export interface ChatterMention {
2
+ username: string;
3
+ display_name: string;
4
+ id: string;
5
+ }
6
+ export declare const parseMessageBodyMentions: (value: unknown) => ChatterMention[];
7
+ export declare const renderMessageBodyMentions: (messageBody: string | null | undefined, messageBodyMentions: unknown) => string | null | undefined;
8
+ export declare const buildMessageBodyWithMentionTokens: (message: string, mentions: ChatterMention[]) => {
9
+ body: string;
10
+ mentions: ChatterMention[];
11
+ };
12
+ export declare const getMentionCandidate: (value: string, cursorPosition: number) => {
13
+ query: string;
14
+ start: number;
15
+ end: number;
16
+ } | null;
17
+ //# sourceMappingURL=chatterMentions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatterMentions.d.ts","sourceRoot":"","sources":["../../../../src/components/core/chatter/chatterMentions.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,wBAAwB,UAAW,OAAO,KAAG,cAAc,EAWvE,CAAC;AAEF,eAAO,MAAM,yBAAyB,gBAAiB,MAAM,GAAG,IAAI,GAAG,SAAS,uBAAuB,OAAO,8BAO7G,CAAC;AAIF,eAAO,MAAM,iCAAiC,YAAa,MAAM,YAAY,cAAc,EAAE;;;CAe5F,CAAC;AAEF,eAAO,MAAM,mBAAmB,UAAW,MAAM,kBAAkB,MAAM;;;;QAWxE,CAAC"}
@@ -0,0 +1,50 @@
1
+ export var parseMessageBodyMentions = function (value) {
2
+ if (!value)
3
+ return [];
4
+ if (Array.isArray(value))
5
+ return value;
6
+ if (typeof value !== 'string')
7
+ return [];
8
+ try {
9
+ var parsed = JSON.parse(value);
10
+ return Array.isArray(parsed) ? parsed : [];
11
+ }
12
+ catch (_a) {
13
+ return [];
14
+ }
15
+ };
16
+ export var renderMessageBodyMentions = function (messageBody, messageBodyMentions) {
17
+ var mentions = parseMessageBodyMentions(messageBodyMentions);
18
+ if (!messageBody || mentions.length === 0)
19
+ return messageBody;
20
+ return mentions.reduce(function (nextMessage, mention, index) { return (nextMessage.replace(new RegExp("\\{\\{\\s*".concat(index, "\\s*\\}\\}"), 'g'), "@".concat(mention.username))); }, messageBody);
21
+ };
22
+ var escapeRegExp = function (value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); };
23
+ export var buildMessageBodyWithMentionTokens = function (message, mentions) {
24
+ var activeMentions = mentions.filter(function (mention, index, arr) {
25
+ return message.includes("@".concat(mention.username)) &&
26
+ arr.findIndex(function (item) { return item.username === mention.username; }) === index;
27
+ });
28
+ var body = activeMentions.reduce(function (nextMessage, mention, index) {
29
+ var mentionRegex = new RegExp("@".concat(escapeRegExp(mention.username), "\\b"), 'g');
30
+ return nextMessage.replace(mentionRegex, "{{ ".concat(index, " }}"));
31
+ }, message);
32
+ return {
33
+ body: body,
34
+ mentions: activeMentions,
35
+ };
36
+ };
37
+ export var getMentionCandidate = function (value, cursorPosition) {
38
+ var _a;
39
+ var beforeCursor = value.slice(0, cursorPosition);
40
+ var match = beforeCursor.match(/(^|\s)@([\w.]*)$/);
41
+ if (!match || match.index === undefined)
42
+ return null;
43
+ var prefixLength = ((_a = match[1]) === null || _a === void 0 ? void 0 : _a.length) || 0;
44
+ return {
45
+ query: match[2] || '',
46
+ start: match.index + prefixLength,
47
+ end: cursorPosition,
48
+ };
49
+ };
50
+ //# sourceMappingURL=chatterMentions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatterMentions.js","sourceRoot":"","sources":["../../../../src/components/core/chatter/chatterMentions.ts"],"names":[],"mappings":"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","sourcesContent":["export interface ChatterMention {\n username: string;\n display_name: string;\n id: string;\n}\n\nexport const parseMessageBodyMentions = (value: unknown): ChatterMention[] => {\n if (!value) return [];\n if (Array.isArray(value)) return value as ChatterMention[];\n if (typeof value !== 'string') return [];\n\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n};\n\nexport const renderMessageBodyMentions = (messageBody: string | null | undefined, messageBodyMentions: unknown) => {\n const mentions = parseMessageBodyMentions(messageBodyMentions);\n if (!messageBody || mentions.length === 0) return messageBody;\n\n return mentions.reduce((nextMessage, mention, index) => (\n nextMessage.replace(new RegExp(`\\\\{\\\\{\\\\s*${index}\\\\s*\\\\}\\\\}`, 'g'), `@${mention.username}`)\n ), messageBody);\n};\n\nconst escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const buildMessageBodyWithMentionTokens = (message: string, mentions: ChatterMention[]) => {\n const activeMentions = mentions.filter((mention, index, arr) =>\n message.includes(`@${mention.username}`) &&\n arr.findIndex(item => item.username === mention.username) === index\n );\n\n const body = activeMentions.reduce((nextMessage, mention, index) => {\n const mentionRegex = new RegExp(`@${escapeRegExp(mention.username)}\\\\b`, 'g');\n return nextMessage.replace(mentionRegex, `{{ ${index} }}`);\n }, message);\n\n return {\n body,\n mentions: activeMentions,\n };\n};\n\nexport const getMentionCandidate = (value: string, cursorPosition: number) => {\n const beforeCursor = value.slice(0, cursorPosition);\n const match = beforeCursor.match(/(^|\\s)@([\\w.]*)$/);\n if (!match || match.index === undefined) return null;\n\n const prefixLength = match[1]?.length || 0;\n return {\n query: match[2] || '',\n start: match.index + prefixLength,\n end: cursorPosition,\n };\n};\n"]}
@@ -0,0 +1,59 @@
1
+ export interface ChatterMention {
2
+ username: string;
3
+ display_name: string;
4
+ id: string;
5
+ }
6
+
7
+ export const parseMessageBodyMentions = (value: unknown): ChatterMention[] => {
8
+ if (!value) return [];
9
+ if (Array.isArray(value)) return value as ChatterMention[];
10
+ if (typeof value !== 'string') return [];
11
+
12
+ try {
13
+ const parsed = JSON.parse(value);
14
+ return Array.isArray(parsed) ? parsed : [];
15
+ } catch {
16
+ return [];
17
+ }
18
+ };
19
+
20
+ export const renderMessageBodyMentions = (messageBody: string | null | undefined, messageBodyMentions: unknown) => {
21
+ const mentions = parseMessageBodyMentions(messageBodyMentions);
22
+ if (!messageBody || mentions.length === 0) return messageBody;
23
+
24
+ return mentions.reduce((nextMessage, mention, index) => (
25
+ nextMessage.replace(new RegExp(`\\{\\{\\s*${index}\\s*\\}\\}`, 'g'), `@${mention.username}`)
26
+ ), messageBody);
27
+ };
28
+
29
+ const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
30
+
31
+ export const buildMessageBodyWithMentionTokens = (message: string, mentions: ChatterMention[]) => {
32
+ const activeMentions = mentions.filter((mention, index, arr) =>
33
+ message.includes(`@${mention.username}`) &&
34
+ arr.findIndex(item => item.username === mention.username) === index
35
+ );
36
+
37
+ const body = activeMentions.reduce((nextMessage, mention, index) => {
38
+ const mentionRegex = new RegExp(`@${escapeRegExp(mention.username)}\\b`, 'g');
39
+ return nextMessage.replace(mentionRegex, `{{ ${index} }}`);
40
+ }, message);
41
+
42
+ return {
43
+ body,
44
+ mentions: activeMentions,
45
+ };
46
+ };
47
+
48
+ export const getMentionCandidate = (value: string, cursorPosition: number) => {
49
+ const beforeCursor = value.slice(0, cursorPosition);
50
+ const match = beforeCursor.match(/(^|\s)@([\w.]*)$/);
51
+ if (!match || match.index === undefined) return null;
52
+
53
+ const prefixLength = match[1]?.length || 0;
54
+ return {
55
+ query: match[2] || '',
56
+ start: match.index + prefixLength,
57
+ end: cursorPosition,
58
+ };
59
+ };
@@ -0,0 +1,26 @@
1
+ import { type ChangeEvent, type KeyboardEvent, type MouseEvent, type RefObject } from 'react';
2
+ import { ChatterMention } from './chatterMentions';
3
+ interface UseChatterMentionsProps {
4
+ value: string;
5
+ onChange: (value: string) => void;
6
+ textareaRef: RefObject<HTMLTextAreaElement | null>;
7
+ }
8
+ export declare const useChatterMentions: ({ value, onChange, textareaRef }: UseChatterMentionsProps) => {
9
+ mentions: ChatterMention[];
10
+ setMentions: import("react").Dispatch<import("react").SetStateAction<ChatterMention[]>>;
11
+ mentionRange: {
12
+ start: number;
13
+ end: number;
14
+ } | null;
15
+ mentionSuggestions: any[];
16
+ activeMentionIndex: number;
17
+ isFetchingMentionUsers: boolean;
18
+ handleMentionTextChange: (event: ChangeEvent<HTMLTextAreaElement>) => void;
19
+ handleMentionKeyDown: (event: KeyboardEvent<HTMLTextAreaElement>) => void;
20
+ handleMentionKeyUp: (event: KeyboardEvent<HTMLTextAreaElement>) => void;
21
+ handleMentionClick: (event: MouseEvent<HTMLTextAreaElement>) => void;
22
+ handleSelectMention: (user: any) => void;
23
+ resetMentions: (nextMentions?: ChatterMention[]) => void;
24
+ };
25
+ export {};
26
+ //# sourceMappingURL=useChatterMentions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChatterMentions.d.ts","sourceRoot":"","sources":["../../../../src/components/core/chatter/useChatterMentions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAC;AAEnH,OAAO,EAAE,cAAc,EAAuB,MAAM,mBAAmB,CAAC;AAExE,UAAU,uBAAuB;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,EAAE,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;CACtD;AAED,eAAO,MAAM,kBAAkB,qCAAsC,uBAAuB;;;;eAG9B,MAAM;aAAO,MAAM;;;;;qCAuBrC,YAAY,mBAAmB,CAAC;kCAMnC,cAAc,mBAAmB,CAAC;gCAmCpC,cAAc,mBAAmB,CAAC;gCAMlC,WAAW,mBAAmB,CAAC;gCAI/B,GAAG;mCA4BD,cAAc,EAAE;CA4CxD,CAAC"}
@@ -0,0 +1,204 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
12
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
38
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
39
+ if (ar || !(i in from)) {
40
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
41
+ ar[i] = from[i];
42
+ }
43
+ }
44
+ return to.concat(ar || Array.prototype.slice.call(from));
45
+ };
46
+ import { useEffect, useState } from 'react';
47
+ import { useLazyGetusersQuery } from '../../../redux/api/userApi';
48
+ import { getMentionCandidate } from './chatterMentions';
49
+ export var useChatterMentions = function (_a) {
50
+ var value = _a.value, onChange = _a.onChange, textareaRef = _a.textareaRef;
51
+ var _b = useState([]), mentions = _b[0], setMentions = _b[1];
52
+ var _c = useState(''), mentionQuery = _c[0], setMentionQuery = _c[1];
53
+ var _d = useState(null), mentionRange = _d[0], setMentionRange = _d[1];
54
+ var _e = useState([]), mentionSuggestions = _e[0], setMentionSuggestions = _e[1];
55
+ var _f = useState(0), activeMentionIndex = _f[0], setActiveMentionIndex = _f[1];
56
+ var _g = useLazyGetusersQuery(), getUsers = _g[0], isFetchingMentionUsers = _g[1].isFetching;
57
+ var closeMentionMenu = function () {
58
+ setMentionRange(null);
59
+ setMentionQuery('');
60
+ setMentionSuggestions([]);
61
+ setActiveMentionIndex(0);
62
+ };
63
+ var updateMentionState = function (nextValue, cursorPosition) {
64
+ var candidate = getMentionCandidate(nextValue, cursorPosition);
65
+ if (!candidate) {
66
+ closeMentionMenu();
67
+ return;
68
+ }
69
+ setMentionRange({ start: candidate.start, end: candidate.end });
70
+ setMentionQuery(candidate.query);
71
+ };
72
+ var handleMentionTextChange = function (event) {
73
+ var _a;
74
+ var nextValue = event.target.value;
75
+ onChange(nextValue);
76
+ updateMentionState(nextValue, (_a = event.target.selectionStart) !== null && _a !== void 0 ? _a : nextValue.length);
77
+ };
78
+ var handleMentionKeyDown = function (event) {
79
+ if (!mentionRange)
80
+ return;
81
+ if (event.key === 'ArrowDown') {
82
+ event.preventDefault();
83
+ setActiveMentionIndex(function (prev) {
84
+ if (mentionSuggestions.length === 0)
85
+ return 0;
86
+ return (prev + 1) % mentionSuggestions.length;
87
+ });
88
+ return;
89
+ }
90
+ if (event.key === 'ArrowUp') {
91
+ event.preventDefault();
92
+ setActiveMentionIndex(function (prev) {
93
+ if (mentionSuggestions.length === 0)
94
+ return 0;
95
+ return (prev - 1 + mentionSuggestions.length) % mentionSuggestions.length;
96
+ });
97
+ return;
98
+ }
99
+ if (event.key === 'Enter' || event.key === 'Tab') {
100
+ if (mentionSuggestions[activeMentionIndex]) {
101
+ event.preventDefault();
102
+ handleSelectMention(mentionSuggestions[activeMentionIndex]);
103
+ }
104
+ return;
105
+ }
106
+ if (event.key === 'Escape') {
107
+ event.preventDefault();
108
+ closeMentionMenu();
109
+ }
110
+ };
111
+ var handleMentionKeyUp = function (event) {
112
+ var _a;
113
+ if (['ArrowDown', 'ArrowUp', 'Enter', 'Tab', 'Escape'].includes(event.key))
114
+ return;
115
+ var target = event.currentTarget;
116
+ updateMentionState(target.value, (_a = target.selectionStart) !== null && _a !== void 0 ? _a : target.value.length);
117
+ };
118
+ var handleMentionClick = function (event) {
119
+ var _a;
120
+ updateMentionState(event.currentTarget.value, (_a = event.currentTarget.selectionStart) !== null && _a !== void 0 ? _a : event.currentTarget.value.length);
121
+ };
122
+ var handleSelectMention = function (user) {
123
+ if (!mentionRange)
124
+ return;
125
+ var username = user === null || user === void 0 ? void 0 : user.username;
126
+ if (!username)
127
+ return;
128
+ var mention = {
129
+ username: username,
130
+ display_name: (user === null || user === void 0 ? void 0 : user.fullName) || username,
131
+ id: String(user === null || user === void 0 ? void 0 : user.id),
132
+ };
133
+ var mentionText = "@".concat(mention.username);
134
+ var nextValue = "".concat(value.slice(0, mentionRange.start)).concat(mentionText, " ").concat(value.slice(mentionRange.end));
135
+ var nextCursorPosition = mentionRange.start + mentionText.length + 1;
136
+ onChange(nextValue);
137
+ setMentions(function (prev) { return (prev.some(function (item) { return item.id === mention.id || item.username === mention.username; })
138
+ ? prev
139
+ : __spreadArray(__spreadArray([], prev, true), [mention], false)); });
140
+ closeMentionMenu();
141
+ window.requestAnimationFrame(function () {
142
+ var _a, _b;
143
+ (_a = textareaRef.current) === null || _a === void 0 ? void 0 : _a.focus();
144
+ (_b = textareaRef.current) === null || _b === void 0 ? void 0 : _b.setSelectionRange(nextCursorPosition, nextCursorPosition);
145
+ });
146
+ };
147
+ var resetMentions = function (nextMentions) {
148
+ if (nextMentions === void 0) { nextMentions = []; }
149
+ setMentions(nextMentions);
150
+ closeMentionMenu();
151
+ };
152
+ useEffect(function () {
153
+ if (!mentionRange)
154
+ return;
155
+ var isActive = true;
156
+ var timeout = window.setTimeout(function () { return __awaiter(void 0, void 0, void 0, function () {
157
+ var encodedQuery, queryString, response, error_1;
158
+ var _a;
159
+ return __generator(this, function (_b) {
160
+ switch (_b.label) {
161
+ case 0:
162
+ _b.trys.push([0, 2, , 3]);
163
+ encodedQuery = encodeURIComponent(mentionQuery);
164
+ queryString = mentionQuery
165
+ ? "filters[username][$containsi]=".concat(encodedQuery, "&limit=8")
166
+ : 'limit=8';
167
+ return [4 /*yield*/, getUsers(queryString).unwrap()];
168
+ case 1:
169
+ response = _b.sent();
170
+ if (!isActive)
171
+ return [2 /*return*/];
172
+ setMentionSuggestions(((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.records) || []);
173
+ setActiveMentionIndex(0);
174
+ return [3 /*break*/, 3];
175
+ case 2:
176
+ error_1 = _b.sent();
177
+ if (isActive)
178
+ setMentionSuggestions([]);
179
+ return [3 /*break*/, 3];
180
+ case 3: return [2 /*return*/];
181
+ }
182
+ });
183
+ }); }, 180);
184
+ return function () {
185
+ isActive = false;
186
+ window.clearTimeout(timeout);
187
+ };
188
+ }, [mentionQuery, mentionRange, getUsers]);
189
+ return {
190
+ mentions: mentions,
191
+ setMentions: setMentions,
192
+ mentionRange: mentionRange,
193
+ mentionSuggestions: mentionSuggestions,
194
+ activeMentionIndex: activeMentionIndex,
195
+ isFetchingMentionUsers: isFetchingMentionUsers,
196
+ handleMentionTextChange: handleMentionTextChange,
197
+ handleMentionKeyDown: handleMentionKeyDown,
198
+ handleMentionKeyUp: handleMentionKeyUp,
199
+ handleMentionClick: handleMentionClick,
200
+ handleSelectMention: handleSelectMention,
201
+ resetMentions: resetMentions,
202
+ };
203
+ };
204
+ //# sourceMappingURL=useChatterMentions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChatterMentions.js","sourceRoot":"","sources":["../../../../src/components/core/chatter/useChatterMentions.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChangeEvent, type KeyboardEvent, type MouseEvent, type RefObject, useEffect, useState } from 'react';\nimport { useLazyGetusersQuery } from '../../../redux/api/userApi';\nimport { ChatterMention, getMentionCandidate } from './chatterMentions';\n\ninterface UseChatterMentionsProps {\n value: string;\n onChange: (value: string) => void;\n textareaRef: RefObject<HTMLTextAreaElement | null>;\n}\n\nexport const useChatterMentions = ({ value, onChange, textareaRef }: UseChatterMentionsProps) => {\n const [mentions, setMentions] = useState<ChatterMention[]>([]);\n const [mentionQuery, setMentionQuery] = useState('');\n const [mentionRange, setMentionRange] = useState<{ start: number; end: number } | null>(null);\n const [mentionSuggestions, setMentionSuggestions] = useState<any[]>([]);\n const [activeMentionIndex, setActiveMentionIndex] = useState(0);\n const [getUsers, { isFetching: isFetchingMentionUsers }] = useLazyGetusersQuery();\n\n const closeMentionMenu = () => {\n setMentionRange(null);\n setMentionQuery('');\n setMentionSuggestions([]);\n setActiveMentionIndex(0);\n };\n\n const updateMentionState = (nextValue: string, cursorPosition: number) => {\n const candidate = getMentionCandidate(nextValue, cursorPosition);\n if (!candidate) {\n closeMentionMenu();\n return;\n }\n\n setMentionRange({ start: candidate.start, end: candidate.end });\n setMentionQuery(candidate.query);\n };\n\n const handleMentionTextChange = (event: ChangeEvent<HTMLTextAreaElement>) => {\n const nextValue = event.target.value;\n onChange(nextValue);\n updateMentionState(nextValue, event.target.selectionStart ?? nextValue.length);\n };\n\n const handleMentionKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (!mentionRange) return;\n\n if (event.key === 'ArrowDown') {\n event.preventDefault();\n setActiveMentionIndex(prev => {\n if (mentionSuggestions.length === 0) return 0;\n return (prev + 1) % mentionSuggestions.length;\n });\n return;\n }\n\n if (event.key === 'ArrowUp') {\n event.preventDefault();\n setActiveMentionIndex(prev => {\n if (mentionSuggestions.length === 0) return 0;\n return (prev - 1 + mentionSuggestions.length) % mentionSuggestions.length;\n });\n return;\n }\n\n if (event.key === 'Enter' || event.key === 'Tab') {\n if (mentionSuggestions[activeMentionIndex]) {\n event.preventDefault();\n handleSelectMention(mentionSuggestions[activeMentionIndex]);\n }\n return;\n }\n\n if (event.key === 'Escape') {\n event.preventDefault();\n closeMentionMenu();\n }\n };\n\n const handleMentionKeyUp = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (['ArrowDown', 'ArrowUp', 'Enter', 'Tab', 'Escape'].includes(event.key)) return;\n const target = event.currentTarget;\n updateMentionState(target.value, target.selectionStart ?? target.value.length);\n };\n\n const handleMentionClick = (event: MouseEvent<HTMLTextAreaElement>) => {\n updateMentionState(event.currentTarget.value, event.currentTarget.selectionStart ?? event.currentTarget.value.length);\n };\n\n const handleSelectMention = (user: any) => {\n if (!mentionRange) return;\n const username = user?.username;\n if (!username) return;\n\n const mention: ChatterMention = {\n username,\n display_name: user?.fullName || username,\n id: String(user?.id),\n };\n const mentionText = `@${mention.username}`;\n const nextValue = `${value.slice(0, mentionRange.start)}${mentionText} ${value.slice(mentionRange.end)}`;\n const nextCursorPosition = mentionRange.start + mentionText.length + 1;\n\n onChange(nextValue);\n setMentions(prev => (\n prev.some(item => item.id === mention.id || item.username === mention.username)\n ? prev\n : [...prev, mention]\n ));\n closeMentionMenu();\n\n window.requestAnimationFrame(() => {\n textareaRef.current?.focus();\n textareaRef.current?.setSelectionRange(nextCursorPosition, nextCursorPosition);\n });\n };\n\n const resetMentions = (nextMentions: ChatterMention[] = []) => {\n setMentions(nextMentions);\n closeMentionMenu();\n };\n\n useEffect(() => {\n if (!mentionRange) return;\n\n let isActive = true;\n const timeout = window.setTimeout(async () => {\n try {\n const encodedQuery = encodeURIComponent(mentionQuery);\n const queryString = mentionQuery\n ? `filters[username][$containsi]=${encodedQuery}&limit=8`\n : 'limit=8';\n const response = await getUsers(queryString).unwrap();\n if (!isActive) return;\n setMentionSuggestions(response?.data?.records || []);\n setActiveMentionIndex(0);\n } catch (error) {\n if (isActive) setMentionSuggestions([]);\n }\n }, 180);\n\n return () => {\n isActive = false;\n window.clearTimeout(timeout);\n };\n }, [mentionQuery, mentionRange, getUsers]);\n\n return {\n mentions,\n setMentions,\n mentionRange,\n mentionSuggestions,\n activeMentionIndex,\n isFetchingMentionUsers,\n handleMentionTextChange,\n handleMentionKeyDown,\n handleMentionKeyUp,\n handleMentionClick,\n handleSelectMention,\n resetMentions,\n };\n};\n"]}