@wakastellar/ui 3.3.3 → 3.5.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/dist/badge-BbwO7QeZ.js +1 -0
- package/dist/badge-BfiocODp.mjs +23 -0
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/chunk-14q5BKub.js +1 -0
- package/dist/{chunk-BH6uBOac.mjs → chunk-Cr9pTUWm.mjs} +5 -5
- package/dist/cn-DEtaFQsA.js +1 -0
- package/dist/cn-DUn6aSIQ.mjs +24 -0
- package/dist/doc.cjs.js +2 -2
- package/dist/doc.es.js +19 -19
- package/dist/editor.cjs.js +48 -0
- package/dist/editor.d.ts +1 -0
- package/dist/editor.es.js +6551 -0
- package/dist/{exceljs.min-DG9M8IZ1.mjs → exceljs.min-DL1XYDll.mjs} +1 -1
- package/dist/{exceljs.min-BuefmDRS.js → exceljs.min-qeIfSCbF.js} +1 -1
- package/dist/export.cjs.js +1 -1
- package/dist/export.es.js +1 -1
- package/dist/index.cjs.js +150 -150
- package/dist/index.es.js +26782 -27591
- package/dist/input-BfaSAGVw.js +1 -0
- package/dist/input-DVr_Qkl8.mjs +14 -0
- package/dist/rich-text.cjs.js +1 -1
- package/dist/rich-text.es.js +1 -1
- package/dist/security-CyBpuklN.mjs +122 -0
- package/dist/security-bFWwDrlg.js +1 -0
- package/dist/separator-NrkltulH.js +1 -0
- package/dist/separator-ibN2mycs.mjs +51 -0
- package/dist/src/components/editor/blocks/index.d.ts +51 -0
- package/dist/src/components/editor/blocks/waka-acceptance-criteria-block.d.ts +60 -0
- package/dist/src/components/editor/blocks/waka-ai-assist-block.d.ts +58 -0
- package/dist/src/components/editor/blocks/waka-api-endpoint-block.d.ts +63 -0
- package/dist/src/components/editor/blocks/waka-code-playground-block.d.ts +61 -0
- package/dist/src/components/editor/blocks/waka-comment-thread-block.d.ts +85 -0
- package/dist/src/components/editor/blocks/waka-diagram-block.d.ts +52 -0
- package/dist/src/components/editor/blocks/waka-embed-block.d.ts +58 -0
- package/dist/src/components/editor/blocks/waka-slash-menu-block.d.ts +67 -0
- package/dist/src/components/editor/blocks/waka-user-story-block.d.ts +79 -0
- package/dist/src/components/editor/blocks/waka-version-diff-block.d.ts +73 -0
- package/dist/src/components/editor/index.d.ts +66 -0
- package/dist/src/components/editor/waka-ai-writer.d.ts +80 -0
- package/dist/src/components/editor/waka-collaborative-editor.d.ts +93 -0
- package/dist/src/components/editor/waka-diff-viewer.d.ts +71 -0
- package/dist/src/components/editor/waka-dnd-editor.d.ts +64 -0
- package/dist/src/components/editor/waka-document-editor.d.ts +92 -0
- package/dist/src/components/editor/waka-editor-elements.d.ts +79 -0
- package/dist/src/components/editor/waka-editor-leaves.d.ts +39 -0
- package/dist/src/components/editor/waka-editor-plugins.d.ts +41 -0
- package/dist/src/components/editor/waka-editor-toolbar.d.ts +20 -0
- package/dist/src/components/editor/waka-editor.d.ts +59 -0
- package/dist/src/components/editor/waka-floating-toolbar.d.ts +47 -0
- package/dist/src/components/editor/waka-markdown-editor.d.ts +60 -0
- package/dist/src/components/editor/waka-mention-editor.d.ts +125 -0
- package/dist/src/components/editor/waka-slash-menu.d.ts +70 -0
- package/dist/src/components/editor/waka-spec-editor.d.ts +88 -0
- package/dist/src/components/index.d.ts +1 -15
- package/dist/src/editor.d.ts +26 -0
- package/dist/textarea-CdQWggYG.js +1 -0
- package/dist/textarea-DJDXJ3nd.mjs +23 -0
- package/dist/types-C2St0wOW.js +1 -0
- package/dist/{types-B6GVaSIP.mjs → types-JnqoLyuv.mjs} +214 -211
- package/dist/{useDataTableImport-BPvfo--2.mjs → useDataTableImport-BWUFesPi.mjs} +3 -3
- package/dist/{useDataTableImport-Cm_pCKnO.js → useDataTableImport-T7ddpN5k.js} +3 -3
- package/dist/waka-doc-renderer-CTxC7Trf.js +3 -0
- package/dist/{waka-doc-renderer-BkIvas3z.mjs → waka-doc-renderer-Cw-Xnyen.mjs} +264 -281
- package/dist/waka-editor-plugins-DR6tpsUC.mjs +135 -0
- package/dist/waka-editor-plugins-sGSh9hn2.js +1 -0
- package/dist/waka-rich-text-editor-BlIdtknG.js +1 -0
- package/dist/waka-rich-text-editor-D1uA3zbB.js +1 -0
- package/dist/waka-rich-text-editor-DgSWiXMW.mjs +342 -0
- package/dist/waka-rich-text-editor-DndVJuDw.mjs +2 -0
- package/package.json +87 -2
- package/src/blocks/footer/index.tsx +1 -6
- package/src/blocks/login/index.tsx +1 -7
- package/src/blocks/profile/index.tsx +3 -5
- package/src/components/editor/blocks/index.ts +182 -0
- package/src/components/editor/blocks/waka-acceptance-criteria-block.tsx +326 -0
- package/src/components/editor/blocks/waka-ai-assist-block.tsx +284 -0
- package/src/components/editor/blocks/waka-api-endpoint-block.tsx +382 -0
- package/src/components/editor/blocks/waka-code-playground-block.tsx +331 -0
- package/src/components/editor/blocks/waka-comment-thread-block.tsx +448 -0
- package/src/components/editor/blocks/waka-diagram-block.tsx +293 -0
- package/src/components/editor/blocks/waka-embed-block.tsx +416 -0
- package/src/components/editor/blocks/waka-slash-menu-block.tsx +432 -0
- package/src/components/editor/blocks/waka-user-story-block.tsx +295 -0
- package/src/components/editor/blocks/waka-version-diff-block.tsx +426 -0
- package/src/components/editor/index.ts +279 -0
- package/src/components/editor/waka-ai-writer.tsx +434 -0
- package/src/components/editor/waka-collaborative-editor.tsx +426 -0
- package/src/components/editor/waka-diff-viewer.tsx +352 -0
- package/src/components/editor/waka-dnd-editor.tsx +284 -0
- package/src/components/editor/waka-document-editor.tsx +502 -0
- package/src/components/editor/waka-editor-elements.tsx +312 -0
- package/src/components/editor/waka-editor-leaves.tsx +101 -0
- package/src/components/editor/waka-editor-plugins.ts +207 -0
- package/src/components/editor/waka-editor-toolbar.tsx +358 -0
- package/src/components/editor/waka-editor.tsx +431 -0
- package/src/components/editor/waka-floating-toolbar.tsx +268 -0
- package/src/components/editor/waka-markdown-editor.tsx +395 -0
- package/src/components/editor/waka-mention-editor.tsx +459 -0
- package/src/components/editor/waka-slash-menu.tsx +392 -0
- package/src/components/editor/waka-spec-editor.tsx +657 -0
- package/src/components/index.ts +1 -18
- package/dist/chunk-BDDJmn7V.js +0 -1
- package/dist/cn-DnPbmOCy.js +0 -1
- package/dist/cn-DpLcAzrf.mjs +0 -22
- package/dist/separator-BDReXBvI.mjs +0 -59
- package/dist/separator-BKjNl9sI.js +0 -1
- package/dist/src/components/waka-actor-badge/index.d.ts +0 -8
- package/dist/src/components/waka-actors-list/index.d.ts +0 -18
- package/dist/src/components/waka-ai-assistant-button/index.d.ts +0 -8
- package/dist/src/components/waka-document-flyover/index.d.ts +0 -10
- package/dist/src/components/waka-document-preview-popup/index.d.ts +0 -26
- package/dist/src/components/waka-hour-balance-badge/index.d.ts +0 -8
- package/dist/src/components/waka-hour-consumption-table/index.d.ts +0 -15
- package/dist/src/components/waka-hour-pack-dialog/index.d.ts +0 -8
- package/dist/src/components/waka-project-stats-header/index.d.ts +0 -15
- package/dist/src/components/waka-step-comment-bubble/index.d.ts +0 -13
- package/dist/src/components/waka-step-comment-panel/index.d.ts +0 -20
- package/dist/src/components/waka-step-permission-matrix/index.d.ts +0 -12
- package/dist/src/components/waka-time-entry-dialog/index.d.ts +0 -16
- package/dist/src/components/waka-time-tracking-flyover/index.d.ts +0 -11
- package/dist/types-BH9cQRqZ.js +0 -1
- package/dist/waka-doc-renderer-BZ2-SqyT.js +0 -3
- package/dist/waka-rich-text-editor-BJGlQgpq.js +0 -1
- package/dist/waka-rich-text-editor-BJzzxeP1.mjs +0 -361
- package/dist/waka-rich-text-editor-wnXLwvUo.js +0 -1
- package/src/components/waka-actor-badge/index.tsx +0 -34
- package/src/components/waka-actors-list/index.tsx +0 -125
- package/src/components/waka-ai-assistant-button/index.tsx +0 -31
- package/src/components/waka-document-flyover/index.tsx +0 -36
- package/src/components/waka-document-preview-popup/index.tsx +0 -103
- package/src/components/waka-hour-balance-badge/index.tsx +0 -43
- package/src/components/waka-hour-consumption-table/index.tsx +0 -72
- package/src/components/waka-hour-pack-dialog/index.tsx +0 -72
- package/src/components/waka-project-stats-header/index.tsx +0 -69
- package/src/components/waka-step-comment-bubble/index.tsx +0 -71
- package/src/components/waka-step-comment-panel/index.tsx +0 -106
- package/src/components/waka-step-permission-matrix/index.tsx +0 -65
- package/src/components/waka-time-entry-dialog/index.tsx +0 -131
- package/src/components/waka-time-tracking-flyover/index.tsx +0 -41
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../utils/cn"
|
|
5
|
+
import { Label } from "../label"
|
|
6
|
+
import { Textarea } from "../textarea"
|
|
7
|
+
import { Avatar, AvatarFallback, AvatarImage } from "../avatar"
|
|
8
|
+
|
|
9
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
type SlateNode = any
|
|
13
|
+
|
|
14
|
+
/** A mentionable user entity */
|
|
15
|
+
export interface MentionableUser {
|
|
16
|
+
/** Unique ID (e.g., keycloak-id or user wid) */
|
|
17
|
+
id: string
|
|
18
|
+
/** Display name */
|
|
19
|
+
name: string
|
|
20
|
+
/** Email address */
|
|
21
|
+
email?: string
|
|
22
|
+
/** Avatar URL */
|
|
23
|
+
avatar?: string
|
|
24
|
+
/** Role or title (shown in the suggestion list) */
|
|
25
|
+
role?: string
|
|
26
|
+
/** Admin level (for display badge) */
|
|
27
|
+
adminLevel?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Describes a mention that was inserted into the document */
|
|
31
|
+
export interface InsertedMention {
|
|
32
|
+
/** The user that was mentioned */
|
|
33
|
+
user: MentionableUser
|
|
34
|
+
/** The Slate path where the mention was inserted */
|
|
35
|
+
path?: number[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface WakaMentionEditorProps {
|
|
39
|
+
/** Initial Slate content */
|
|
40
|
+
value?: SlateNode[]
|
|
41
|
+
/** Change callback */
|
|
42
|
+
onChange?: (value: SlateNode[]) => void
|
|
43
|
+
/** Read-only mode */
|
|
44
|
+
readOnly?: boolean
|
|
45
|
+
/** Placeholder */
|
|
46
|
+
placeholder?: string
|
|
47
|
+
/** CSS class */
|
|
48
|
+
className?: string
|
|
49
|
+
/** Editor CSS class */
|
|
50
|
+
editorClassName?: string
|
|
51
|
+
/** Minimum height in px */
|
|
52
|
+
minHeight?: number
|
|
53
|
+
/** Label */
|
|
54
|
+
label?: string
|
|
55
|
+
/** Description */
|
|
56
|
+
description?: string
|
|
57
|
+
/** Error */
|
|
58
|
+
error?: string
|
|
59
|
+
/**
|
|
60
|
+
* Async search function that resolves mentionable users.
|
|
61
|
+
* Called when the user types after "@".
|
|
62
|
+
* Should return matching users from the WakaStart API.
|
|
63
|
+
*/
|
|
64
|
+
onSearch: (query: string) => Promise<MentionableUser[]>
|
|
65
|
+
/**
|
|
66
|
+
* Callback when a mention is inserted.
|
|
67
|
+
* Can be used to trigger notifications.
|
|
68
|
+
*/
|
|
69
|
+
onMentionInserted?: (mention: InsertedMention) => void
|
|
70
|
+
/**
|
|
71
|
+
* Trigger character for mentions. Default: "@"
|
|
72
|
+
*/
|
|
73
|
+
trigger?: string
|
|
74
|
+
/**
|
|
75
|
+
* Debounce delay for the search function in ms.
|
|
76
|
+
* Default: 300
|
|
77
|
+
*/
|
|
78
|
+
searchDebounceMs?: number
|
|
79
|
+
/**
|
|
80
|
+
* Maximum number of search results to show.
|
|
81
|
+
* Default: 8
|
|
82
|
+
*/
|
|
83
|
+
maxResults?: number
|
|
84
|
+
/** Extra Plate plugins */
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
extraPlugins?: any[]
|
|
87
|
+
/** Ref to editor instance */
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
+
editorRef?: React.MutableRefObject<any>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ─── Component ───────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* WakaMentionEditor -- Rich text editor with @mention support.
|
|
96
|
+
*
|
|
97
|
+
* Integrates `@platejs/mention` to provide user mention functionality.
|
|
98
|
+
* When the user types "@", a dropdown with matching users appears.
|
|
99
|
+
* The search function is async, allowing API-backed user resolution
|
|
100
|
+
* scoped to the current tenant (Partner > Network > Customer).
|
|
101
|
+
*
|
|
102
|
+
* When a mention is inserted, the `onMentionInserted` callback fires,
|
|
103
|
+
* allowing the app to trigger notifications via ws-back-wakastart.
|
|
104
|
+
*
|
|
105
|
+
* The mention element renders as an inline pill with the user's
|
|
106
|
+
* avatar and name, styled with design system tokens.
|
|
107
|
+
*
|
|
108
|
+
* Useful for:
|
|
109
|
+
* - Spec review comments with @author mentions
|
|
110
|
+
* - Task assignment in WakaStart projects
|
|
111
|
+
* - Chat/collaboration features
|
|
112
|
+
* - Audit notes with @reviewer references
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```tsx
|
|
116
|
+
* <WakaMentionEditor
|
|
117
|
+
* value={content}
|
|
118
|
+
* onChange={setContent}
|
|
119
|
+
* onSearch={async (q) => {
|
|
120
|
+
* const users = await usersApi.search(q);
|
|
121
|
+
* return users.map(u => ({ id: u.wid, name: u.fullName, email: u.email }));
|
|
122
|
+
* }}
|
|
123
|
+
* onMentionInserted={(m) => {
|
|
124
|
+
* notificationsApi.send(m.user.id, "You were mentioned in a document");
|
|
125
|
+
* }}
|
|
126
|
+
* />
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export const WakaMentionEditor = React.forwardRef<HTMLDivElement, WakaMentionEditorProps>(
|
|
130
|
+
(
|
|
131
|
+
{
|
|
132
|
+
value,
|
|
133
|
+
onChange,
|
|
134
|
+
readOnly = false,
|
|
135
|
+
placeholder = "Tapez @ pour mentionner un utilisateur...",
|
|
136
|
+
className,
|
|
137
|
+
editorClassName,
|
|
138
|
+
minHeight = 200,
|
|
139
|
+
label,
|
|
140
|
+
description,
|
|
141
|
+
error,
|
|
142
|
+
onSearch,
|
|
143
|
+
onMentionInserted,
|
|
144
|
+
trigger = "@",
|
|
145
|
+
searchDebounceMs = 300,
|
|
146
|
+
maxResults = 8,
|
|
147
|
+
extraPlugins,
|
|
148
|
+
editorRef,
|
|
149
|
+
},
|
|
150
|
+
ref
|
|
151
|
+
) => {
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
153
|
+
const [bundle, setBundle] = React.useState<any>(null)
|
|
154
|
+
const [loadError, setLoadError] = React.useState(false)
|
|
155
|
+
const [suggestions, setSuggestions] = React.useState<MentionableUser[]>([])
|
|
156
|
+
const [searching, setSearching] = React.useState(false)
|
|
157
|
+
const searchTimerRef = React.useRef<ReturnType<typeof setTimeout>>()
|
|
158
|
+
|
|
159
|
+
const configRef = React.useRef({ onSearch, trigger, searchDebounceMs, maxResults, onMentionInserted })
|
|
160
|
+
configRef.current = { onSearch, trigger, searchDebounceMs, maxResults, onMentionInserted }
|
|
161
|
+
|
|
162
|
+
// ── Load Plate + Mention ──────────────────────────────────────────────
|
|
163
|
+
React.useEffect(() => {
|
|
164
|
+
let cancelled = false
|
|
165
|
+
|
|
166
|
+
const load = async () => {
|
|
167
|
+
try {
|
|
168
|
+
const cfg = configRef.current
|
|
169
|
+
|
|
170
|
+
const [plateReact, basicNodes, tableModule, layoutModule, calloutModule, linkModule, mentionModule] =
|
|
171
|
+
await Promise.all([
|
|
172
|
+
import("platejs/react"),
|
|
173
|
+
import("@platejs/basic-nodes/react"),
|
|
174
|
+
import("@platejs/table/react"),
|
|
175
|
+
import("@platejs/layout/react"),
|
|
176
|
+
import("@platejs/callout/react"),
|
|
177
|
+
import("@platejs/link/react"),
|
|
178
|
+
import("@platejs/mention/react"),
|
|
179
|
+
])
|
|
180
|
+
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
|
+
const plugins: any[] = [
|
|
183
|
+
basicNodes.BoldPlugin, basicNodes.ItalicPlugin, basicNodes.UnderlinePlugin,
|
|
184
|
+
basicNodes.StrikethroughPlugin, basicNodes.CodePlugin, basicNodes.HighlightPlugin,
|
|
185
|
+
basicNodes.H1Plugin, basicNodes.H2Plugin, basicNodes.H3Plugin,
|
|
186
|
+
basicNodes.H4Plugin, basicNodes.BlockquotePlugin,
|
|
187
|
+
tableModule.TablePlugin, tableModule.TableRowPlugin,
|
|
188
|
+
tableModule.TableCellPlugin, tableModule.TableCellHeaderPlugin,
|
|
189
|
+
layoutModule.ColumnPlugin, layoutModule.ColumnItemPlugin,
|
|
190
|
+
calloutModule.CalloutPlugin,
|
|
191
|
+
linkModule.LinkPlugin.configure({
|
|
192
|
+
options: { allowedSchemes: ["http", "https", "mailto", "tel"] },
|
|
193
|
+
}),
|
|
194
|
+
// Mention plugin
|
|
195
|
+
mentionModule.MentionPlugin.configure({
|
|
196
|
+
options: {
|
|
197
|
+
trigger: cfg.trigger,
|
|
198
|
+
triggerPreviousCharPattern: /^$|^[\s"']$/,
|
|
199
|
+
insertSpaceAfterMention: true,
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
mentionModule.MentionInputPlugin,
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
if (!cancelled) {
|
|
206
|
+
setBundle({
|
|
207
|
+
Plate: plateReact.Plate,
|
|
208
|
+
PlateContent: plateReact.PlateContent,
|
|
209
|
+
usePlateEditor: plateReact.usePlateEditor,
|
|
210
|
+
plugins,
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
} catch (err) {
|
|
214
|
+
console.error("[WakaMentionEditor] Failed to load:", err)
|
|
215
|
+
if (!cancelled) setLoadError(true)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
load()
|
|
220
|
+
return () => { cancelled = true }
|
|
221
|
+
}, [])
|
|
222
|
+
|
|
223
|
+
// ── Search handler ────────────────────────────────────────────────────
|
|
224
|
+
const handleSearch = React.useCallback(
|
|
225
|
+
(query: string) => {
|
|
226
|
+
if (searchTimerRef.current) clearTimeout(searchTimerRef.current)
|
|
227
|
+
|
|
228
|
+
if (!query.trim()) {
|
|
229
|
+
setSuggestions([])
|
|
230
|
+
setSearching(false)
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
setSearching(true)
|
|
235
|
+
searchTimerRef.current = setTimeout(async () => {
|
|
236
|
+
try {
|
|
237
|
+
const cfg = configRef.current
|
|
238
|
+
const results = await cfg.onSearch(query)
|
|
239
|
+
setSuggestions(results.slice(0, cfg.maxResults))
|
|
240
|
+
} catch {
|
|
241
|
+
setSuggestions([])
|
|
242
|
+
} finally {
|
|
243
|
+
setSearching(false)
|
|
244
|
+
}
|
|
245
|
+
}, configRef.current.searchDebounceMs)
|
|
246
|
+
},
|
|
247
|
+
[]
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
// ── Render ────────────────────────────────────────────────────────────
|
|
251
|
+
if (bundle) {
|
|
252
|
+
return (
|
|
253
|
+
<MentionEditorInner
|
|
254
|
+
ref={ref}
|
|
255
|
+
bundle={bundle}
|
|
256
|
+
value={value}
|
|
257
|
+
onChange={onChange}
|
|
258
|
+
readOnly={readOnly}
|
|
259
|
+
placeholder={placeholder}
|
|
260
|
+
className={className}
|
|
261
|
+
editorClassName={editorClassName}
|
|
262
|
+
minHeight={minHeight}
|
|
263
|
+
label={label}
|
|
264
|
+
description={description}
|
|
265
|
+
error={error}
|
|
266
|
+
suggestions={suggestions}
|
|
267
|
+
searching={searching}
|
|
268
|
+
onSearch={handleSearch}
|
|
269
|
+
onMentionInserted={onMentionInserted}
|
|
270
|
+
extraPlugins={extraPlugins}
|
|
271
|
+
editorRef={editorRef}
|
|
272
|
+
/>
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<div ref={ref} className={cn("space-y-1.5", className)}>
|
|
278
|
+
{label && <Label>{label}</Label>}
|
|
279
|
+
{description && <p className="text-sm text-muted-foreground">{description}</p>}
|
|
280
|
+
<div className="relative">
|
|
281
|
+
<Textarea placeholder={placeholder} disabled style={{ minHeight }} />
|
|
282
|
+
<div className="absolute inset-0 flex items-center justify-center bg-background/50 rounded-lg">
|
|
283
|
+
<span className="text-sm text-muted-foreground animate-pulse">
|
|
284
|
+
{loadError ? "Erreur (@platejs/mention requis)" : "Chargement..."}
|
|
285
|
+
</span>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
{error && <p className="text-sm text-destructive">{error}</p>}
|
|
289
|
+
</div>
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
WakaMentionEditor.displayName = "WakaMentionEditor"
|
|
295
|
+
|
|
296
|
+
// ─── Inner component ────────────────────────────────────────────────────────
|
|
297
|
+
|
|
298
|
+
interface MentionEditorInnerProps {
|
|
299
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
300
|
+
bundle: any
|
|
301
|
+
value?: SlateNode[]
|
|
302
|
+
onChange?: (value: SlateNode[]) => void
|
|
303
|
+
readOnly?: boolean
|
|
304
|
+
placeholder?: string
|
|
305
|
+
className?: string
|
|
306
|
+
editorClassName?: string
|
|
307
|
+
minHeight?: number
|
|
308
|
+
label?: string
|
|
309
|
+
description?: string
|
|
310
|
+
error?: string
|
|
311
|
+
suggestions: MentionableUser[]
|
|
312
|
+
searching: boolean
|
|
313
|
+
onSearch: (query: string) => void
|
|
314
|
+
onMentionInserted?: (mention: InsertedMention) => void
|
|
315
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
316
|
+
extraPlugins?: any[]
|
|
317
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
318
|
+
editorRef?: React.MutableRefObject<any>
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const MentionEditorInner = React.forwardRef<HTMLDivElement, MentionEditorInnerProps>(
|
|
322
|
+
(
|
|
323
|
+
{
|
|
324
|
+
bundle: { Plate, PlateContent, usePlateEditor, plugins },
|
|
325
|
+
value,
|
|
326
|
+
onChange,
|
|
327
|
+
readOnly,
|
|
328
|
+
placeholder,
|
|
329
|
+
className,
|
|
330
|
+
editorClassName,
|
|
331
|
+
minHeight,
|
|
332
|
+
label,
|
|
333
|
+
description,
|
|
334
|
+
error,
|
|
335
|
+
suggestions,
|
|
336
|
+
searching,
|
|
337
|
+
extraPlugins,
|
|
338
|
+
editorRef,
|
|
339
|
+
},
|
|
340
|
+
ref
|
|
341
|
+
) => {
|
|
342
|
+
const allPlugins = React.useMemo(
|
|
343
|
+
() => [...plugins, ...(extraPlugins ?? [])],
|
|
344
|
+
[plugins, extraPlugins]
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
const editor = usePlateEditor({
|
|
348
|
+
plugins: allPlugins,
|
|
349
|
+
value: value ?? [{ type: "p", children: [{ text: "" }] }],
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
React.useEffect(() => {
|
|
353
|
+
if (editorRef) editorRef.current = editor
|
|
354
|
+
}, [editor, editorRef])
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<div ref={ref} className={cn("space-y-1.5", className)}>
|
|
358
|
+
{label && <Label>{label}</Label>}
|
|
359
|
+
{description && <p className="text-sm text-muted-foreground">{description}</p>}
|
|
360
|
+
|
|
361
|
+
<Plate
|
|
362
|
+
editor={editor}
|
|
363
|
+
onChange={onChange ? ({ value: v }: { value: SlateNode[] }) => onChange(v) : undefined}
|
|
364
|
+
readOnly={readOnly}
|
|
365
|
+
>
|
|
366
|
+
<div className={cn(
|
|
367
|
+
"border rounded-lg overflow-hidden relative",
|
|
368
|
+
error && "border-destructive",
|
|
369
|
+
)}>
|
|
370
|
+
<div style={{ minHeight }}>
|
|
371
|
+
<PlateContent
|
|
372
|
+
placeholder={placeholder}
|
|
373
|
+
readOnly={readOnly}
|
|
374
|
+
className={cn(
|
|
375
|
+
"prose prose-sm max-w-none p-4",
|
|
376
|
+
"focus-within:outline-none",
|
|
377
|
+
"[&_[data-slate-editor]]:outline-none [&_[data-slate-editor]]:min-h-[inherit]",
|
|
378
|
+
editorClassName,
|
|
379
|
+
)}
|
|
380
|
+
/>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
{/* Mention suggestions dropdown (rendered as overlay) */}
|
|
384
|
+
{suggestions.length > 0 && (
|
|
385
|
+
<MentionSuggestions suggestions={suggestions} searching={searching} />
|
|
386
|
+
)}
|
|
387
|
+
</div>
|
|
388
|
+
</Plate>
|
|
389
|
+
|
|
390
|
+
{error && <p className="text-sm text-destructive">{error}</p>}
|
|
391
|
+
</div>
|
|
392
|
+
)
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
MentionEditorInner.displayName = "MentionEditorInner"
|
|
397
|
+
|
|
398
|
+
// ─── Mention suggestions component ──────────────────────────────────────────
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Standalone mention suggestion list component.
|
|
402
|
+
* Can be used outside of WakaMentionEditor for custom integrations.
|
|
403
|
+
*/
|
|
404
|
+
export function MentionSuggestions({
|
|
405
|
+
suggestions,
|
|
406
|
+
searching,
|
|
407
|
+
className,
|
|
408
|
+
onSelect,
|
|
409
|
+
}: {
|
|
410
|
+
suggestions: MentionableUser[]
|
|
411
|
+
searching?: boolean
|
|
412
|
+
className?: string
|
|
413
|
+
onSelect?: (user: MentionableUser) => void
|
|
414
|
+
}) {
|
|
415
|
+
return (
|
|
416
|
+
<div className={cn(
|
|
417
|
+
"absolute z-50 w-64 max-h-60 overflow-y-auto",
|
|
418
|
+
"rounded-lg border bg-popover shadow-lg",
|
|
419
|
+
"p-1 mt-1",
|
|
420
|
+
className,
|
|
421
|
+
)}>
|
|
422
|
+
{searching && (
|
|
423
|
+
<div className="px-2 py-1.5 text-xs text-muted-foreground animate-pulse">
|
|
424
|
+
Recherche...
|
|
425
|
+
</div>
|
|
426
|
+
)}
|
|
427
|
+
{suggestions.map((user) => (
|
|
428
|
+
<button
|
|
429
|
+
key={user.id}
|
|
430
|
+
type="button"
|
|
431
|
+
className={cn(
|
|
432
|
+
"flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left",
|
|
433
|
+
"text-sm transition-colors cursor-pointer",
|
|
434
|
+
"hover:bg-accent/50",
|
|
435
|
+
)}
|
|
436
|
+
onClick={() => onSelect?.(user)}
|
|
437
|
+
>
|
|
438
|
+
<Avatar className="h-6 w-6">
|
|
439
|
+
{user.avatar && <AvatarImage src={user.avatar} alt={user.name} />}
|
|
440
|
+
<AvatarFallback className="text-[10px] bg-primary/10 text-primary">
|
|
441
|
+
{user.name.slice(0, 2).toUpperCase()}
|
|
442
|
+
</AvatarFallback>
|
|
443
|
+
</Avatar>
|
|
444
|
+
<div className="min-w-0 flex-1">
|
|
445
|
+
<div className="font-medium truncate">{user.name}</div>
|
|
446
|
+
{user.email && (
|
|
447
|
+
<div className="text-xs text-muted-foreground truncate">{user.email}</div>
|
|
448
|
+
)}
|
|
449
|
+
</div>
|
|
450
|
+
{user.role && (
|
|
451
|
+
<span className="text-[10px] text-muted-foreground shrink-0">{user.role}</span>
|
|
452
|
+
)}
|
|
453
|
+
</button>
|
|
454
|
+
))}
|
|
455
|
+
</div>
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
MentionSuggestions.displayName = "MentionSuggestions"
|