@alifd/chat 0.2.0-beta.1 → 0.2.0-beta.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/es/HTMLRenderer/index.d.ts +4 -0
- package/es/HTMLRenderer/index.js +138 -0
- package/es/HTMLRenderer/index.less +194 -0
- package/es/HTMLRenderer/main.scss +233 -0
- package/es/HTMLRenderer/style.d.ts +1 -0
- package/es/HTMLRenderer/style.js +1 -0
- package/es/HTMLRenderer/types.d.ts +46 -0
- package/es/HTMLRenderer/types.js +1 -0
- package/es/ImagePreview/index.d.ts +13 -0
- package/es/ImagePreview/index.js +72 -0
- package/es/ImagePreview/main.scss +15 -0
- package/es/ImagePreview/style.d.ts +1 -0
- package/es/ImagePreview/style.js +1 -0
- package/es/ImagePreview/types.d.ts +31 -0
- package/es/ImagePreview/types.js +1 -0
- package/es/feedback/types.d.ts +6 -11
- package/es/index.d.ts +3 -0
- package/es/index.js +4 -1
- package/es/markdown/index.d.ts +6 -0
- package/es/markdown/index.js +138 -0
- package/es/markdown/main.scss +153 -0
- package/es/markdown/style.d.ts +3 -0
- package/es/markdown/style.js +3 -0
- package/es/markdown/types.d.ts +21 -0
- package/es/markdown/types.js +1 -0
- package/es/utils/func.d.ts +1 -0
- package/es/utils/func.js +9 -0
- package/lib/HTMLRenderer/index.d.ts +4 -0
- package/lib/HTMLRenderer/index.js +141 -0
- package/lib/HTMLRenderer/index.less +194 -0
- package/lib/HTMLRenderer/main.scss +233 -0
- package/lib/HTMLRenderer/style.d.ts +1 -0
- package/lib/HTMLRenderer/style.js +3 -0
- package/lib/HTMLRenderer/types.d.ts +46 -0
- package/lib/HTMLRenderer/types.js +2 -0
- package/lib/ImagePreview/index.d.ts +13 -0
- package/lib/ImagePreview/index.js +75 -0
- package/lib/ImagePreview/main.scss +15 -0
- package/lib/ImagePreview/style.d.ts +1 -0
- package/lib/ImagePreview/style.js +3 -0
- package/lib/ImagePreview/types.d.ts +31 -0
- package/lib/ImagePreview/types.js +2 -0
- package/lib/feedback/types.d.ts +6 -11
- package/lib/index.d.ts +3 -0
- package/lib/index.js +8 -2
- package/lib/markdown/index.d.ts +6 -0
- package/lib/markdown/index.js +141 -0
- package/lib/markdown/main.scss +153 -0
- package/lib/markdown/style.d.ts +3 -0
- package/lib/markdown/style.js +5 -0
- package/lib/markdown/types.d.ts +21 -0
- package/lib/markdown/types.js +2 -0
- package/lib/utils/func.d.ts +1 -0
- package/lib/utils/func.js +10 -0
- package/package.json +49 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './main.scss';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './main.scss';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* @api ImagePreview
|
|
4
|
+
*/
|
|
5
|
+
export interface ImagePreviewProps extends React.HTMLAttributes<HTMLElement> {
|
|
6
|
+
/**
|
|
7
|
+
* 自定义类名
|
|
8
|
+
* @en Custom class name
|
|
9
|
+
*/
|
|
10
|
+
className?: string;
|
|
11
|
+
/**
|
|
12
|
+
* 图片的源地址
|
|
13
|
+
* @en Source URL of the image
|
|
14
|
+
*/
|
|
15
|
+
src: string;
|
|
16
|
+
/**
|
|
17
|
+
* 子元素
|
|
18
|
+
* @en Child elements
|
|
19
|
+
*/
|
|
20
|
+
children?: React.ReactElement;
|
|
21
|
+
/**
|
|
22
|
+
* 图片的宽度
|
|
23
|
+
* @en Width of the image
|
|
24
|
+
*/
|
|
25
|
+
width?: number;
|
|
26
|
+
/**
|
|
27
|
+
* 图片的高度
|
|
28
|
+
* @en Height of the image
|
|
29
|
+
*/
|
|
30
|
+
height?: number;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/es/feedback/types.d.ts
CHANGED
|
@@ -8,15 +8,6 @@ export type FeedbackStatus = 'good' | 'stale' | 'bad';
|
|
|
8
8
|
* @api
|
|
9
9
|
*/
|
|
10
10
|
export type FeedbackActiveStatus = 'good' | 'bad';
|
|
11
|
-
export interface TranslateInfo {
|
|
12
|
-
targetLang?: string;
|
|
13
|
-
icon?: string;
|
|
14
|
-
isTranslate?: boolean;
|
|
15
|
-
}
|
|
16
|
-
export interface TranslateConfig {
|
|
17
|
-
needTranslate?: boolean;
|
|
18
|
-
translateInfo?: TranslateInfo;
|
|
19
|
-
}
|
|
20
11
|
/**
|
|
21
12
|
* @api Feedback
|
|
22
13
|
*/
|
|
@@ -57,9 +48,13 @@ export interface FeedbackProps extends HTMLAttributesWeek {
|
|
|
57
48
|
*/
|
|
58
49
|
direction?: 'ver' | 'hoz';
|
|
59
50
|
/**
|
|
60
|
-
*
|
|
51
|
+
* 是否需要分享按钮
|
|
52
|
+
*/
|
|
53
|
+
needShare?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 是否需要分享按钮
|
|
61
56
|
*/
|
|
62
|
-
|
|
57
|
+
onShareClick?: () => void;
|
|
63
58
|
}
|
|
64
59
|
/**
|
|
65
60
|
* @api Feedback.Good
|
package/es/index.d.ts
CHANGED
|
@@ -13,5 +13,8 @@ export { default as Message } from './message';
|
|
|
13
13
|
export { default as Icon } from './icon';
|
|
14
14
|
export { default as Balloon } from './balloon';
|
|
15
15
|
export { default as List } from './list';
|
|
16
|
+
export { default as ImagePreview } from './ImagePreview';
|
|
17
|
+
export { default as HTMLRenderer } from './HTMLRenderer';
|
|
18
|
+
export { default as Markdown } from './markdown';
|
|
16
19
|
export { default as CardLoading } from './card-loading';
|
|
17
20
|
export declare const version: string;
|
package/es/index.js
CHANGED
|
@@ -13,5 +13,8 @@ export { default as Message } from './message';
|
|
|
13
13
|
export { default as Icon } from './icon';
|
|
14
14
|
export { default as Balloon } from './balloon';
|
|
15
15
|
export { default as List } from './list';
|
|
16
|
+
export { default as ImagePreview } from './ImagePreview';
|
|
17
|
+
export { default as HTMLRenderer } from './HTMLRenderer';
|
|
18
|
+
export { default as Markdown } from './markdown';
|
|
16
19
|
export { default as CardLoading } from './card-loading';
|
|
17
|
-
export const version = '0.2.0-beta.
|
|
20
|
+
export const version = '0.2.0-beta.2';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MarkdownProps } from './types';
|
|
3
|
+
import 'highlight.js/styles/github.css';
|
|
4
|
+
export * from './types';
|
|
5
|
+
declare const _default: import("@alifd/next/types/config-provider/types").ConfiguredComponentClass<Pick<MarkdownProps & React.RefAttributes<HTMLDivElement>, "key" | keyof MarkdownProps> & import("@alifd/next/types/config-provider/types").ComponentCommonProps, HTMLDivElement, {}>;
|
|
6
|
+
export default _default;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React, { forwardRef, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import classnames from 'classnames';
|
|
3
|
+
import MarkdownIt from 'markdown-it';
|
|
4
|
+
import HTMLRenderer from '../HTMLRenderer';
|
|
5
|
+
import { ConfigProvider } from '@alifd/next';
|
|
6
|
+
import { assignSubComponent } from '../utils';
|
|
7
|
+
import { init, transformTextWithEmoji } from '@ali/dingtalk-im-emoji';
|
|
8
|
+
import hljs from 'highlight.js'; // 引入 highlight.js
|
|
9
|
+
import 'highlight.js/styles/github.css'; // 引入代码高亮样式
|
|
10
|
+
/**
|
|
11
|
+
* @component Markdown
|
|
12
|
+
* @en Markdown
|
|
13
|
+
* @type 通用 - General
|
|
14
|
+
* @remarks Markdown 组件用于解析和渲染 Markdown 文本内容。 - Markdown component used to parse and render Markdown text content.
|
|
15
|
+
* @when 需要解析和渲染 Markdown 文本内容时使用。 - Use when you need to parse and render Markdown text content.
|
|
16
|
+
*/
|
|
17
|
+
const Markdown = forwardRef(({ className, onReady, content }, ref) => {
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
// 初始化表情库
|
|
20
|
+
init();
|
|
21
|
+
onReady === null || onReady === void 0 ? void 0 : onReady();
|
|
22
|
+
}, [onReady]);
|
|
23
|
+
const containerRef = useRef(null);
|
|
24
|
+
// 处理 HTML 标签
|
|
25
|
+
const processedContent = useMemo(() => {
|
|
26
|
+
const regex = /<font\s+([^>]+)>([^<]+)<\/font>/g;
|
|
27
|
+
return content.replace(regex, (match, attributes, text) => {
|
|
28
|
+
let sizeToken = '';
|
|
29
|
+
let colorTokenV2 = '';
|
|
30
|
+
// 提取属性
|
|
31
|
+
const sizeTokenMatch = attributes.match(/sizeToken=([\w_]+)/);
|
|
32
|
+
const colorTokenV2Match = attributes.match(/colorTokenV2=([\w_]+)/);
|
|
33
|
+
if (sizeTokenMatch) {
|
|
34
|
+
sizeToken = sizeTokenMatch[1].replace(/-/g, '_');
|
|
35
|
+
}
|
|
36
|
+
if (colorTokenV2Match) {
|
|
37
|
+
colorTokenV2 = colorTokenV2Match[1].replace(/-/g, '_');
|
|
38
|
+
}
|
|
39
|
+
// 生成样式
|
|
40
|
+
const styleParts = [];
|
|
41
|
+
if (sizeToken) {
|
|
42
|
+
styleParts.push(`font-size: var(--${sizeToken})`);
|
|
43
|
+
}
|
|
44
|
+
if (colorTokenV2) {
|
|
45
|
+
styleParts.push(`color: var(--${colorTokenV2})`);
|
|
46
|
+
}
|
|
47
|
+
const style = styleParts.join('; ');
|
|
48
|
+
return `<span style="${style}">${text}</span>`;
|
|
49
|
+
});
|
|
50
|
+
}, [content]);
|
|
51
|
+
// 转换表情符号
|
|
52
|
+
let transformedContent = useMemo(() => {
|
|
53
|
+
return transformTextWithEmoji(processedContent, {
|
|
54
|
+
genFrame: (name, url) => {
|
|
55
|
+
return {
|
|
56
|
+
htmlTag: 'img',
|
|
57
|
+
selfClose: true,
|
|
58
|
+
attr: {
|
|
59
|
+
draggable: 'false',
|
|
60
|
+
style: 'vertical-align: text-center; display: inline-block; pointer-events: none;', // 使用相对单位和 inline-block 显示
|
|
61
|
+
class: 'emoji',
|
|
62
|
+
title: '[' + name + ']',
|
|
63
|
+
src: url.url || url.staticURL,
|
|
64
|
+
alt: name,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}, [processedContent]);
|
|
70
|
+
// 渲染 Markdown
|
|
71
|
+
const html = useMemo(() => {
|
|
72
|
+
const md = new MarkdownIt({
|
|
73
|
+
html: true, // 确保允许解析 HTML 标签
|
|
74
|
+
xhtmlOut: true,
|
|
75
|
+
breaks: true,
|
|
76
|
+
langPrefix: 'language-',
|
|
77
|
+
linkify: true,
|
|
78
|
+
typographer: true,
|
|
79
|
+
highlight: (str, lang) => {
|
|
80
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
81
|
+
try {
|
|
82
|
+
return hljs.highlight(lang, str, true).value;
|
|
83
|
+
}
|
|
84
|
+
catch (__) { }
|
|
85
|
+
}
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
// console.log(transformedContent, processedContent);
|
|
90
|
+
// 这里的 </ xx> 都被转成了/ , 所以会font标签的元素 后面都会多一个 </ xx>
|
|
91
|
+
transformedContent = transformedContent.replace(/<//g, '</');
|
|
92
|
+
return md.render(transformedContent);
|
|
93
|
+
}, [transformedContent]);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (containerRef.current) {
|
|
96
|
+
// 清除现有的 .code-block-header 元素
|
|
97
|
+
const existingHeaders = containerRef.current.querySelectorAll('.code-block-header');
|
|
98
|
+
existingHeaders.forEach(header => header.remove());
|
|
99
|
+
const codeBlocks = containerRef.current.querySelectorAll('pre code');
|
|
100
|
+
codeBlocks.forEach((block) => {
|
|
101
|
+
var _a;
|
|
102
|
+
const code = block.textContent || '';
|
|
103
|
+
const pre = block.parentElement;
|
|
104
|
+
const lang = block.className.replace('language-', '') || 'plaintext';
|
|
105
|
+
// 创建代码块头部
|
|
106
|
+
const header = document.createElement('div');
|
|
107
|
+
header.className = 'code-block-header';
|
|
108
|
+
// 创建语言标签
|
|
109
|
+
const languageSpan = document.createElement('span');
|
|
110
|
+
languageSpan.className = 'code-block-language';
|
|
111
|
+
languageSpan.textContent = lang;
|
|
112
|
+
// 创建复制按钮
|
|
113
|
+
const copyButton = document.createElement('button');
|
|
114
|
+
copyButton.className = 'code-block-copy';
|
|
115
|
+
copyButton.textContent = '复制';
|
|
116
|
+
copyButton.onclick = () => {
|
|
117
|
+
navigator.clipboard.writeText(code).then(() => {
|
|
118
|
+
copyButton.textContent = '已复制';
|
|
119
|
+
setTimeout(() => {
|
|
120
|
+
copyButton.textContent = '复制';
|
|
121
|
+
}, 1000);
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
header.appendChild(languageSpan);
|
|
125
|
+
header.appendChild(copyButton);
|
|
126
|
+
// 插入头部到代码块前
|
|
127
|
+
(_a = pre === null || pre === void 0 ? void 0 : pre.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(header, pre);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}, [html]);
|
|
131
|
+
return (React.createElement("div", { ref: containerRef, className: classnames(className) },
|
|
132
|
+
React.createElement(HTMLRenderer, { imagePreview: true }, html)));
|
|
133
|
+
});
|
|
134
|
+
const MarkdownWithSub = assignSubComponent(Markdown, {
|
|
135
|
+
displayName: 'Markdown',
|
|
136
|
+
});
|
|
137
|
+
export * from './types';
|
|
138
|
+
export default ConfigProvider.config(MarkdownWithSub);
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
$primaryColor: #0080ff;
|
|
2
|
+
$alternativePrimaryColor: $primaryColor;
|
|
3
|
+
$mistPrimaryColor: fadeout($alternativePrimaryColor,93);
|
|
4
|
+
$mediumTextColor: hsla(230,60%,11%,0.7);
|
|
5
|
+
$subTextColor: hsla(230,90%,11%,0.45);
|
|
6
|
+
|
|
7
|
+
@mixin f-single-line() {
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
white-space: nowrap;
|
|
10
|
+
text-overflow: ellipsis;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.box {
|
|
14
|
+
max-width: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.box img:only-child, .box video:only-child, .box .image:only-child {
|
|
18
|
+
display: inline-block;
|
|
19
|
+
margin: 0;
|
|
20
|
+
font-size: inherit;
|
|
21
|
+
vertical-align: inherit;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.emoji {
|
|
25
|
+
width: 1em;
|
|
26
|
+
height: 1em;
|
|
27
|
+
vertical-align: text-top;
|
|
28
|
+
display: inline-block;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.title {
|
|
33
|
+
font-size: 16px;
|
|
34
|
+
font-weight: bolder;
|
|
35
|
+
margin-bottom: 12px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.text > div {
|
|
39
|
+
color:$subTextColor;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.reference {
|
|
43
|
+
margin-top: 6px;
|
|
44
|
+
font-size: 12px;
|
|
45
|
+
|
|
46
|
+
&-tip {
|
|
47
|
+
display: flex;
|
|
48
|
+
margin: 0 -16px 6px 0;
|
|
49
|
+
justify-content: flex-end;
|
|
50
|
+
|
|
51
|
+
&-inner {
|
|
52
|
+
@include f-single-line();
|
|
53
|
+
background: linear-gradient(to right, transparent, $mistPrimaryColor);
|
|
54
|
+
padding: 0 16px 0 32px;
|
|
55
|
+
color: $mediumTextColor;
|
|
56
|
+
min-width: 0;
|
|
57
|
+
flex: 0 1 auto;
|
|
58
|
+
font-size: 12px;
|
|
59
|
+
line-height: 20px;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.trustworthyRiskAlert {
|
|
65
|
+
font-size: 12px;
|
|
66
|
+
line-height: 18px;
|
|
67
|
+
color: $subTextColor;
|
|
68
|
+
margin-bottom: 8px;
|
|
69
|
+
}
|
|
70
|
+
.code-block-header {
|
|
71
|
+
display: flex;
|
|
72
|
+
height:24px;
|
|
73
|
+
// margin-top: 8px;
|
|
74
|
+
justify-content: space-between;
|
|
75
|
+
align-items: center;
|
|
76
|
+
background-color: #EAECED;
|
|
77
|
+
padding: 5px 10px;
|
|
78
|
+
border-top-left-radius: 4px;
|
|
79
|
+
border-top-right-radius: 4px;
|
|
80
|
+
border-bottom: 1px solid #ddd;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.code-block-language {
|
|
84
|
+
font-weight: bold;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.code-block-copy {
|
|
88
|
+
color: var(--common_level3_base_color);
|
|
89
|
+
border: none;
|
|
90
|
+
border-radius: 4px;
|
|
91
|
+
height: 20px;
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pre {
|
|
96
|
+
background-color: #f8f8f8;
|
|
97
|
+
border-bottom-left-radius: 14px;
|
|
98
|
+
border-bottom-right-radius: 14px;
|
|
99
|
+
padding: 10px;
|
|
100
|
+
margin-bottom: 10px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.code-block-copy:hover {
|
|
104
|
+
background-color: #d2d2d2;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
:root {
|
|
108
|
+
--common_hypertitle_text_style__font_size: 64px;
|
|
109
|
+
--common_largetitle_text_style__font_size: 32px;
|
|
110
|
+
--common_h1_text_style__font_size: 24px;
|
|
111
|
+
--common_h2_text_style__font_size: 20px;
|
|
112
|
+
--common_h3_text_style__font_size: 18px;
|
|
113
|
+
--common_h4_text_style__font_size: 16px;
|
|
114
|
+
--common_h5_text_style__font_size: 15px;
|
|
115
|
+
--common_body_text_style__font_size: 14px;
|
|
116
|
+
--common_footnote_text_style__font_size: 12px;
|
|
117
|
+
|
|
118
|
+
--common_yellow1_color: #e5c442;
|
|
119
|
+
--common_orange1_color: #f1a13b;
|
|
120
|
+
--common_red1_color: #ec6033;
|
|
121
|
+
--common_pink1_color: #da5388;
|
|
122
|
+
--common_purple1_color: #af68cd;
|
|
123
|
+
--common_blue1_color: #387df6;
|
|
124
|
+
--common_water1_color: #79c6f5;
|
|
125
|
+
--common_olive1_color: #77912b;
|
|
126
|
+
--common_green1_color: #4fae51;
|
|
127
|
+
--common_level1_base_color: #1a1a1f;
|
|
128
|
+
--common_level2_base_color: #777578;
|
|
129
|
+
--common_level3_base_color: #a2a3a5;
|
|
130
|
+
--common_level4_base_color: #c8c8c9;
|
|
131
|
+
--common_gray1_color: #888f94;
|
|
132
|
+
--common_gray2_color: #abafb3;
|
|
133
|
+
--common_gray3_color: #c4c7ca;
|
|
134
|
+
--common_gray4_color: #696969;
|
|
135
|
+
--common_gray5_color: #e5e6e8;
|
|
136
|
+
--common_gray6_color: #f0f1f2;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//暗黑模式的变量设置
|
|
140
|
+
@media (prefers-color-scheme: dark), (min-width: 0px) {
|
|
141
|
+
:root {
|
|
142
|
+
--common_level4_base_color:#47484a;
|
|
143
|
+
--common_gray1_color:#bebebe;
|
|
144
|
+
--common_gray2_color:#646464;
|
|
145
|
+
--common_gray3_color:#4b4b4b;
|
|
146
|
+
--common_gray4_color:#3a3a3a;
|
|
147
|
+
--common_gray5_color:#2c2c2c;
|
|
148
|
+
--common_gray6_color:#1e1e1e;
|
|
149
|
+
--common_level1_base_color:#D1D1D1;
|
|
150
|
+
--common_level2_base_color: #8C8C8D;
|
|
151
|
+
--common_level3_base_color:#757577;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* @api Markdown
|
|
4
|
+
*/
|
|
5
|
+
export interface MarkdownProps extends React.HTMLAttributes<HTMLElement> {
|
|
6
|
+
/**
|
|
7
|
+
* 自定义类名
|
|
8
|
+
* @en Custom class name
|
|
9
|
+
*/
|
|
10
|
+
className?: string;
|
|
11
|
+
/**
|
|
12
|
+
* 组件准备就绪的回调函数
|
|
13
|
+
* @en Callback function when the component is ready
|
|
14
|
+
*/
|
|
15
|
+
onReady?: () => void;
|
|
16
|
+
/**
|
|
17
|
+
* Markdown 文本
|
|
18
|
+
* @en Data object
|
|
19
|
+
*/
|
|
20
|
+
content: string;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/es/utils/func.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { type AnyFunction } from './types';
|
|
2
2
|
export declare function debounce<Callback extends AnyFunction>(callback: Callback, delay: number, leading?: boolean): (...args: Parameters<Callback>) => void;
|
|
3
3
|
export declare function throttle<Callback extends AnyFunction>(callback: Callback, delay: number): (...args: Parameters<Callback>) => void;
|
|
4
|
+
export declare function findAncestor(element: HTMLElement | null, where: (e: HTMLElement) => boolean | undefined): HTMLElement | null;
|
package/es/utils/func.js
CHANGED
|
@@ -28,3 +28,12 @@ export function throttle(callback, delay) {
|
|
|
28
28
|
};
|
|
29
29
|
return fn;
|
|
30
30
|
}
|
|
31
|
+
export function findAncestor(element, where) {
|
|
32
|
+
if (!element || element === document.body) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (where(element)) {
|
|
36
|
+
return element;
|
|
37
|
+
}
|
|
38
|
+
return findAncestor(element.parentElement, where);
|
|
39
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { HTMLRendererProps } from './types';
|
|
2
|
+
export * from './types';
|
|
3
|
+
declare const _default: import("@alifd/next/types/config-provider/types").ConfiguredComponentClass<HTMLRendererProps & import("@alifd/next/types/config-provider/types").ComponentCommonProps, unknown, {}>;
|
|
4
|
+
export default _default;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
/**
|
|
5
|
+
* @component HTML 渲染器
|
|
6
|
+
* @en HTML Renderer
|
|
7
|
+
* @type 通用 - General
|
|
8
|
+
* @remarks HTML 渲染组件用于解析和渲染 HTML 字符串,同时提供图片预览、链接处理和文本复制等功能。 - HTML renderer component used to parse and render HTML strings, with additional features such as image zoom, link handling, text copying and so on.
|
|
9
|
+
* @when 需要解析和渲染 HTML 字符串时使用。 - Use when you need to parse and render HTML strings.
|
|
10
|
+
*/
|
|
11
|
+
const react_1 = tslib_1.__importStar(require("react"));
|
|
12
|
+
const classnames_1 = tslib_1.__importDefault(require("classnames"));
|
|
13
|
+
const sanitize_html_1 = tslib_1.__importDefault(require("sanitize-html"));
|
|
14
|
+
const html_react_parser_1 = tslib_1.__importStar(require("html-react-parser"));
|
|
15
|
+
const react_photo_view_1 = require("react-photo-view");
|
|
16
|
+
const query_string_1 = tslib_1.__importDefault(require("query-string"));
|
|
17
|
+
const ImagePreview_1 = tslib_1.__importDefault(require("../ImagePreview"));
|
|
18
|
+
const next_1 = require("@alifd/next");
|
|
19
|
+
const utils_1 = require("../utils");
|
|
20
|
+
const sanitizeHtmlOptions = {
|
|
21
|
+
allowedTags: sanitize_html_1.default.defaults.allowedTags.concat([
|
|
22
|
+
'acronym', 'audio', 'big', 'center', 'del', 'dir', 'font',
|
|
23
|
+
'img', 'ins', 'source', 'strike', 'track', 'tt', 'video',
|
|
24
|
+
]),
|
|
25
|
+
allowedAttributes: {
|
|
26
|
+
'*': ['data-*', 'title', 'align', 'bgcolor', 'class', 'style'],
|
|
27
|
+
a: ['href', 'download'],
|
|
28
|
+
audio: ['controls', 'crossorigin', 'loop', 'muted', 'preload', 'src'],
|
|
29
|
+
br: ['clear'],
|
|
30
|
+
caption: ['align'],
|
|
31
|
+
col: ['span'],
|
|
32
|
+
colgroup: ['span'],
|
|
33
|
+
font: ['color', 'face', 'size'],
|
|
34
|
+
hr: ['color', 'noshade', 'size'],
|
|
35
|
+
img: ['alt', 'src', 'crossorigin', 'decoding', 'sizes', 'srcset', 'align', 'width', 'height'],
|
|
36
|
+
li: ['value', 'type'],
|
|
37
|
+
ol: ['reversed', 'start', 'type'],
|
|
38
|
+
source: ['sizes', 'src', 'srcset', 'type', 'media'],
|
|
39
|
+
table: ['border', 'cellpadding', 'cellspacing', 'frame', 'rules', 'summary'],
|
|
40
|
+
td: ['headers', 'colspan', 'rowspan'],
|
|
41
|
+
th: ['headers', 'colspan', 'rowspan'],
|
|
42
|
+
track: ['default', 'kind', 'label', 'src', 'srclang'],
|
|
43
|
+
ul: ['type'],
|
|
44
|
+
video: ['controls', 'crossorigin', 'loop', 'muted', 'preload', 'src', 'poster', 'width', 'height'],
|
|
45
|
+
},
|
|
46
|
+
enforceHtmlBoundary: false,
|
|
47
|
+
disallowedTagsMode: 'discard',
|
|
48
|
+
allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'tel', 'message', 'copy', 'dingtalk', 'dtmd'],
|
|
49
|
+
allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],
|
|
50
|
+
allowProtocolRelative: false,
|
|
51
|
+
};
|
|
52
|
+
const HTMLRenderer = (0, react_1.memo)(function HTMLRenderer({ className, children, imagePreview, loose, handleOpenLink, sendTextMessage, copyText, i18n }) {
|
|
53
|
+
// 处理钉钉特定链接
|
|
54
|
+
function handleDingtalkUrl(href) {
|
|
55
|
+
return () => {
|
|
56
|
+
const { query } = query_string_1.default.parseUrl(href);
|
|
57
|
+
const { content, url, _assistant_adaptation, context } = query;
|
|
58
|
+
if (_assistant_adaptation === 'false') {
|
|
59
|
+
delete query._assistant_adaptation;
|
|
60
|
+
const newUrl = query_string_1.default.stringifyUrl({ url: href, query }, { sort: false });
|
|
61
|
+
handleOpenLink === null || handleOpenLink === void 0 ? void 0 : handleOpenLink(newUrl);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let contextObj;
|
|
65
|
+
try {
|
|
66
|
+
contextObj = JSON.parse(context || '');
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
contextObj = {};
|
|
70
|
+
}
|
|
71
|
+
if (contextObj.allowToDingtalk && contextObj.allowToDingtalk === true) {
|
|
72
|
+
handleOpenLink === null || handleOpenLink === void 0 ? void 0 : handleOpenLink(href);
|
|
73
|
+
}
|
|
74
|
+
else if (content) {
|
|
75
|
+
sendTextMessage === null || sendTextMessage === void 0 ? void 0 : sendTextMessage(`${content}`);
|
|
76
|
+
}
|
|
77
|
+
else if (url) {
|
|
78
|
+
handleOpenLink === null || handleOpenLink === void 0 ? void 0 : handleOpenLink(url === null || url === void 0 ? void 0 : url.toString());
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
handleOpenLink === null || handleOpenLink === void 0 ? void 0 : handleOpenLink(href);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const parserOptions = {
|
|
86
|
+
replace: domNode => {
|
|
87
|
+
if (domNode instanceof html_react_parser_1.Element && domNode.attribs) {
|
|
88
|
+
const { name } = domNode;
|
|
89
|
+
if (name === 'a') {
|
|
90
|
+
const element = ((0, html_react_parser_1.domToReact)([domNode]));
|
|
91
|
+
const { props } = element;
|
|
92
|
+
if (props.href && props.href.startsWith('message://')) {
|
|
93
|
+
const msgValue = decodeURIComponent(props.href.slice(10));
|
|
94
|
+
return react_1.default.cloneElement(element, {
|
|
95
|
+
onClick: () => sendTextMessage === null || sendTextMessage === void 0 ? void 0 : sendTextMessage(msgValue),
|
|
96
|
+
href: undefined
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (props.href && props.href.startsWith('copy://')) {
|
|
100
|
+
const copyValue = decodeURIComponent(props.href.slice(7));
|
|
101
|
+
return react_1.default.cloneElement(element, {
|
|
102
|
+
onClick: copyText === null || copyText === void 0 ? void 0 : copyText(copyValue, i18n),
|
|
103
|
+
href: undefined
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (props.href && /^dtmd:\/\/dingtalkclient|^dingtalk:\/\/dingtalkclient\/action\/jumprobot|^(https?:)?\/\/qr.dingtalk.com\/action\/jumprobot/.test(props.href)) {
|
|
107
|
+
return react_1.default.cloneElement(element, {
|
|
108
|
+
onClick: handleDingtalkUrl(props.href),
|
|
109
|
+
href: undefined
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return react_1.default.cloneElement(element, {
|
|
113
|
+
target: '_blank',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (name === 'img') {
|
|
117
|
+
const element = ((0, html_react_parser_1.domToReact)([domNode]));
|
|
118
|
+
if (imagePreview) {
|
|
119
|
+
return react_1.default.createElement(ImagePreview_1.default, { className: "image", src: element.props.src }, element);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
return element;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const element = (0, react_1.useMemo)(() => {
|
|
129
|
+
return (0, html_react_parser_1.default)((0, sanitize_html_1.default)(children || '', sanitizeHtmlOptions), parserOptions);
|
|
130
|
+
}, [children]);
|
|
131
|
+
return (react_1.default.createElement("div", { className: (0, classnames_1.default)('box', className, {
|
|
132
|
+
'loose': loose,
|
|
133
|
+
}) }, imagePreview ?
|
|
134
|
+
react_1.default.createElement(react_photo_view_1.PhotoProvider, null, element)
|
|
135
|
+
: element));
|
|
136
|
+
});
|
|
137
|
+
const HTMLRendererWithSub = (0, utils_1.assignSubComponent)(HTMLRenderer, {
|
|
138
|
+
displayName: 'HTMLRenderer',
|
|
139
|
+
});
|
|
140
|
+
tslib_1.__exportStar(require("./types"), exports);
|
|
141
|
+
exports.default = next_1.ConfigProvider.config(HTMLRendererWithSub);
|