@lark-apaas/coding-template-nestjs-react-fullstack 0.1.54 → 0.1.55-alpha.20261030081308

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 (30) hide show
  1. package/package.json +1 -1
  2. package/template/client/src/components/business-ui/README.md +10 -0
  3. package/template/client/src/components/business-ui/api/user-profiles/queries.ts +1 -7
  4. package/template/client/src/components/business-ui/comment/README.md +177 -0
  5. package/template/client/src/components/business-ui/comment/ccm-document-source.ts +252 -0
  6. package/template/client/src/components/business-ui/comment/comment-content.tsx +231 -0
  7. package/template/client/src/components/business-ui/comment/comment-input.tsx +536 -0
  8. package/template/client/src/components/business-ui/comment/comment-provider.tsx +213 -0
  9. package/template/client/src/components/business-ui/comment/content.ts +261 -0
  10. package/template/client/src/components/business-ui/comment/dom-editor.ts +582 -0
  11. package/template/client/src/components/business-ui/comment/image-attachments.tsx +375 -0
  12. package/template/client/src/components/business-ui/comment/mention-provider.tsx +168 -0
  13. package/template/client/src/components/business-ui/comment/mention-suggestions.tsx +411 -0
  14. package/template/client/src/components/business-ui/comment/mention-view.tsx +171 -0
  15. package/template/client/src/components/business-ui/comment/types.ts +149 -0
  16. package/template/client/src/components/business-ui/user-display/user-display.tsx +0 -13
  17. package/template/client/src/components/business-ui/user-profile/user-profile.tsx +146 -397
  18. package/template/client/src/components/business-ui/user-select/user-pill.tsx +0 -13
  19. package/template/client/src/hooks/use-example.ts +45 -36
  20. package/template/client/src/pages/ExamplePage/ExamplePage.tsx +7 -8
  21. package/template/client/src/tailwind-theme.css +5 -6
  22. package/template/eslint.config.js +0 -6
  23. package/template/package-lock.json +94 -73
  24. package/template/package.json +5 -4
  25. package/template/server/modules/hello/hello.controller.ts +4 -8
  26. package/template/server/modules/hello/hello.service.ts +3 -5
  27. package/template/shared/api.interface.ts +1 -8
  28. package/template/client/src/components/business-ui/user-profile/lark-card-pool.ts +0 -217
  29. package/template/client/src/lib/api-error.ts +0 -36
  30. package/template/server/modules/hello/hello.dto.ts +0 -21
@@ -0,0 +1,231 @@
1
+ import { fromApiContent } from './content';
2
+ import {
3
+ DocumentIcon,
4
+ USER_MENTION_CLASS,
5
+ INPUT_USER_MENTION_CLASS,
6
+ INPUT_USER_MENTION_LABEL_CLASS,
7
+ COMMENT_THEME_CLASS,
8
+ USER_MENTION_SELF_CLASS,
9
+ atUserLabel,
10
+ USER_MENTION_LABEL_CLASS,
11
+ DOCUMENT_MENTION_CLASS,
12
+ DOCUMENT_MENTION_UNREADABLE_CLASS,
13
+ } from './mention-view';
14
+ import * as React from 'react';
15
+ import { cn } from '@/lib/utils';
16
+ import { mentionLabel, normalizeValue, safeUrl } from './content';
17
+ import {
18
+ useMentionContext,
19
+ useMentionEntity,
20
+ useResolveComment,
21
+ } from './mention-provider';
22
+ import type {
23
+ CommentMentionClickHandler,
24
+ CommentApiContent,
25
+ CommentPart,
26
+ CommentValue,
27
+ } from './types';
28
+
29
+ function Mention({
30
+ part,
31
+ onMentionClick,
32
+ mentionVariant,
33
+ }: {
34
+ part: Extract<CommentPart, { type: 'mention' }>;
35
+ onMentionClick?: CommentMentionClickHandler;
36
+ mentionVariant?: 'text' | 'pill';
37
+ }) {
38
+ const { strict, store, currentUserID } = useMentionContext();
39
+ const result = useMentionEntity(part.ref);
40
+ const label = mentionLabel(part.ref, part.label, result, strict);
41
+ if (part.ref.kind === 'document') {
42
+ const resolved = result?.status === 'resolved' ? result : undefined;
43
+ const documentHref = safeUrl(
44
+ resolved?.href || (!strict ? part.href : undefined),
45
+ );
46
+ const metadata =
47
+ resolved?.document || (!strict ? part.document : undefined);
48
+ const title = resolved
49
+ ? resolved.label
50
+ : result?.status === 'forbidden' ||
51
+ result?.status === 'not-found' ||
52
+ strict
53
+ ? label
54
+ : part.label;
55
+ const body = (
56
+ <>
57
+ {title ? (
58
+ <DocumentIcon refValue={part.ref} metadata={metadata} />
59
+ ) : null}
60
+ <span>{title || documentHref || '文档'}</span>
61
+ </>
62
+ );
63
+ if (documentHref)
64
+ return (
65
+ <a
66
+ className={
67
+ result?.status === 'forbidden' || resolved?.readable === false
68
+ ? DOCUMENT_MENTION_UNREADABLE_CLASS
69
+ : DOCUMENT_MENTION_CLASS
70
+ }
71
+ href={documentHref}
72
+ target="_blank"
73
+ rel="noopener noreferrer"
74
+ data-doclink="true"
75
+ data-doclink-token={part.ref.id}
76
+ data-doclink-type={part.ref.resourceType}
77
+ onClick={(event) => event.stopPropagation()}
78
+ onMouseDown={(event) => event.preventDefault()}
79
+ >
80
+ {body}
81
+ </a>
82
+ );
83
+ if (result?.status === 'error')
84
+ return (
85
+ <button
86
+ type="button"
87
+ className={DOCUMENT_MENTION_CLASS}
88
+ title="信息加载失败,点击重试"
89
+ onClick={() => store.ensure([part.ref], true)}
90
+ >
91
+ {body}
92
+ </button>
93
+ );
94
+ return <span className={DOCUMENT_MENTION_CLASS}>{body}</span>;
95
+ }
96
+ const userLabelClass =
97
+ mentionVariant === 'pill'
98
+ ? INPUT_USER_MENTION_LABEL_CLASS
99
+ : part.ref.id === currentUserID
100
+ ? USER_MENTION_SELF_CLASS
101
+ : USER_MENTION_LABEL_CLASS;
102
+ const body = (
103
+ <span
104
+ className={cn(
105
+ userLabelClass,
106
+ result?.status === 'forbidden' && 'text-muted-foreground',
107
+ )}
108
+ >
109
+ {atUserLabel(label)}
110
+ </span>
111
+ );
112
+ const className =
113
+ mentionVariant === 'pill' ? INPUT_USER_MENTION_CLASS : USER_MENTION_CLASS;
114
+ if (
115
+ onMentionClick &&
116
+ result?.status !== 'forbidden' &&
117
+ result?.status !== 'not-found'
118
+ )
119
+ return (
120
+ <button
121
+ type="button"
122
+ className={className}
123
+ onClick={(event) => onMentionClick(part.ref, event.currentTarget)}
124
+ >
125
+ {body}
126
+ </button>
127
+ );
128
+ if (result?.status === 'error')
129
+ return (
130
+ <button
131
+ type="button"
132
+ className={className}
133
+ title="信息加载失败,点击重试"
134
+ onClick={() => store.ensure([part.ref], true)}
135
+ >
136
+ {body}
137
+ </button>
138
+ );
139
+ return <span className={className}>{body}</span>;
140
+ }
141
+
142
+ export type CommentContentProps = {
143
+ className?: string;
144
+ onMentionClick?: CommentMentionClickHandler;
145
+ mentionVariant?: 'text' | 'pill';
146
+ } & (
147
+ | { value: CommentValue; content?: never }
148
+ | { content: readonly CommentApiContent[]; value?: never }
149
+ );
150
+
151
+ /** Pass API content with userID only; metadata is fetched internally in batches. */
152
+ export function CommentContent(props: CommentContentProps) {
153
+ const { className, onMentionClick, mentionVariant = 'text' } = props;
154
+ let normalized: CommentValue;
155
+ try {
156
+ normalized = props.value
157
+ ? normalizeValue(props.value)
158
+ : fromApiContent(props.content!);
159
+ } catch {
160
+ return (
161
+ <p role="alert" className="text-sm text-destructive">
162
+ 评论内容格式无效,无法展示
163
+ </p>
164
+ );
165
+ }
166
+ return React.createElement(ResolvedContent, {
167
+ value: normalized,
168
+ className,
169
+ onMentionClick,
170
+ mentionVariant,
171
+ });
172
+ }
173
+
174
+ function ResolvedContent({
175
+ value: normalized,
176
+ className,
177
+ onMentionClick,
178
+ mentionVariant,
179
+ }: {
180
+ value: CommentValue;
181
+ className?: string;
182
+ onMentionClick?: CommentMentionClickHandler;
183
+ mentionVariant?: 'text' | 'pill';
184
+ }) {
185
+ useResolveComment(normalized);
186
+ return (
187
+ <div
188
+ className={cn(
189
+ COMMENT_THEME_CLASS,
190
+ 'text-foreground text-sm leading-[22px]',
191
+ className,
192
+ )}
193
+ >
194
+ <div className="whitespace-pre-wrap break-words">
195
+ {normalized.content.map((part, index) =>
196
+ part.type === 'text' ? (
197
+ part.text
198
+ ) : part.type === 'break' ? (
199
+ <br key={index} />
200
+ ) : part.type === 'link' ? (
201
+ <a
202
+ key={index}
203
+ href={safeUrl(part.url)}
204
+ target="_blank"
205
+ rel="noopener noreferrer"
206
+ className="break-words text-primary no-underline hover:underline"
207
+ onClick={(event) => event.stopPropagation()}
208
+ >
209
+ {part.url}
210
+ </a>
211
+ ) : part.type === 'unresolved-mention' ? (
212
+ <span
213
+ key={index}
214
+ className="text-subtle-foreground"
215
+ title="原评论缺少被提及对象的信息"
216
+ >
217
+ 引用信息缺失
218
+ </span>
219
+ ) : (
220
+ <Mention
221
+ key={index}
222
+ part={part}
223
+ onMentionClick={onMentionClick}
224
+ mentionVariant={mentionVariant}
225
+ />
226
+ ),
227
+ )}
228
+ </div>
229
+ </div>
230
+ );
231
+ }