one-design-next 0.0.11 → 0.0.13
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/_genui-types.d.ts +45 -10
- package/dist/attachments/index.js +67 -10
- package/dist/attachments/style/index.css +86 -14
- package/dist/collapse/primitive.d.ts +1 -1
- package/dist/composer/chip.d.ts +4 -4
- package/dist/composer/clipboard.d.ts +12 -0
- package/dist/composer/clipboard.js +84 -0
- package/dist/composer/editor.d.ts +9 -1
- package/dist/composer/editor.js +70 -14
- package/dist/composer/hooks/useChipManager.d.ts +6 -1
- package/dist/composer/hooks/useChipManager.js +76 -27
- package/dist/composer/hooks/useChipSelectionMarker.d.ts +7 -0
- package/dist/composer/hooks/useChipSelectionMarker.js +66 -0
- package/dist/composer/index.d.ts +58 -1
- package/dist/composer/index.js +219 -102
- package/dist/composer/inline-ref.d.ts +9 -0
- package/dist/composer/inline-ref.js +21 -0
- package/dist/composer/send-meta.d.ts +6 -0
- package/dist/composer/send-meta.js +67 -0
- package/dist/composer/style/index.css +48 -53
- package/dist/composer/utils.d.ts +9 -0
- package/dist/composer/utils.js +43 -2
- package/dist/fab/index.js +3 -16
- package/dist/fab/style/index.css +1 -3
- package/dist/icon/svg-data.d.ts +4 -0
- package/dist/icon/svg-data.js +1 -1
- package/dist/icon/types.d.ts +1 -1
- package/dist/image/index.d.ts +43 -0
- package/dist/image/index.js +51 -0
- package/dist/image/style/index.css +59 -0
- package/dist/image/style/index.d.ts +2 -0
- package/dist/image/style/index.js +2 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -0
- package/dist/invocation/index.d.ts +17 -0
- package/dist/invocation/index.js +84 -0
- package/dist/invocation/style/index.css +58 -0
- package/dist/invocation/style/index.d.ts +2 -0
- package/dist/invocation/style/index.js +2 -0
- package/dist/mention/index.d.ts +17 -0
- package/dist/mention/index.js +90 -0
- package/dist/mention/style/index.css +58 -0
- package/dist/mention/style/index.d.ts +2 -0
- package/dist/mention/style/index.js +2 -0
- package/dist/message-image/index.d.ts +42 -0
- package/dist/message-image/index.js +46 -0
- package/dist/message-image/style/index.css +60 -0
- package/dist/message-image/style/index.d.ts +2 -0
- package/dist/message-image/style/index.js +2 -0
- package/dist/preview-panel/index.d.ts +5 -1
- package/dist/preview-panel/index.js +27 -2
- package/dist/user-bubble/index.d.ts +11 -1
- package/dist/user-bubble/index.js +30 -5
- package/dist/user-bubble/style/index.css +6 -0
- package/package.json +2 -3
package/dist/_genui-types.d.ts
CHANGED
|
@@ -25,8 +25,16 @@ export interface Attachment {
|
|
|
25
25
|
type: string;
|
|
26
26
|
url?: string;
|
|
27
27
|
size?: number;
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* 特殊变体:
|
|
30
|
+
* - 'report':AI 生成报告的大卡片(含创建时间 + 右侧预览缩略图)。
|
|
31
|
+
* - 'thumb-card':紧凑缩略卡——左侧 32×32 缩略图(image/* + url 时为图本身,
|
|
32
|
+
* 其余类型为分类色 icon)、右侧两行(文件名 + 类型标签)。适合长聊天流回看 /
|
|
33
|
+
* 引用 / 有正文 + 多图列表等"占位省、识别强"的场景。与默认的「图片大预览
|
|
34
|
+
* imageCardInner」并列,由调用方按上下文选择。
|
|
35
|
+
* - 未传:走普通文件卡片(image/* + url 自动展开为内嵌大预览 imageCardInner)。
|
|
36
|
+
*/
|
|
37
|
+
variant?: 'report' | 'thumb-card';
|
|
30
38
|
/** variant='report' 时展示在标题下的创建时间,例如 "03-09 15:46"。 */
|
|
31
39
|
createdAt?: string;
|
|
32
40
|
/** variant='report' 时右侧预览缩略图地址。未传时渲染为浅色占位层。 */
|
|
@@ -46,6 +54,25 @@ export interface PreviewTab {
|
|
|
46
54
|
content: string;
|
|
47
55
|
type: string;
|
|
48
56
|
}
|
|
57
|
+
/** Composer 正文内 `/` 触发的内联引用(Invocation 组件数据契约)。 */
|
|
58
|
+
export interface InvocationData {
|
|
59
|
+
id: string;
|
|
60
|
+
/** 业务类型:skill / command / 自定义 */
|
|
61
|
+
kind: string;
|
|
62
|
+
refId: string;
|
|
63
|
+
label: string;
|
|
64
|
+
icon?: string;
|
|
65
|
+
params?: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
/** Composer 正文内 `@` 触发的内联引用(Mention 组件数据契约)。 */
|
|
68
|
+
export interface MentionData {
|
|
69
|
+
id: string;
|
|
70
|
+
kind: string;
|
|
71
|
+
refId: string;
|
|
72
|
+
label: string;
|
|
73
|
+
icon?: string;
|
|
74
|
+
params?: Record<string, unknown>;
|
|
75
|
+
}
|
|
49
76
|
export interface SkillItem {
|
|
50
77
|
id: string;
|
|
51
78
|
label: string;
|
|
@@ -61,14 +88,13 @@ export interface SkillItem {
|
|
|
61
88
|
export interface SendMeta {
|
|
62
89
|
attachments?: Attachment[];
|
|
63
90
|
webSearch?: boolean;
|
|
91
|
+
/** `/` 内联引用列表(Invocation),按编辑器内出现顺序。 */
|
|
92
|
+
invocations?: InvocationData[];
|
|
93
|
+
/** `@` 内联引用列表(Mention),按编辑器内出现顺序。 */
|
|
94
|
+
mentions?: MentionData[];
|
|
64
95
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* - 通过 `kind` 字段区分 skill / mention / 自定义类型
|
|
68
|
-
* - 业务方按需 filter,例如:`meta.chips?.filter(c => c.kind === 'skill')`
|
|
69
|
-
*
|
|
70
|
-
* BREAKING(pre-1.0):取代 v0.0.x 的 `skill?: string` 单值字段。
|
|
71
|
-
* 老业务迁移:`meta.skill` → `meta.chips?.find(c => c.kind === 'skill')?.label`
|
|
96
|
+
* @deprecated 请改用 `invocations` / `mentions`。本字段仍双写一版,便于旧业务迁移。
|
|
97
|
+
* 编辑器内 chip 列表(按出现顺序);`kind: 'skill'` 对应 invocation。
|
|
72
98
|
*/
|
|
73
99
|
chips?: ComposerChipMeta[];
|
|
74
100
|
}
|
|
@@ -78,7 +104,7 @@ export interface ComposerChipMeta {
|
|
|
78
104
|
skillId: string;
|
|
79
105
|
label: string;
|
|
80
106
|
icon: string;
|
|
81
|
-
kind?: 'skill' | 'mention' | (string & {});
|
|
107
|
+
kind?: 'skill' | 'invocation' | 'mention' | (string & {});
|
|
82
108
|
}
|
|
83
109
|
export type AgentEvent = {
|
|
84
110
|
kind: 'thinking';
|
|
@@ -105,6 +131,10 @@ export interface MessageSnapshot {
|
|
|
105
131
|
message: string;
|
|
106
132
|
retryable?: boolean;
|
|
107
133
|
};
|
|
134
|
+
/**
|
|
135
|
+
* 用户对该版本回答的反馈状态。点踩/点赞按版本独立维护——切到不同版本应回显该版本自身的态度。
|
|
136
|
+
*/
|
|
137
|
+
feedback?: 'positive' | 'negative' | null;
|
|
108
138
|
}
|
|
109
139
|
export interface ChatMessage {
|
|
110
140
|
id: string;
|
|
@@ -126,4 +156,9 @@ export interface ChatMessage {
|
|
|
126
156
|
* 助手消息可选的快捷回复(如意图澄清)。由上层在正文下渲染为可点选项,点击语义等同用户发送该句。
|
|
127
157
|
*/
|
|
128
158
|
suggestionChips?: string[];
|
|
159
|
+
/**
|
|
160
|
+
* 用户对**最新版本**回答的反馈状态。历史版本的反馈各自存在 `regenerations[i].feedback`,
|
|
161
|
+
* 切到不同版本时按版本独立回显——同一问题的不同回答用户态度可以不同。
|
|
162
|
+
*/
|
|
163
|
+
feedback?: 'positive' | 'negative' | null;
|
|
129
164
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
1
2
|
import HoverFill from "../hover-fill";
|
|
2
3
|
import Icon from "../icon";
|
|
3
4
|
import "./style";
|
|
@@ -130,10 +131,68 @@ export function Attachments(_ref) {
|
|
|
130
131
|
_ref$compact = _ref.compact,
|
|
131
132
|
compact = _ref$compact === void 0 ? false : _ref$compact;
|
|
132
133
|
if (attachments.length === 0) return null;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* thumb-card 渲染:紧凑卡(左缩略图 + 右两行文件名/类型)。
|
|
137
|
+
*
|
|
138
|
+
* 抽成组件内部 helper 而非内联,是为了让 compact 分支与 default 分支共用同一份
|
|
139
|
+
* 渲染逻辑——variant 是「调用方显式 opt-in」的信号,必须优先于 compact 隐式推导:
|
|
140
|
+
* 「有正文 + 附件」场景(compact=true)下,调用方传 thumb-card 就是不想让附件
|
|
141
|
+
* 退化成 chip,而想要"小卡片 + 缩略图"承担引用语义。
|
|
142
|
+
*/
|
|
143
|
+
function renderThumbCard(a, i) {
|
|
144
|
+
var meta = getFileMeta(a.type);
|
|
145
|
+
var isImage = a.type.startsWith('image/') && !!a.url;
|
|
146
|
+
var thumbCardInner = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
147
|
+
"data-odn-attachments-thumb-card-thumb": true,
|
|
148
|
+
style: isImage ? undefined : {
|
|
149
|
+
backgroundColor: meta.bg
|
|
150
|
+
}
|
|
151
|
+
}, isImage ? /*#__PURE__*/React.createElement("img", {
|
|
152
|
+
src: a.url,
|
|
153
|
+
alt: ""
|
|
154
|
+
}) : /*#__PURE__*/React.createElement(Icon, {
|
|
155
|
+
name: meta.icon,
|
|
156
|
+
size: 16,
|
|
157
|
+
style: {
|
|
158
|
+
color: meta.color
|
|
159
|
+
}
|
|
160
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
161
|
+
"data-odn-attachments-thumb-card-info": true
|
|
162
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
163
|
+
"data-odn-attachments-thumb-card-name": true
|
|
164
|
+
}, a.name), /*#__PURE__*/React.createElement("div", {
|
|
165
|
+
"data-odn-attachments-thumb-card-type": true
|
|
166
|
+
}, formatType(a.type, a.name))));
|
|
167
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
168
|
+
key: i,
|
|
169
|
+
"data-odn-attachments-item": true,
|
|
170
|
+
"data-odn-attachments-item-thumb-card": ""
|
|
171
|
+
}, onFileClick ? /*#__PURE__*/React.createElement("button", {
|
|
172
|
+
type: "button",
|
|
173
|
+
onClick: function onClick() {
|
|
174
|
+
return onFileClick(a);
|
|
175
|
+
},
|
|
176
|
+
"data-odn-attachments-thumb-card": true,
|
|
177
|
+
"data-odn-attachments-clickable": ""
|
|
178
|
+
}, thumbCardInner) : /*#__PURE__*/React.createElement("div", {
|
|
179
|
+
"data-odn-attachments-thumb-card": true
|
|
180
|
+
}, thumbCardInner), onRemove && /*#__PURE__*/React.createElement("button", {
|
|
181
|
+
onClick: function onClick() {
|
|
182
|
+
return onRemove(i);
|
|
183
|
+
},
|
|
184
|
+
"data-odn-attachments-remove": true
|
|
185
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
186
|
+
name: "x",
|
|
187
|
+
size: 10
|
|
188
|
+
})));
|
|
189
|
+
}
|
|
133
190
|
if (compact) {
|
|
134
191
|
return /*#__PURE__*/React.createElement("div", {
|
|
135
192
|
"data-odn-attachments-compact": true
|
|
136
193
|
}, attachments.map(function (a, i) {
|
|
194
|
+
// thumb-card 优先于 chip 默认路径——见上方 renderThumbCard 注释。
|
|
195
|
+
if (a.variant === 'thumb-card') return renderThumbCard(a, i);
|
|
137
196
|
var meta = getFileMeta(a.type);
|
|
138
197
|
var chip = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Icon, {
|
|
139
198
|
name: meta.icon,
|
|
@@ -251,14 +310,10 @@ export function Attachments(_ref) {
|
|
|
251
310
|
size: 10
|
|
252
311
|
})));
|
|
253
312
|
}
|
|
313
|
+
if (a.variant === 'thumb-card') return renderThumbCard(a, i);
|
|
254
314
|
var meta = getFileMeta(a.type);
|
|
255
315
|
var isImage = a.type.startsWith('image/') && a.url;
|
|
256
|
-
var
|
|
257
|
-
"data-odn-attachments-img-wrapper": true
|
|
258
|
-
}, /*#__PURE__*/React.createElement("img", {
|
|
259
|
-
src: a.url,
|
|
260
|
-
alt: a.name
|
|
261
|
-
})) : /*#__PURE__*/React.createElement("div", {
|
|
316
|
+
var fileCardInner = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
262
317
|
"data-odn-attachments-file-icon": true,
|
|
263
318
|
style: {
|
|
264
319
|
backgroundColor: meta.bg
|
|
@@ -276,19 +331,21 @@ export function Attachments(_ref) {
|
|
|
276
331
|
}, a.name), /*#__PURE__*/React.createElement("div", {
|
|
277
332
|
"data-odn-attachments-meta": true
|
|
278
333
|
}, formatType(a.type, a.name), a.size ? " \xB7 ".concat(formatSize(a.size)) : '')));
|
|
279
|
-
return /*#__PURE__*/React.createElement("div", {
|
|
334
|
+
return /*#__PURE__*/React.createElement("div", _extends({
|
|
280
335
|
key: i,
|
|
281
336
|
"data-odn-attachments-item": true
|
|
282
|
-
},
|
|
337
|
+
}, isImage ? {
|
|
338
|
+
'data-odn-attachments-item-image-card': ''
|
|
339
|
+
} : {}), onFileClick ? /*#__PURE__*/React.createElement("button", {
|
|
283
340
|
type: "button",
|
|
284
341
|
onClick: function onClick() {
|
|
285
342
|
return onFileClick(a);
|
|
286
343
|
},
|
|
287
344
|
"data-odn-attachments-card": true,
|
|
288
345
|
"data-odn-attachments-clickable": ""
|
|
289
|
-
},
|
|
346
|
+
}, fileCardInner) : /*#__PURE__*/React.createElement("div", {
|
|
290
347
|
"data-odn-attachments-card": true
|
|
291
|
-
},
|
|
348
|
+
}, fileCardInner), onRemove && /*#__PURE__*/React.createElement("button", {
|
|
292
349
|
onClick: function onClick() {
|
|
293
350
|
return onRemove(i);
|
|
294
351
|
},
|
|
@@ -84,20 +84,6 @@
|
|
|
84
84
|
border-color: color-mix(in srgb, var(--odn-color-primary) 50%, transparent);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
[data-odn-attachments-img-wrapper] {
|
|
88
|
-
height: 0;
|
|
89
|
-
min-height: 100%;
|
|
90
|
-
aspect-ratio: 1;
|
|
91
|
-
border-radius: 3px;
|
|
92
|
-
overflow: hidden;
|
|
93
|
-
align-self: center;
|
|
94
|
-
}
|
|
95
|
-
[data-odn-attachments-img-wrapper] img {
|
|
96
|
-
width: 100%;
|
|
97
|
-
height: 100%;
|
|
98
|
-
object-fit: cover;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
87
|
[data-odn-attachments-file-icon] {
|
|
102
88
|
display: flex;
|
|
103
89
|
align-items: center;
|
|
@@ -152,6 +138,92 @@
|
|
|
152
138
|
color: var(--odn-color-error);
|
|
153
139
|
}
|
|
154
140
|
|
|
141
|
+
/* ==========================================================================
|
|
142
|
+
* Thumb-card 变体(紧凑缩略卡)
|
|
143
|
+
* 左侧缩略图(image/* + url 时为图本身;其余类型为分类色 icon),
|
|
144
|
+
* 右侧两行(文件名 + 类型标签)。
|
|
145
|
+
*
|
|
146
|
+
* 节奏与默认「文件附件卡」(data-odn-attachments-card) 严格对齐:同样的
|
|
147
|
+
* padding / border-radius / border / 字号,仅 icon 槽内容由「彩色 icon」
|
|
148
|
+
* 替换为「图片缩略 or 彩色 icon」——保证 thumb-card 与 file-card 同行 wrap
|
|
149
|
+
* 时高度 / 圆角 / 边线一致,不会出现错位。
|
|
150
|
+
* ========================================================================== */
|
|
151
|
+
[data-odn-attachments-item-thumb-card] {
|
|
152
|
+
display: inline-flex;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
[data-odn-attachments-thumb-card] {
|
|
156
|
+
display: grid;
|
|
157
|
+
grid-template-columns: auto 1fr;
|
|
158
|
+
gap: 8px;
|
|
159
|
+
padding: 6px 10px 6px 6px;
|
|
160
|
+
border-radius: 6px;
|
|
161
|
+
border: 1px solid var(--odn-color-black-6);
|
|
162
|
+
background: var(--odn-color-solid-black-1);
|
|
163
|
+
box-sizing: border-box;
|
|
164
|
+
font: inherit;
|
|
165
|
+
color: inherit;
|
|
166
|
+
text-align: left;
|
|
167
|
+
appearance: none;
|
|
168
|
+
transition: border-color 0.15s;
|
|
169
|
+
}
|
|
170
|
+
[data-odn-attachments-thumb-card][data-odn-attachments-clickable] {
|
|
171
|
+
cursor: pointer;
|
|
172
|
+
}
|
|
173
|
+
[data-odn-attachments-thumb-card][data-odn-attachments-clickable]:hover {
|
|
174
|
+
border-color: color-mix(in srgb, var(--odn-color-primary) 50%, transparent);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
[data-odn-attachments-thumb-card-thumb] {
|
|
178
|
+
display: flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
justify-content: center;
|
|
181
|
+
height: 0;
|
|
182
|
+
min-height: 100%;
|
|
183
|
+
aspect-ratio: 1;
|
|
184
|
+
border-radius: 3px;
|
|
185
|
+
overflow: hidden;
|
|
186
|
+
}
|
|
187
|
+
[data-odn-attachments-thumb-card-thumb] img {
|
|
188
|
+
width: 100%;
|
|
189
|
+
height: 100%;
|
|
190
|
+
object-fit: cover;
|
|
191
|
+
display: block;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
[data-odn-attachments-thumb-card-info] {
|
|
195
|
+
min-width: 0;
|
|
196
|
+
display: flex;
|
|
197
|
+
flex-direction: column;
|
|
198
|
+
justify-content: center;
|
|
199
|
+
gap: 2px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
[data-odn-attachments-thumb-card-name] {
|
|
203
|
+
font-size: 12px;
|
|
204
|
+
line-height: normal;
|
|
205
|
+
color: var(--odn-color-black-12);
|
|
206
|
+
overflow: hidden;
|
|
207
|
+
text-overflow: ellipsis;
|
|
208
|
+
white-space: nowrap;
|
|
209
|
+
max-width: 120px;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
[data-odn-attachments-thumb-card-type] {
|
|
213
|
+
font-size: 10px;
|
|
214
|
+
line-height: 1.25;
|
|
215
|
+
color: var(--odn-color-black-9);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
[data-odn-attachments-item-image-card] {
|
|
219
|
+
flex-basis: 100%;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
[data-odn-attachments-card]:not([data-odn-attachments-image-card]),
|
|
223
|
+
[data-odn-attachments-thumb-card] {
|
|
224
|
+
width: 192px;
|
|
225
|
+
}
|
|
226
|
+
|
|
155
227
|
/* ==========================================================================
|
|
156
228
|
* Report 变体(AI 生成报告入口卡片)
|
|
157
229
|
* 设计对齐:豆包报告卡片 —— 左侧图标 + 标题/创建时间,右侧预览缩略图,
|
|
@@ -25,7 +25,7 @@ type CollapsibleContentProps = Omit<HTMLMotionProps<'div'>, 'children' | 'ref' |
|
|
|
25
25
|
children?: React.ReactNode;
|
|
26
26
|
transition?: Transition;
|
|
27
27
|
};
|
|
28
|
-
declare const CollapsibleContent: React.ForwardRefExoticComponent<Omit<HTMLMotionProps<"div">, "
|
|
28
|
+
declare const CollapsibleContent: React.ForwardRefExoticComponent<Omit<HTMLMotionProps<"div">, "children" | "transition" | "ref"> & {
|
|
29
29
|
children?: React.ReactNode;
|
|
30
30
|
transition?: Transition | undefined;
|
|
31
31
|
} & React.RefAttributes<HTMLDivElement>>;
|
package/dist/composer/chip.d.ts
CHANGED
|
@@ -22,11 +22,11 @@ export interface ChipData {
|
|
|
22
22
|
/**
|
|
23
23
|
* chip 语义来源标记,业务方自定义。Composer 内部不消费 kind,
|
|
24
24
|
* 仅原样透传到 onSend(meta.chips),方便业务方按 kind 分类。
|
|
25
|
-
* 约定:'
|
|
26
|
-
* 'mention' = `@`
|
|
27
|
-
*
|
|
25
|
+
* 约定:'invocation' = 工具栏按钮 / `/` 触发(SendMeta.invocations);
|
|
26
|
+
* 'mention' = `@` 触发(SendMeta.mentions);
|
|
27
|
+
* 'skill' 为兼容别名,等同 invocation。
|
|
28
28
|
*/
|
|
29
|
-
kind?: '
|
|
29
|
+
kind?: 'invocation' | 'mention' | 'skill' | (string & {});
|
|
30
30
|
}
|
|
31
31
|
export interface ChipProps {
|
|
32
32
|
data: ChipData;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChipData } from './chip';
|
|
2
|
+
import type { ManagedChip } from './hooks/useChipManager';
|
|
3
|
+
export declare const COMPOSER_CLIPBOARD_MIME = "application/x-odn-composer-chips";
|
|
4
|
+
export interface ComposerClipboardPayload {
|
|
5
|
+
value: string;
|
|
6
|
+
chips: ChipData[];
|
|
7
|
+
}
|
|
8
|
+
export declare function buildClipboardPayload(range: Range, chips: ManagedChip[]): ComposerClipboardPayload | null;
|
|
9
|
+
export declare function parseClipboardPayload(raw: string): ComposerClipboardPayload | null;
|
|
10
|
+
/** 粘贴时重映射 chip id,避免同编辑器内 id 冲突。 */
|
|
11
|
+
export declare function remapClipboardPayload(payload: ComposerClipboardPayload): ComposerClipboardPayload;
|
|
12
|
+
export declare function plainTextFromClipboardPayload(payload: ComposerClipboardPayload): string;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
3
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
4
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
5
|
+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
6
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
7
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
8
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
9
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
|
10
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
11
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
12
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
13
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
14
|
+
import { createMarker, getPlainText, stripMarkers } from "./utils";
|
|
15
|
+
export var COMPOSER_CLIPBOARD_MIME = 'application/x-odn-composer-chips';
|
|
16
|
+
export function buildClipboardPayload(range, chips) {
|
|
17
|
+
var fragment = range.cloneContents();
|
|
18
|
+
var wrapper = document.createElement('div');
|
|
19
|
+
wrapper.appendChild(fragment);
|
|
20
|
+
var value = getPlainText(wrapper);
|
|
21
|
+
var selected = [];
|
|
22
|
+
var _iterator = _createForOfIteratorHelper(chips),
|
|
23
|
+
_step;
|
|
24
|
+
try {
|
|
25
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
26
|
+
var chip = _step.value;
|
|
27
|
+
if (range.intersectsNode(chip.host)) {
|
|
28
|
+
selected.push(chip.data);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
_iterator.e(err);
|
|
33
|
+
} finally {
|
|
34
|
+
_iterator.f();
|
|
35
|
+
}
|
|
36
|
+
if (!value && selected.length === 0) return null;
|
|
37
|
+
return {
|
|
38
|
+
value: value,
|
|
39
|
+
chips: selected
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function parseClipboardPayload(raw) {
|
|
43
|
+
try {
|
|
44
|
+
var parsed = JSON.parse(raw);
|
|
45
|
+
if (typeof parsed.value !== 'string' || !Array.isArray(parsed.chips)) return null;
|
|
46
|
+
return parsed;
|
|
47
|
+
} catch (_unused) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 粘贴时重映射 chip id,避免同编辑器内 id 冲突。 */
|
|
53
|
+
export function remapClipboardPayload(payload) {
|
|
54
|
+
var idMap = new Map();
|
|
55
|
+
var chips = payload.chips.map(function (chip) {
|
|
56
|
+
var newId = "c_".concat(Date.now(), "_").concat(Math.random().toString(36).slice(2, 8));
|
|
57
|
+
idMap.set(chip.id, newId);
|
|
58
|
+
return _objectSpread(_objectSpread({}, chip), {}, {
|
|
59
|
+
id: newId
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
var value = payload.value;
|
|
63
|
+
var _iterator2 = _createForOfIteratorHelper(idMap),
|
|
64
|
+
_step2;
|
|
65
|
+
try {
|
|
66
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
67
|
+
var _step2$value = _slicedToArray(_step2.value, 2),
|
|
68
|
+
oldId = _step2$value[0],
|
|
69
|
+
newId = _step2$value[1];
|
|
70
|
+
value = value.split(createMarker(oldId)).join(createMarker(newId));
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
_iterator2.e(err);
|
|
74
|
+
} finally {
|
|
75
|
+
_iterator2.f();
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
value: value,
|
|
79
|
+
chips: chips
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function plainTextFromClipboardPayload(payload) {
|
|
83
|
+
return stripMarkers(payload.value);
|
|
84
|
+
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - chip 状态(id ↔ host)由 useChipManager 维护
|
|
18
18
|
*/
|
|
19
19
|
/// <reference types="react" />
|
|
20
|
-
import {
|
|
20
|
+
import type { ChipData } from './chip';
|
|
21
21
|
/** Composer editor 触发态对外信号——告诉父组件该弹浮层选 skill 了。 */
|
|
22
22
|
export interface ComposerEditorTriggerInfo {
|
|
23
23
|
/** 触发字符(@ 或 /) */
|
|
@@ -86,6 +86,14 @@ export interface ComposerEditorProps {
|
|
|
86
86
|
* 若提供且 `files.length > 0`,编辑器会 `preventDefault` 且不再插入纯文本。
|
|
87
87
|
*/
|
|
88
88
|
onPasteFiles?: (files: File[]) => void;
|
|
89
|
+
/** 复制 / 剪切时回调(选区含 chip 时触发)。 */
|
|
90
|
+
onCopyChips?: (chips: ChipData[]) => void;
|
|
91
|
+
/**
|
|
92
|
+
* 禁用:contenteditable 关闭,editor 不接受输入 / 聚焦 / 粘贴。
|
|
93
|
+
* 已有的文本与 chip 仍渲染(仅冻结,不清空),便于"等接通后继续编辑"。
|
|
94
|
+
* @default false
|
|
95
|
+
*/
|
|
96
|
+
disabled?: boolean;
|
|
89
97
|
className?: string;
|
|
90
98
|
style?: React.CSSProperties;
|
|
91
99
|
}
|
package/dist/composer/editor.js
CHANGED
|
@@ -31,9 +31,11 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
|
31
31
|
|
|
32
32
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react';
|
|
33
33
|
import { createPortal } from 'react-dom';
|
|
34
|
-
import {
|
|
34
|
+
import { buildClipboardPayload, COMPOSER_CLIPBOARD_MIME, parseClipboardPayload, plainTextFromClipboardPayload, remapClipboardPayload } from "./clipboard";
|
|
35
35
|
import { useChipManager } from "./hooks/useChipManager";
|
|
36
|
-
import {
|
|
36
|
+
import { useChipSelectionMarker } from "./hooks/useChipSelectionMarker";
|
|
37
|
+
import { findChipHostBeforeCaret, getPlainText, isEditorEmpty, probeTrigger, resolveCaretJumpAroundChip, ZWSP } from "./utils";
|
|
38
|
+
import { ComposerInlineRef } from "./inline-ref";
|
|
37
39
|
import ScrollArea from "../scroll-area";
|
|
38
40
|
|
|
39
41
|
/** Composer editor 触发态对外信号——告诉父组件该弹浮层选 skill 了。 */
|
|
@@ -99,6 +101,9 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
99
101
|
onTriggerKeyDown = _ref.onTriggerKeyDown,
|
|
100
102
|
onHeightChange = _ref.onHeightChange,
|
|
101
103
|
onPasteFiles = _ref.onPasteFiles,
|
|
104
|
+
onCopyChips = _ref.onCopyChips,
|
|
105
|
+
_ref$disabled = _ref.disabled,
|
|
106
|
+
disabled = _ref$disabled === void 0 ? false : _ref$disabled,
|
|
102
107
|
className = _ref.className,
|
|
103
108
|
style = _ref.style;
|
|
104
109
|
var editorRef = useRef(null);
|
|
@@ -182,13 +187,15 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
182
187
|
* ───────────────────────────────────── */
|
|
183
188
|
var _useChipManager = useChipManager(function () {
|
|
184
189
|
fireChange();
|
|
185
|
-
// 插入 / 删除 chip 后 caret 位置变了,重算 trigger
|
|
186
190
|
recomputeTrigger();
|
|
187
191
|
}),
|
|
188
192
|
chips = _useChipManager.chips,
|
|
189
193
|
_insertChip = _useChipManager.insertChip,
|
|
194
|
+
insertSerializedAtRange = _useChipManager.insertSerializedAtRange,
|
|
195
|
+
syncChipsAfterDomMutation = _useChipManager.syncChipsAfterDomMutation,
|
|
190
196
|
removeChip = _useChipManager.removeChip,
|
|
191
197
|
resetChips = _useChipManager.resetChips;
|
|
198
|
+
useChipSelectionMarker(editorRef, chips);
|
|
192
199
|
|
|
193
200
|
/* ─────────────────────────────────────
|
|
194
201
|
* Trigger 探测
|
|
@@ -483,10 +490,45 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
483
490
|
insertLineBreak();
|
|
484
491
|
}
|
|
485
492
|
}, [onPressEnter, insertLineBreak, removeChip, onTriggerKeyDown]);
|
|
493
|
+
var writeClipboardPayload = useCallback(function (e, payload) {
|
|
494
|
+
if (!payload) return false;
|
|
495
|
+
e.clipboardData.setData(COMPOSER_CLIPBOARD_MIME, JSON.stringify(payload));
|
|
496
|
+
e.clipboardData.setData('text/plain', plainTextFromClipboardPayload(payload));
|
|
497
|
+
onCopyChips === null || onCopyChips === void 0 || onCopyChips(payload.chips);
|
|
498
|
+
return true;
|
|
499
|
+
}, [onCopyChips]);
|
|
500
|
+
var handleCopy = useCallback(function (e) {
|
|
501
|
+
var el = editorRef.current;
|
|
502
|
+
if (!el || disabled) return;
|
|
503
|
+
var sel = window.getSelection();
|
|
504
|
+
if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
|
|
505
|
+
var range = sel.getRangeAt(0);
|
|
506
|
+
if (!el.contains(range.commonAncestorContainer)) return;
|
|
507
|
+
var payload = buildClipboardPayload(range, chips);
|
|
508
|
+
if (!payload) return;
|
|
509
|
+
e.preventDefault();
|
|
510
|
+
writeClipboardPayload(e, payload);
|
|
511
|
+
}, [chips, disabled, writeClipboardPayload]);
|
|
512
|
+
var handleCut = useCallback(function (e) {
|
|
513
|
+
var el = editorRef.current;
|
|
514
|
+
if (!el || disabled) return;
|
|
515
|
+
var sel = window.getSelection();
|
|
516
|
+
if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
|
|
517
|
+
var range = sel.getRangeAt(0);
|
|
518
|
+
if (!el.contains(range.commonAncestorContainer)) return;
|
|
519
|
+
var payload = buildClipboardPayload(range, chips);
|
|
520
|
+
if (!payload) return;
|
|
521
|
+
e.preventDefault();
|
|
522
|
+
if (!writeClipboardPayload(e, payload)) return;
|
|
523
|
+
range.deleteContents();
|
|
524
|
+
syncChipsAfterDomMutation(el);
|
|
525
|
+
fireChange();
|
|
526
|
+
recomputeTrigger();
|
|
527
|
+
requestScrollCaretIntoView();
|
|
528
|
+
}, [chips, disabled, writeClipboardPayload, syncChipsAfterDomMutation, fireChange, recomputeTrigger, requestScrollCaretIntoView]);
|
|
486
529
|
|
|
487
530
|
/* ─────────────────────────────────────
|
|
488
|
-
* Paste
|
|
489
|
-
* 避免外部富文本污染 DOM;无 handler 时文件粘贴仍 preventDefault 以免整页乱入。
|
|
531
|
+
* Paste:优先私有 mime 还原 chip;纯文本走受控插入;文件交给 onPasteFiles。
|
|
490
532
|
* ───────────────────────────────────── */
|
|
491
533
|
var handlePaste = useCallback(function (e) {
|
|
492
534
|
var fileList = e.clipboardData.files;
|
|
@@ -512,13 +554,26 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
512
554
|
onPasteFiles === null || onPasteFiles === void 0 || onPasteFiles(fromItems);
|
|
513
555
|
return;
|
|
514
556
|
}
|
|
557
|
+
var el = editorRef.current;
|
|
558
|
+
if (!el) return;
|
|
559
|
+
var rawPayload = e.clipboardData.getData(COMPOSER_CLIPBOARD_MIME);
|
|
560
|
+
var parsed = rawPayload ? parseClipboardPayload(rawPayload) : null;
|
|
515
561
|
e.preventDefault();
|
|
516
|
-
var text = e.clipboardData.getData('text/plain');
|
|
517
|
-
if (!text) return;
|
|
518
562
|
var sel = window.getSelection();
|
|
519
563
|
if (!sel || sel.rangeCount === 0) return;
|
|
520
564
|
var range = sel.getRangeAt(0);
|
|
565
|
+
if (!el.contains(range.commonAncestorContainer)) return;
|
|
521
566
|
range.deleteContents();
|
|
567
|
+
if (parsed) {
|
|
568
|
+
var remapped = remapClipboardPayload(parsed);
|
|
569
|
+
insertSerializedAtRange(remapped.value, remapped.chips, range, el);
|
|
570
|
+
fireChange();
|
|
571
|
+
recomputeTrigger();
|
|
572
|
+
requestScrollCaretIntoView();
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
var text = e.clipboardData.getData('text/plain');
|
|
576
|
+
if (!text) return;
|
|
522
577
|
var node = document.createTextNode(text);
|
|
523
578
|
range.insertNode(node);
|
|
524
579
|
var newRange = document.createRange();
|
|
@@ -528,7 +583,7 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
528
583
|
sel.addRange(newRange);
|
|
529
584
|
fireChange();
|
|
530
585
|
requestScrollCaretIntoView();
|
|
531
|
-
}, [fireChange, requestScrollCaretIntoView, onPasteFiles]);
|
|
586
|
+
}, [fireChange, requestScrollCaretIntoView, onPasteFiles, insertSerializedAtRange, recomputeTrigger]);
|
|
532
587
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ScrollArea, {
|
|
533
588
|
controlRef: scrollRef,
|
|
534
589
|
autoHide: true,
|
|
@@ -538,11 +593,13 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
538
593
|
}, style)
|
|
539
594
|
}, /*#__PURE__*/React.createElement("div", {
|
|
540
595
|
ref: editorRef,
|
|
541
|
-
contentEditable:
|
|
596
|
+
contentEditable: !disabled,
|
|
542
597
|
suppressContentEditableWarning: true,
|
|
543
598
|
role: "textbox",
|
|
544
599
|
"aria-multiline": "true",
|
|
600
|
+
"aria-disabled": disabled || undefined,
|
|
545
601
|
"data-odn-composer-editor": true,
|
|
602
|
+
"data-odn-composer-editor-disabled": disabled || undefined,
|
|
546
603
|
"data-empty": empty || undefined,
|
|
547
604
|
"data-placeholder": placeholder,
|
|
548
605
|
className: className,
|
|
@@ -550,13 +607,12 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
550
607
|
onCompositionStart: handleCompositionStart,
|
|
551
608
|
onCompositionEnd: handleCompositionEnd,
|
|
552
609
|
onKeyDown: handleKeyDown,
|
|
610
|
+
onCopy: handleCopy,
|
|
611
|
+
onCut: handleCut,
|
|
553
612
|
onPaste: handlePaste
|
|
554
613
|
})), chips.map(function (chip) {
|
|
555
|
-
return /*#__PURE__*/createPortal( /*#__PURE__*/React.createElement(
|
|
556
|
-
data: chip.data
|
|
557
|
-
onRemove: function onRemove() {
|
|
558
|
-
return removeChip(chip.data.id);
|
|
559
|
-
}
|
|
614
|
+
return /*#__PURE__*/createPortal( /*#__PURE__*/React.createElement(ComposerInlineRef, {
|
|
615
|
+
data: chip.data
|
|
560
616
|
}), chip.host, chip.data.id);
|
|
561
617
|
}));
|
|
562
618
|
});
|
|
@@ -24,9 +24,14 @@ export interface UseChipManagerReturn {
|
|
|
24
24
|
* 在编辑器当前光标位置插入 chip:
|
|
25
25
|
* - selection 在编辑器内 → 插入光标处
|
|
26
26
|
* - selection 在编辑器外 / 无 selection → fallback 到末尾
|
|
27
|
-
* 插入后光标自动移到 chip 之后的 ZWSP 锚点之后,方便用户继续输入。
|
|
28
27
|
*/
|
|
29
28
|
insertChip: (data: ChipData, editor: HTMLElement) => void;
|
|
29
|
+
/** 在指定 range 处插入 chip(调用方负责 deleteContents)。 */
|
|
30
|
+
insertChipAtRange: (data: ChipData, range: Range, editor: HTMLElement) => HTMLSpanElement;
|
|
31
|
+
/** 将含 marker 的 raw value + chips 插入 range(粘贴用)。 */
|
|
32
|
+
insertSerializedAtRange: (value: string, chips: ChipData[], range: Range, editor: HTMLElement) => void;
|
|
33
|
+
/** 移除已从 DOM 摘下的 chip 状态(cut / 选区删除后)。 */
|
|
34
|
+
syncChipsAfterDomMutation: (editor: HTMLElement) => void;
|
|
30
35
|
/** 按 chip id 移除(同时清理伴随的 ZWSP 锚点)。 */
|
|
31
36
|
removeChip: (id: string) => void;
|
|
32
37
|
/** 清空所有 chip 的 React 状态(DOM 由调用方 editor.innerHTML='' 一并处理)。 */
|