@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.
- package/package.json +1 -1
- package/template/client/src/components/business-ui/README.md +10 -0
- package/template/client/src/components/business-ui/api/user-profiles/queries.ts +1 -7
- package/template/client/src/components/business-ui/comment/README.md +177 -0
- package/template/client/src/components/business-ui/comment/ccm-document-source.ts +252 -0
- package/template/client/src/components/business-ui/comment/comment-content.tsx +231 -0
- package/template/client/src/components/business-ui/comment/comment-input.tsx +536 -0
- package/template/client/src/components/business-ui/comment/comment-provider.tsx +213 -0
- package/template/client/src/components/business-ui/comment/content.ts +261 -0
- package/template/client/src/components/business-ui/comment/dom-editor.ts +582 -0
- package/template/client/src/components/business-ui/comment/image-attachments.tsx +375 -0
- package/template/client/src/components/business-ui/comment/mention-provider.tsx +168 -0
- package/template/client/src/components/business-ui/comment/mention-suggestions.tsx +411 -0
- package/template/client/src/components/business-ui/comment/mention-view.tsx +171 -0
- package/template/client/src/components/business-ui/comment/types.ts +149 -0
- package/template/client/src/components/business-ui/user-display/user-display.tsx +0 -13
- package/template/client/src/components/business-ui/user-profile/user-profile.tsx +146 -397
- package/template/client/src/components/business-ui/user-select/user-pill.tsx +0 -13
- package/template/client/src/hooks/use-example.ts +45 -36
- package/template/client/src/pages/ExamplePage/ExamplePage.tsx +7 -8
- package/template/client/src/tailwind-theme.css +5 -6
- package/template/eslint.config.js +0 -6
- package/template/package-lock.json +94 -73
- package/template/package.json +5 -4
- package/template/server/modules/hello/hello.controller.ts +4 -8
- package/template/server/modules/hello/hello.service.ts +3 -5
- package/template/shared/api.interface.ts +1 -8
- package/template/client/src/components/business-ui/user-profile/lark-card-pool.ts +0 -217
- package/template/client/src/lib/api-error.ts +0 -36
- package/template/server/modules/hello/hello.dto.ts +0 -21
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { Ref } from 'react';
|
|
2
|
+
|
|
3
|
+
export type MentionRef =
|
|
4
|
+
| { kind: 'user'; id: string; idType: 'apaas-user' }
|
|
5
|
+
| { kind: 'document'; id: string; resourceType: string };
|
|
6
|
+
|
|
7
|
+
export interface MentionOption {
|
|
8
|
+
ref: MentionRef;
|
|
9
|
+
label: string;
|
|
10
|
+
avatarUrl?: string;
|
|
11
|
+
href?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
document?: DocumentMentionMetadata;
|
|
14
|
+
isExternal?: boolean;
|
|
15
|
+
/** Known host-document embed permission; a grey link still opens the access-request page. */
|
|
16
|
+
readable?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DocumentMentionMetadata {
|
|
20
|
+
subType?: number;
|
|
21
|
+
iconInfo?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type MentionResult =
|
|
25
|
+
| (MentionOption & { status: 'resolved' })
|
|
26
|
+
| { ref: MentionRef; status: 'not-found' | 'forbidden' | 'error' };
|
|
27
|
+
|
|
28
|
+
export type CommentPart =
|
|
29
|
+
| { type: 'text'; text: string }
|
|
30
|
+
| { type: 'break' }
|
|
31
|
+
| { type: 'link'; url: string }
|
|
32
|
+
| { type: 'unresolved-mention' }
|
|
33
|
+
| {
|
|
34
|
+
type: 'mention';
|
|
35
|
+
ref: MentionRef;
|
|
36
|
+
label?: string;
|
|
37
|
+
href?: string;
|
|
38
|
+
document?: DocumentMentionMetadata;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** A host supplies authenticated document search and metadata, independently of user IDs. */
|
|
42
|
+
export type CommentDocumentSource = Pick<
|
|
43
|
+
CommentDataSource,
|
|
44
|
+
'scopeKey' | 'search' | 'resolve' | 'recommend'
|
|
45
|
+
>;
|
|
46
|
+
|
|
47
|
+
export interface CommentValue {
|
|
48
|
+
version: 1;
|
|
49
|
+
content: CommentPart[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Confirmed reply.content protocol. Editor metadata is not part of the request. */
|
|
53
|
+
export type CommentApiContent =
|
|
54
|
+
| { type: 'text'; text: string }
|
|
55
|
+
| { type: 'mention'; userID?: number }
|
|
56
|
+
| { type: 'link'; url: string };
|
|
57
|
+
|
|
58
|
+
export interface CommentSubmitPayload {
|
|
59
|
+
content: CommentApiContent[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Storage result, not an assumed Base comment image protocol. */
|
|
63
|
+
export interface CommentImageResult {
|
|
64
|
+
id: string;
|
|
65
|
+
url: string;
|
|
66
|
+
filePath?: string;
|
|
67
|
+
bucketId?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface CommentImage {
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
size: number;
|
|
74
|
+
previewUrl: string;
|
|
75
|
+
status: 'uploading' | 'uploaded' | 'error';
|
|
76
|
+
result?: CommentImageResult;
|
|
77
|
+
error?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type CommentImageUploader = (
|
|
81
|
+
file: File,
|
|
82
|
+
options: { signal: AbortSignal },
|
|
83
|
+
) => Promise<CommentImageResult>;
|
|
84
|
+
|
|
85
|
+
export type MentionKind = 'all' | 'user' | 'document';
|
|
86
|
+
|
|
87
|
+
/** Keep this object stable. Change scopeKey when the user/tenant/app/permissions change. */
|
|
88
|
+
export interface CommentDataSource {
|
|
89
|
+
scopeKey: string;
|
|
90
|
+
/** Bare @ default list, independent of keyword search. Failures must reject. */
|
|
91
|
+
recommend?: (args: {
|
|
92
|
+
kind: MentionKind;
|
|
93
|
+
cursor?: string;
|
|
94
|
+
signal: AbortSignal;
|
|
95
|
+
}) => Promise<{ items: MentionOption[]; nextCursor?: string }>;
|
|
96
|
+
/** Legacy sources without recommend can still handle an empty query here. */
|
|
97
|
+
search(args: {
|
|
98
|
+
query: string;
|
|
99
|
+
kind: MentionKind;
|
|
100
|
+
cursor?: string;
|
|
101
|
+
signal: AbortSignal;
|
|
102
|
+
}): Promise<{ items: MentionOption[]; nextCursor?: string }>;
|
|
103
|
+
resolve(refs: MentionRef[], signal: AbortSignal): Promise<MentionResult[]>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface CommentInputProps {
|
|
107
|
+
ref?: Ref<CommentInputHandle>;
|
|
108
|
+
documentKey: string;
|
|
109
|
+
value: CommentValue;
|
|
110
|
+
/** Synchronously set the controlled value; debounce persistence, not this callback. */
|
|
111
|
+
onChange: (value: CommentValue) => void;
|
|
112
|
+
/** A keyboard intent only. The host owns requests, buttons, loading and errors. */
|
|
113
|
+
onSubmitRequest?: () => void;
|
|
114
|
+
disabled?: boolean;
|
|
115
|
+
enableDocuments?: boolean;
|
|
116
|
+
/** Images are attachments below the body, never inline editor nodes. */
|
|
117
|
+
enableImages?: boolean;
|
|
118
|
+
uploadImage?: CommentImageUploader;
|
|
119
|
+
maxImages?: number;
|
|
120
|
+
/** Bytes; defaults to 10 MiB. */
|
|
121
|
+
maxImageSize?: number;
|
|
122
|
+
onImagesChange?: (images: CommentImage[]) => void;
|
|
123
|
+
/** Optional controlled permission intent. The host performs actual grants. */
|
|
124
|
+
grantMentionAccess?: boolean;
|
|
125
|
+
onGrantMentionAccessChange?: (checked: boolean) => void;
|
|
126
|
+
placeholder?: string;
|
|
127
|
+
submitMode?: 'enter' | 'mod-enter';
|
|
128
|
+
className?: string;
|
|
129
|
+
/** Mount the suggestion panel inside a Dialog/Sheet when appropriate. */
|
|
130
|
+
portalContainer?: HTMLElement;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface CommentInputHandle {
|
|
134
|
+
/** Read the latest wire content. Does not validate whether the host should send. */
|
|
135
|
+
getContent(): CommentApiContent[];
|
|
136
|
+
getImages(): CommentImage[];
|
|
137
|
+
/** Clear the body as an undoable edit; keep the same editing session. */
|
|
138
|
+
clear(): void;
|
|
139
|
+
/** Attachments have their own lifecycle, independent of body undo history. */
|
|
140
|
+
clearImages(): void;
|
|
141
|
+
setEditable(editable: boolean): void;
|
|
142
|
+
focus(): void;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The host decides how to display a profile or navigate a reference. */
|
|
146
|
+
export type CommentMentionClickHandler = (
|
|
147
|
+
ref: MentionRef,
|
|
148
|
+
anchor: HTMLElement,
|
|
149
|
+
) => void;
|
|
@@ -13,18 +13,6 @@ import {
|
|
|
13
13
|
PopoverTrigger,
|
|
14
14
|
} from '@client/src/components/ui/popover';
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* 阻止 Popover 打开时的自动聚焦。
|
|
18
|
-
*
|
|
19
|
-
* Radix 的 FocusScope 在弹层挂载时会把焦点给容器里第一个可聚焦元素。人员卡片
|
|
20
|
-
* 挂载那一刻内容就已经在了(飞书名片里有「消息」等按钮),于是一打开就有个
|
|
21
|
-
* 按钮带上焦点环。这是张只读预览卡,焦点应该留在触发器上 —— Radix 自己的
|
|
22
|
-
* HoverCard 也是这么处理的。
|
|
23
|
-
*/
|
|
24
|
-
const handleProfileOpenAutoFocus = (event: Event) => {
|
|
25
|
-
event.preventDefault();
|
|
26
|
-
};
|
|
27
|
-
|
|
28
16
|
export interface IUserDisplayProps {
|
|
29
17
|
/**
|
|
30
18
|
* 用户列表
|
|
@@ -100,7 +88,6 @@ export const UserDisplay: React.FC<IUserDisplayProps> = ({
|
|
|
100
88
|
<PopoverContent
|
|
101
89
|
align="start"
|
|
102
90
|
sideOffset={8}
|
|
103
|
-
onOpenAutoFocus={handleProfileOpenAutoFocus}
|
|
104
91
|
className="w-[320px] border-0 border-border/50 bg-card p-0 shadow-[0px_8px_24px_8px_rgba(31,35,41,0.04),0px_6px_12px_rgba(31,35,41,0.04),0px_4px_8px_-8px_rgba(31,35,41,0.06)]"
|
|
105
92
|
>
|
|
106
93
|
<UserProfile value={user} />
|