im-ui-mobile 0.1.0 → 0.1.2
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/components/im-avatar/im-avatar.vue +7 -7
- package/components/im-badge/im-badge.vue +326 -0
- package/components/im-button/im-button.vue +71 -34
- package/components/im-card/im-card.vue +563 -0
- package/components/im-chat-item/im-chat-item.vue +5 -4
- package/components/im-col/im-col.vue +191 -0
- package/components/im-dialog/im-dialog.vue +543 -0
- package/components/im-double-tap-view/im-double-tap-view.vue +93 -0
- package/components/im-emoji-picker/im-emoji-picker.vue +1143 -0
- package/components/im-friend-item/im-friend-item.vue +1 -1
- package/components/im-group-item/im-group-item.vue +1 -1
- package/components/im-group-member-selector/im-group-member-selector.vue +5 -5
- package/components/im-group-rtc-join/im-group-rtc-join.vue +8 -8
- package/components/im-icon/im-icon.vue +593 -0
- package/components/im-image-upload/im-image-upload.vue +0 -2
- package/components/im-link/im-link.vue +628 -0
- package/components/im-loading/im-loading.vue +13 -4
- package/components/im-mention-picker/im-mention-picker.vue +8 -7
- package/components/im-message-action/im-message-action.vue +678 -0
- package/components/im-message-item/im-message-item.vue +28 -26
- package/components/im-message-list/im-message-list.vue +1108 -0
- package/components/im-modal/im-modal.vue +373 -0
- package/components/im-nav-bar/im-nav-bar.vue +689 -75
- package/components/im-parse/im-parse.vue +1054 -0
- package/components/im-popup/im-popup.vue +467 -0
- package/components/im-read-receipt/im-read-receipt.vue +10 -10
- package/components/im-row/im-row.vue +189 -0
- package/components/im-search/im-search.vue +762 -0
- package/components/im-sku/im-sku.vue +720 -0
- package/components/im-sku/utils/helper.ts +182 -0
- package/components/im-stepper/im-stepper.vue +585 -0
- package/components/im-stepper/utils/helper.ts +167 -0
- package/components/im-tabs/im-tabs.vue +1022 -0
- package/components/im-tabs/tabs-navigation.vue +489 -0
- package/components/im-tabs/utils/helper.ts +181 -0
- package/components/im-tabs-tab-pane/im-tabs-tab-pane.vue +145 -0
- package/components/im-upload/im-upload.vue +1236 -0
- package/components/im-voice-input/im-voice-input.vue +1 -1
- package/index.js +3 -5
- package/index.scss +19 -0
- package/libs/emoji-data.ts +229 -0
- package/libs/index.ts +16 -16
- package/package.json +1 -2
- package/styles/button.scss +33 -33
- package/theme.scss +2 -2
- package/types/components/badge.d.ts +42 -0
- package/types/components/button.d.ts +2 -1
- package/types/components/card.d.ts +122 -0
- package/types/components/col.d.ts +37 -0
- package/types/components/dialog.d.ts +125 -0
- package/types/components/double-tap-view.d.ts +31 -0
- package/types/components/emoji-picker.d.ts +121 -0
- package/types/components/group-rtc-join.d.ts +1 -1
- package/types/components/icon.d.ts +77 -0
- package/types/components/link.d.ts +55 -0
- package/types/components/loading.d.ts +1 -0
- package/types/components/message-action.d.ts +96 -0
- package/types/components/message-item.d.ts +2 -2
- package/types/components/message-list.d.ts +136 -0
- package/types/components/modal.d.ts +106 -0
- package/types/components/nav-bar.d.ts +125 -0
- package/types/components/parse.d.ts +90 -0
- package/types/components/popup.d.ts +58 -0
- package/types/components/row.d.ts +31 -0
- package/types/components/search.d.ts +54 -0
- package/types/components/sku.d.ts +195 -0
- package/types/components/stepper.d.ts +99 -0
- package/types/components/tabs-tab-pane.d.ts +27 -0
- package/types/components/tabs.d.ts +117 -0
- package/types/components/upload.d.ts +137 -0
- package/types/components.d.ts +19 -1
- package/types/index.d.ts +38 -1
- package/types/libs/index.d.ts +10 -10
- package/types/utils/base64.d.ts +5 -0
- package/types/utils/dom.d.ts +3 -0
- package/types/utils/enums.d.ts +4 -5
- package/types/utils/validator.d.ts +74 -0
- package/utils/base64.js +18 -0
- package/utils/dom.js +353 -1
- package/utils/enums.js +4 -5
- package/utils/validator.js +230 -0
- package/components/im-file-upload/im-file-upload.vue +0 -309
- package/plugins/uview-plus.js +0 -29
- package/types/components/arrow-bar.d.ts +0 -14
- package/types/components/file-upload.d.ts +0 -58
package/types/libs/index.d.ts
CHANGED
|
@@ -19,8 +19,8 @@ export interface Chat {
|
|
|
19
19
|
id?: string | number;
|
|
20
20
|
targetId: number;
|
|
21
21
|
type: 'PRIVATE' | 'GROUP';
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
displayName: string;
|
|
23
|
+
avatar: string;
|
|
24
24
|
isDnd: boolean;
|
|
25
25
|
lastContent: string;
|
|
26
26
|
lastSendTime?: number;
|
|
@@ -49,11 +49,11 @@ export interface Message {
|
|
|
49
49
|
sendNickName?: string;
|
|
50
50
|
atUserIds?: number[];
|
|
51
51
|
sendId?: number;
|
|
52
|
-
|
|
52
|
+
receiveId?: number;
|
|
53
53
|
groupId?: number;
|
|
54
54
|
receipt?: boolean;
|
|
55
55
|
receiptOk?: boolean;
|
|
56
|
-
|
|
56
|
+
readCount?: number;
|
|
57
57
|
quoteMessage?: Message;
|
|
58
58
|
fileId?: string;
|
|
59
59
|
}
|
|
@@ -63,7 +63,7 @@ export interface Message {
|
|
|
63
63
|
export interface Friend {
|
|
64
64
|
id: number;
|
|
65
65
|
nickName: string;
|
|
66
|
-
|
|
66
|
+
avatar?: string;
|
|
67
67
|
online: boolean;
|
|
68
68
|
onlineWeb: boolean;
|
|
69
69
|
onlineApp: boolean;
|
|
@@ -103,7 +103,7 @@ export interface GroupMember {
|
|
|
103
103
|
id?: number;
|
|
104
104
|
userId: number;
|
|
105
105
|
showNickName: string;
|
|
106
|
-
|
|
106
|
+
avatar?: string;
|
|
107
107
|
quit?: boolean;
|
|
108
108
|
[key: string]: any;
|
|
109
109
|
}
|
|
@@ -116,14 +116,14 @@ export interface Group {
|
|
|
116
116
|
isBanned?: boolean;
|
|
117
117
|
reason?: string;
|
|
118
118
|
name: string;
|
|
119
|
-
|
|
119
|
+
avatar: string;
|
|
120
120
|
isDnd: boolean;
|
|
121
121
|
quit?: boolean;
|
|
122
122
|
topMessage?: string;
|
|
123
123
|
memberCount?: number;
|
|
124
124
|
createTime?: number;
|
|
125
125
|
showGroupName: string;
|
|
126
|
-
|
|
126
|
+
avatarThumb?: string;
|
|
127
127
|
remarkGroupName?: string;
|
|
128
128
|
remarkNickName?: string;
|
|
129
129
|
notice?: string;
|
|
@@ -183,8 +183,8 @@ export interface RecorderFile {
|
|
|
183
183
|
export interface UserInfo {
|
|
184
184
|
id: number;
|
|
185
185
|
nickName: string;
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
avatar: string;
|
|
187
|
+
avatarThumb?: string;
|
|
188
188
|
email?: string;
|
|
189
189
|
phone?: string;
|
|
190
190
|
gender?: number;
|
package/types/utils/dom.d.ts
CHANGED
|
@@ -7,5 +7,8 @@ declare const _default: {
|
|
|
7
7
|
hasHtmlSpecialChars: (text: string) => boolean;
|
|
8
8
|
setSafeHTML: (element: HTMLElement, html: string) => void;
|
|
9
9
|
safeHTML: (strings: TemplateStringsArray, ...values: any[]) => string;
|
|
10
|
+
nodesToHtml: (nodes: any[], options: any) => string;
|
|
11
|
+
extractPlainText: (nodes: any[]) => string;
|
|
12
|
+
escapeHtml: (text: string) => string;
|
|
10
13
|
};
|
|
11
14
|
export default _default;
|
package/types/utils/enums.d.ts
CHANGED
|
@@ -8,9 +8,8 @@ export declare enum MESSAGE_TYPE {
|
|
|
8
8
|
AUDIO = 3,
|
|
9
9
|
VIDEO = 4,
|
|
10
10
|
RECALL = 10,
|
|
11
|
-
|
|
11
|
+
READ = 11,
|
|
12
12
|
RECEIPT = 12,
|
|
13
|
-
TIP_TIME = 20,
|
|
14
13
|
TIP_TEXT = 21,
|
|
15
14
|
LOADING = 30,
|
|
16
15
|
ACT_RT_VOICE = 40,
|
|
@@ -66,8 +65,8 @@ export declare enum TERMINAL_TYPE {
|
|
|
66
65
|
export declare enum MESSAGE_STATUS {
|
|
67
66
|
FAILED = -2,// 发送失败
|
|
68
67
|
SENDING = -1,// 发送中(消息没到服务器)
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
SENT = 0,// 未送达(消息已到服务器,但对方没收到)
|
|
69
|
+
RECEIVED = 1,// 已送达(对方已收到,但是未读消息)
|
|
71
70
|
RECALL = 2,// 已撤回
|
|
72
|
-
|
|
71
|
+
READ = 3 // 已读
|
|
73
72
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 密码校验选项接口
|
|
3
|
+
*/
|
|
4
|
+
export interface PasswordValidationOptions {
|
|
5
|
+
/** 最小长度,默认8 */
|
|
6
|
+
minLength?: number;
|
|
7
|
+
/** 是否要求大写字母,默认true */
|
|
8
|
+
requireUppercase?: boolean;
|
|
9
|
+
/** 是否要求小写字母,默认true */
|
|
10
|
+
requireLowercase?: boolean;
|
|
11
|
+
/** 是否要求数字,默认true */
|
|
12
|
+
requireNumbers?: boolean;
|
|
13
|
+
/** 是否要求特殊字符,默认true */
|
|
14
|
+
requireSpecialChars?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 密码校验结果接口
|
|
19
|
+
*/
|
|
20
|
+
export interface PasswordValidationResult {
|
|
21
|
+
/** 是否通过校验 */
|
|
22
|
+
isValid: boolean;
|
|
23
|
+
/** 密码强度(弱/中/强) */
|
|
24
|
+
strength: '弱' | '中' | '强';
|
|
25
|
+
/** 错误消息数组 */
|
|
26
|
+
messages: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 校验函数声明
|
|
31
|
+
*/
|
|
32
|
+
export declare function isNumber(value: string | number): boolean;
|
|
33
|
+
export declare function isInteger(value: string | number): boolean;
|
|
34
|
+
export declare function isDecimal(value: string | number, decimalPlaces?: number): boolean;
|
|
35
|
+
export declare function isPositiveInteger(value: string | number): boolean;
|
|
36
|
+
export declare function isMobilePhone(phone: string): boolean;
|
|
37
|
+
export declare function isInternationalPhone(phone: string): boolean;
|
|
38
|
+
export declare function isLandlinePhone(phone: string): boolean;
|
|
39
|
+
export declare function isEmail(email: string): boolean;
|
|
40
|
+
export declare function isEmailStrict(email: string): boolean;
|
|
41
|
+
export declare function isChineseIDCard(idCard: string): boolean;
|
|
42
|
+
export declare function isURL(url: string): boolean;
|
|
43
|
+
export declare function validatePassword(
|
|
44
|
+
password: string,
|
|
45
|
+
options?: PasswordValidationOptions
|
|
46
|
+
): PasswordValidationResult;
|
|
47
|
+
export declare function isChineseName(name: string): boolean;
|
|
48
|
+
export declare function isLicensePlate(plate: string): boolean;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 校验工具模块默认导出接口
|
|
52
|
+
*/
|
|
53
|
+
export interface ValidatorModule {
|
|
54
|
+
isNumber: typeof isNumber;
|
|
55
|
+
isInteger: typeof isInteger;
|
|
56
|
+
isDecimal: typeof isDecimal;
|
|
57
|
+
isPositiveInteger: typeof isPositiveInteger;
|
|
58
|
+
isMobilePhone: typeof isMobilePhone;
|
|
59
|
+
isInternationalPhone: typeof isInternationalPhone;
|
|
60
|
+
isLandlinePhone: typeof isLandlinePhone;
|
|
61
|
+
isEmail: typeof isEmail;
|
|
62
|
+
isEmailStrict: typeof isEmailStrict;
|
|
63
|
+
isChineseIDCard: typeof isChineseIDCard;
|
|
64
|
+
isURL: typeof isURL;
|
|
65
|
+
validatePassword: typeof validatePassword;
|
|
66
|
+
isChineseName: typeof isChineseName;
|
|
67
|
+
isLicensePlate: typeof isLicensePlate;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 默认导出
|
|
72
|
+
*/
|
|
73
|
+
declare const validator: ValidatorModule;
|
|
74
|
+
export default validator;
|
package/utils/base64.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// 编码字符串为 base64(支持中文)
|
|
2
|
+
const encodeBase64 = (str) => {
|
|
3
|
+
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => {
|
|
4
|
+
return String.fromCharCode('0x' + p1);
|
|
5
|
+
}));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// 解码 base64 字符串
|
|
9
|
+
const decodeBase64 = (str) => {
|
|
10
|
+
return decodeURIComponent(atob(str).split('').map(c => {
|
|
11
|
+
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
12
|
+
}).join(''));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default {
|
|
16
|
+
encodeBase64,
|
|
17
|
+
decodeBase64
|
|
18
|
+
}
|
package/utils/dom.js
CHANGED
|
@@ -136,6 +136,355 @@ const safeHTML = (strings, ...values) => {
|
|
|
136
136
|
return result;
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 将富文本节点数组转换回 HTML 字符串
|
|
143
|
+
* @param {Array|Object} nodes - 富文本节点数组或单个节点
|
|
144
|
+
* @param {Object} options - 转换选项
|
|
145
|
+
* @returns {string} HTML 字符串
|
|
146
|
+
*/
|
|
147
|
+
function nodesToHtml(nodes, options = {}) {
|
|
148
|
+
const defaultOptions = {
|
|
149
|
+
escapeSpecialChars: false,
|
|
150
|
+
selfClosingTags: ['img', 'br', 'hr', 'input', 'meta', 'link', 'source'],
|
|
151
|
+
keepStyle: true,
|
|
152
|
+
keepClass: true,
|
|
153
|
+
restoreLinks: true, // 是否恢复链接为a标签
|
|
154
|
+
keepDataAttributes: true, // 是否保留data-属性
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const mergedOptions = { ...defaultOptions, ...options };
|
|
158
|
+
|
|
159
|
+
// 处理节点数组
|
|
160
|
+
const nodeArray = Array.isArray(nodes) ? nodes : [nodes];
|
|
161
|
+
|
|
162
|
+
return nodeArray.map(node => processNode(node, mergedOptions)).join('');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 处理单个节点
|
|
167
|
+
* @param {Object} node - 节点对象
|
|
168
|
+
* @param {Object} options - 选项
|
|
169
|
+
* @returns {string} 处理后的HTML
|
|
170
|
+
*/
|
|
171
|
+
function processNode(node, options) {
|
|
172
|
+
if (!node) return '';
|
|
173
|
+
|
|
174
|
+
// 处理文本节点
|
|
175
|
+
if (node.type === 'text' || node.text !== undefined) {
|
|
176
|
+
const text = node.text || '';
|
|
177
|
+
return options.escapeSpecialChars ? escapeHtml(text) : text;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 处理子节点为字符串的情况
|
|
181
|
+
if (typeof node.children === 'string') {
|
|
182
|
+
const content = options.escapeSpecialChars ? escapeHtml(node.children) : node.children;
|
|
183
|
+
|
|
184
|
+
// 如果没有标签名,直接返回内容
|
|
185
|
+
if (!node.name) {
|
|
186
|
+
return content;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const attrs = processAttributes(node.attrs || {}, options);
|
|
190
|
+
|
|
191
|
+
// 检查是否是链接节点,尝试恢复为a标签
|
|
192
|
+
if (options.restoreLinks && isLinkNode(node)) {
|
|
193
|
+
return restoreLinkNode(node, content);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return `<${node.name}${attrs}>${content}</${node.name}>`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 处理子节点数组
|
|
200
|
+
const tagName = node.name || 'div';
|
|
201
|
+
const attrs = processAttributes(node.attrs || {}, options);
|
|
202
|
+
|
|
203
|
+
// 处理子节点
|
|
204
|
+
let children = '';
|
|
205
|
+
if (node.children) {
|
|
206
|
+
if (Array.isArray(node.children)) {
|
|
207
|
+
children = node.children.map(child => processNode(child, options)).join('');
|
|
208
|
+
} else {
|
|
209
|
+
children = processNode(node.children, options);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 检查是否是链接节点,尝试恢复为a标签
|
|
214
|
+
if (options.restoreLinks && isLinkNode(node)) {
|
|
215
|
+
return restoreLinkNode(node, children);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 自闭合标签处理
|
|
219
|
+
if (options.selfClosingTags.includes(tagName.toLowerCase()) && !children.trim()) {
|
|
220
|
+
return `<${tagName}${attrs} />`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return `<${tagName}${attrs}>${children}</${tagName}>`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 处理属性
|
|
228
|
+
* @param {Object} attrs - 属性对象
|
|
229
|
+
* @param {Object} options - 选项
|
|
230
|
+
* @returns {string} 属性字符串
|
|
231
|
+
*/
|
|
232
|
+
function processAttributes(attrs, options) {
|
|
233
|
+
if (!attrs || Object.keys(attrs).length === 0) {
|
|
234
|
+
return '';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const processedAttrs = [];
|
|
238
|
+
|
|
239
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
240
|
+
// 跳过某些属性
|
|
241
|
+
if (!shouldKeepAttribute(name, value, options)) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const attrValue = typeof value === 'string'
|
|
246
|
+
? escapeHtmlAttribute(value)
|
|
247
|
+
: String(value);
|
|
248
|
+
|
|
249
|
+
processedAttrs.push(`${name}="${attrValue}"`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return processedAttrs.length > 0 ? ' ' + processedAttrs.join(' ') : '';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 判断是否保留属性
|
|
257
|
+
* @param {string} name - 属性名
|
|
258
|
+
* @param {any} value - 属性值
|
|
259
|
+
* @param {Object} options - 选项
|
|
260
|
+
* @returns {boolean} 是否保留
|
|
261
|
+
*/
|
|
262
|
+
function shouldKeepAttribute(name, value, options) {
|
|
263
|
+
// 空值不处理
|
|
264
|
+
if (value === null || value === undefined || value === '') {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 根据选项过滤样式和类名
|
|
269
|
+
if (name === 'style' && !options.keepStyle) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (name === 'class' && !options.keepClass) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 处理data-属性
|
|
278
|
+
if (name.startsWith('data-')) {
|
|
279
|
+
// 如果是链接节点的data属性,保留用于恢复链接
|
|
280
|
+
if (name === 'data-link-url' || name === 'data-link-id') {
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
// 其他data属性根据选项决定
|
|
284
|
+
return options.keepDataAttributes;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* 判断是否是链接节点
|
|
292
|
+
* @param {Object} node - 节点对象
|
|
293
|
+
* @returns {boolean} 是否是链接节点
|
|
294
|
+
*/
|
|
295
|
+
function isLinkNode(node) {
|
|
296
|
+
return node.attrs && (
|
|
297
|
+
node.attrs['data-link-url'] ||
|
|
298
|
+
node.attrs.class && node.attrs.class.includes('im-parse-link')
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* 恢复链接节点为a标签
|
|
304
|
+
* @param {Object} node - 链接节点
|
|
305
|
+
* @param {string} content - 节点内容
|
|
306
|
+
* @returns {string} a标签HTML
|
|
307
|
+
*/
|
|
308
|
+
function restoreLinkNode(node, content) {
|
|
309
|
+
const attrs = node.attrs || {};
|
|
310
|
+
const url = attrs['data-link-url'];
|
|
311
|
+
|
|
312
|
+
if (!url) {
|
|
313
|
+
// 如果没有URL,返回原始节点
|
|
314
|
+
const originalAttrs = processAttributes(attrs, {});
|
|
315
|
+
return `<${node.name || 'span'}${originalAttrs}>${content}</${node.name || 'span'}>`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 解码URL
|
|
319
|
+
let decodedUrl;
|
|
320
|
+
try {
|
|
321
|
+
decodedUrl = decodeURIComponent(url);
|
|
322
|
+
} catch {
|
|
323
|
+
decodedUrl = url;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 构建a标签属性
|
|
327
|
+
const linkAttrs = {
|
|
328
|
+
href: decodedUrl,
|
|
329
|
+
style: attrs.style || '',
|
|
330
|
+
class: attrs.class ? attrs.class.replace('im-parse-link', '').trim() : '',
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const linkAttrStr = processAttributes(linkAttrs, {});
|
|
334
|
+
|
|
335
|
+
return `<a${linkAttrStr}>${content}</a>`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* 转义 HTML 特殊字符
|
|
340
|
+
* @param {string} text - 原始文本
|
|
341
|
+
* @returns {string} 转义后的文本
|
|
342
|
+
*/
|
|
343
|
+
function escapeHtml(text) {
|
|
344
|
+
const escapeMap = {
|
|
345
|
+
'&': '&',
|
|
346
|
+
'<': '<',
|
|
347
|
+
'>': '>',
|
|
348
|
+
'"': '"',
|
|
349
|
+
"'": ''',
|
|
350
|
+
'`': '`',
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
return text.replace(/[&<>"'`]/g, match => escapeMap[match] || match);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* 转义 HTML 属性值中的特殊字符
|
|
358
|
+
* @param {string} text - 原始文本
|
|
359
|
+
* @returns {string} 转义后的文本
|
|
360
|
+
*/
|
|
361
|
+
function escapeHtmlAttribute(text) {
|
|
362
|
+
return text
|
|
363
|
+
.replace(/"/g, '"')
|
|
364
|
+
.replace(/'/g, ''')
|
|
365
|
+
.replace(/&/g, '&')
|
|
366
|
+
.replace(/</g, '<')
|
|
367
|
+
.replace(/>/g, '>');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// /**
|
|
371
|
+
// * 专门用于 im-parse 组件的转换函数
|
|
372
|
+
// * @param {Array} parsedNodes - im-parse组件的parsedNodes
|
|
373
|
+
// * @param {string} originalContent - 原始HTML内容(可选)
|
|
374
|
+
// * @param {Object} options - 转换选项
|
|
375
|
+
// * @returns {string} HTML字符串
|
|
376
|
+
// */
|
|
377
|
+
// function imParseNodesToHtml(parsedNodes, originalContent = '', options = {}) {
|
|
378
|
+
// try {
|
|
379
|
+
// // 默认选项
|
|
380
|
+
// const defaultOptions = {
|
|
381
|
+
// restoreLinks: true,
|
|
382
|
+
// keepStyle: false, // 通常不需要保留im-parse添加的内联样式
|
|
383
|
+
// keepClass: false, // 通常不需要保留im-parse添加的类名
|
|
384
|
+
// escapeSpecialChars: true,
|
|
385
|
+
// };
|
|
386
|
+
|
|
387
|
+
// const mergedOptions = { ...defaultOptions, ...options };
|
|
388
|
+
|
|
389
|
+
// // 转换为HTML
|
|
390
|
+
// const html = nodesToHtml(parsedNodes, mergedOptions);
|
|
391
|
+
|
|
392
|
+
// // 如果有原始内容,可以尝试更智能的恢复
|
|
393
|
+
// if (originalContent && mergedOptions.restoreLinks) {
|
|
394
|
+
// return smartRestoreLinks(html, originalContent);
|
|
395
|
+
// }
|
|
396
|
+
|
|
397
|
+
// return html;
|
|
398
|
+
// } catch (error) {
|
|
399
|
+
// console.error('转换回 HTML 失败:', error);
|
|
400
|
+
// return originalContent || '';
|
|
401
|
+
// }
|
|
402
|
+
// }
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* 智能恢复链接(基于原始内容)
|
|
406
|
+
* @param {string} html - 转换后的HTML
|
|
407
|
+
* @param {string} original - 原始HTML
|
|
408
|
+
* @returns {string} 恢复链接后的HTML
|
|
409
|
+
*/
|
|
410
|
+
function smartRestoreLinks(html, original) {
|
|
411
|
+
// 从原始内容中提取链接信息
|
|
412
|
+
const linkMatches = [];
|
|
413
|
+
const linkRegex = /<a\s+(?:[^>]*?\s+)?href=(["'])(.*?)\1[^>]*?>([\s\S]*?)<\/a>/gi;
|
|
414
|
+
let match;
|
|
415
|
+
|
|
416
|
+
while ((match = linkRegex.exec(original)) !== null) {
|
|
417
|
+
linkMatches.push({
|
|
418
|
+
url: match[2],
|
|
419
|
+
text: match[3],
|
|
420
|
+
fullMatch: match[0]
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// 如果没有链接,直接返回
|
|
425
|
+
if (linkMatches.length === 0) {
|
|
426
|
+
return html;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// 尝试用正则替换恢复链接
|
|
430
|
+
let restoredHtml = html;
|
|
431
|
+
|
|
432
|
+
// 替换im-parse生成的链接占位符
|
|
433
|
+
linkMatches.forEach((link, index) => {
|
|
434
|
+
const placeholderRegex = new RegExp(
|
|
435
|
+
`<span[^>]*?data-link-url=["']${escapeRegExp(encodeURIComponent(link.url))}["'][^>]*?>([\\s\\S]*?)<\\/span>`,
|
|
436
|
+
'gi'
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
restoredHtml = restoredHtml.replace(placeholderRegex, () => {
|
|
440
|
+
return `<a href="${escapeHtml(link.url)}">${link.text}</a>`;
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
return restoredHtml;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* 转义正则表达式特殊字符
|
|
449
|
+
* @param {string} string - 原始字符串
|
|
450
|
+
* @returns {string} 转义后的字符串
|
|
451
|
+
*/
|
|
452
|
+
function escapeRegExp(string) {
|
|
453
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* 从im-parse节点中提取纯文本
|
|
458
|
+
* @param {Array|Object} nodes - 节点数组或单个节点
|
|
459
|
+
* @returns {string} 纯文本
|
|
460
|
+
*/
|
|
461
|
+
function extractPlainText(nodes) {
|
|
462
|
+
if (!nodes) return '';
|
|
463
|
+
|
|
464
|
+
const nodeArray = Array.isArray(nodes) ? nodes : [nodes];
|
|
465
|
+
|
|
466
|
+
return nodeArray.map(node => {
|
|
467
|
+
if (node.type === 'text' || node.text !== undefined) {
|
|
468
|
+
return node.text || '';
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (typeof node.children === 'string') {
|
|
472
|
+
return node.children;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (node.children) {
|
|
476
|
+
if (Array.isArray(node.children)) {
|
|
477
|
+
return extractPlainText(node.children);
|
|
478
|
+
}
|
|
479
|
+
return extractPlainText(node.children);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return '';
|
|
483
|
+
}).join('');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
|
|
139
488
|
export default {
|
|
140
489
|
html2Escape,
|
|
141
490
|
htmlEscape,
|
|
@@ -144,5 +493,8 @@ export default {
|
|
|
144
493
|
escapeTextContent,
|
|
145
494
|
hasHtmlSpecialChars,
|
|
146
495
|
setSafeHTML,
|
|
147
|
-
safeHTML
|
|
496
|
+
safeHTML,
|
|
497
|
+
nodesToHtml,
|
|
498
|
+
extractPlainText,
|
|
499
|
+
escapeHtml,
|
|
148
500
|
};
|
package/utils/enums.js
CHANGED
|
@@ -8,9 +8,8 @@ export const MESSAGE_TYPE = {
|
|
|
8
8
|
AUDIO: 3,
|
|
9
9
|
VIDEO: 4,
|
|
10
10
|
RECALL: 10,
|
|
11
|
-
|
|
11
|
+
READ: 11,
|
|
12
12
|
RECEIPT: 12,
|
|
13
|
-
TIP_TIME: 20,
|
|
14
13
|
TIP_TEXT: 21,
|
|
15
14
|
LOADING: 30,
|
|
16
15
|
ACT_RT_VOICE: 40,
|
|
@@ -69,8 +68,8 @@ export const TERMINAL_TYPE = {
|
|
|
69
68
|
export const MESSAGE_STATUS = {
|
|
70
69
|
FAILED: -2, // 发送失败
|
|
71
70
|
SENDING: -1, // 发送中(消息没到服务器)
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
SENT: 0, // 未送达(消息已到服务器,但对方没收到)
|
|
72
|
+
RECEIVED: 1, // 已送达(对方已收到,但是未读消息)
|
|
74
73
|
RECALL: 2, // 已撤回
|
|
75
|
-
|
|
74
|
+
READ: 3, // 消息已读
|
|
76
75
|
};
|