@h-ai/ui 0.1.0-alpha.29 → 0.1.0-alpha.31
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/README.md +50 -3
- package/dist/components/compounds/Accordion.svelte +6 -3
- package/dist/components/compounds/Accordion.svelte.d.ts +3 -1
- package/dist/components/compounds/ActionSheet.svelte +11 -8
- package/dist/components/compounds/ActionSheet.svelte.d.ts +3 -1
- package/dist/components/compounds/Alert.svelte +6 -4
- package/dist/components/compounds/Alert.svelte.d.ts +3 -2
- package/dist/components/compounds/AppBar.svelte +13 -9
- package/dist/components/compounds/AppBar.svelte.d.ts +3 -1
- package/dist/components/compounds/BottomNav.svelte +13 -10
- package/dist/components/compounds/BottomNav.svelte.d.ts +3 -1
- package/dist/components/compounds/Breadcrumb.svelte +6 -4
- package/dist/components/compounds/Breadcrumb.svelte.d.ts +3 -2
- package/dist/components/compounds/Calendar.svelte +7 -2
- package/dist/components/compounds/Calendar.svelte.d.ts +3 -1
- package/dist/components/compounds/Card.svelte +6 -4
- package/dist/components/compounds/Card.svelte.d.ts +3 -2
- package/dist/components/compounds/Combobox.svelte +6 -2
- package/dist/components/compounds/Combobox.svelte.d.ts +3 -1
- package/dist/components/compounds/Confirm.svelte +9 -7
- package/dist/components/compounds/Confirm.svelte.d.ts +3 -2
- package/dist/components/compounds/DataTable.svelte +6 -2
- package/dist/components/compounds/DataTable.svelte.d.ts +2 -1
- package/dist/components/compounds/DatePicker.svelte +7 -2
- package/dist/components/compounds/DatePicker.svelte.d.ts +3 -1
- package/dist/components/compounds/Drawer.svelte +6 -4
- package/dist/components/compounds/Drawer.svelte.d.ts +3 -2
- package/dist/components/compounds/Dropdown.svelte +6 -4
- package/dist/components/compounds/Dropdown.svelte.d.ts +3 -2
- package/dist/components/compounds/Empty.svelte +6 -4
- package/dist/components/compounds/Empty.svelte.d.ts +3 -2
- package/dist/components/compounds/Form.svelte +11 -9
- package/dist/components/compounds/Form.svelte.d.ts +3 -2
- package/dist/components/compounds/FormField.svelte +6 -4
- package/dist/components/compounds/FormField.svelte.d.ts +3 -2
- package/dist/components/compounds/InfiniteScroll.svelte +10 -7
- package/dist/components/compounds/InfiniteScroll.svelte.d.ts +3 -1
- package/dist/components/compounds/MessageBoxContainer.svelte +6 -1
- package/dist/components/compounds/MessageBoxContainer.svelte.d.ts +3 -17
- package/dist/components/compounds/MessageBoxDialog.svelte +9 -7
- package/dist/components/compounds/MessageBoxDialog.svelte.d.ts +2 -1
- package/dist/components/compounds/MessageContainer.svelte +43 -66
- package/dist/components/compounds/MessageContainer.svelte.d.ts +2 -1
- package/dist/components/compounds/Modal.svelte +29 -43
- package/dist/components/compounds/Modal.svelte.d.ts +3 -2
- package/dist/components/compounds/PageHeader.svelte +6 -2
- package/dist/components/compounds/PageHeader.svelte.d.ts +3 -1
- package/dist/components/compounds/Pagination.svelte +6 -4
- package/dist/components/compounds/Pagination.svelte.d.ts +3 -2
- package/dist/components/compounds/Popover.svelte +10 -8
- package/dist/components/compounds/Popover.svelte.d.ts +3 -2
- package/dist/components/compounds/PullRefresh.svelte +11 -8
- package/dist/components/compounds/PullRefresh.svelte.d.ts +3 -1
- package/dist/components/compounds/Result.svelte +6 -4
- package/dist/components/compounds/Result.svelte.d.ts +3 -2
- package/dist/components/compounds/SafeArea.svelte +6 -3
- package/dist/components/compounds/SafeArea.svelte.d.ts +3 -1
- package/dist/components/compounds/Skeleton.svelte +6 -4
- package/dist/components/compounds/Skeleton.svelte.d.ts +3 -2
- package/dist/components/compounds/Steps.svelte +6 -4
- package/dist/components/compounds/Steps.svelte.d.ts +3 -2
- package/dist/components/compounds/SwipeCell.svelte +6 -3
- package/dist/components/compounds/SwipeCell.svelte.d.ts +3 -1
- package/dist/components/compounds/Tabs.svelte +6 -4
- package/dist/components/compounds/Tabs.svelte.d.ts +3 -2
- package/dist/components/compounds/TagInput.svelte +11 -9
- package/dist/components/compounds/TagInput.svelte.d.ts +3 -2
- package/dist/components/compounds/Timeline.svelte +6 -3
- package/dist/components/compounds/Timeline.svelte.d.ts +3 -1
- package/dist/components/compounds/ToastContainer.svelte +6 -2
- package/dist/components/compounds/ToastContainer.svelte.d.ts +2 -1
- package/dist/components/compounds/Tooltip.svelte +6 -4
- package/dist/components/compounds/Tooltip.svelte.d.ts +3 -2
- package/dist/components/primitives/Avatar.svelte +6 -4
- package/dist/components/primitives/Avatar.svelte.d.ts +3 -2
- package/dist/components/primitives/Badge.svelte +6 -4
- package/dist/components/primitives/Badge.svelte.d.ts +3 -2
- package/dist/components/primitives/BareButton.svelte +15 -12
- package/dist/components/primitives/BareButton.svelte.d.ts +3 -2
- package/dist/components/primitives/BareInput.svelte +37 -34
- package/dist/components/primitives/BareInput.svelte.d.ts +3 -2
- package/dist/components/primitives/Button.svelte +12 -10
- package/dist/components/primitives/Button.svelte.d.ts +3 -2
- package/dist/components/primitives/Checkbox.svelte +6 -4
- package/dist/components/primitives/Checkbox.svelte.d.ts +3 -2
- package/dist/components/primitives/IconButton.svelte +6 -4
- package/dist/components/primitives/IconButton.svelte.d.ts +3 -2
- package/dist/components/primitives/Input.svelte +6 -4
- package/dist/components/primitives/Input.svelte.d.ts +3 -2
- package/dist/components/primitives/Progress.svelte +6 -4
- package/dist/components/primitives/Progress.svelte.d.ts +3 -2
- package/dist/components/primitives/Radio.svelte +6 -4
- package/dist/components/primitives/Radio.svelte.d.ts +2 -2
- package/dist/components/primitives/Range.svelte +6 -3
- package/dist/components/primitives/Range.svelte.d.ts +3 -1
- package/dist/components/primitives/Rating.svelte +6 -3
- package/dist/components/primitives/Rating.svelte.d.ts +3 -1
- package/dist/components/primitives/Select.svelte +255 -48
- package/dist/components/primitives/Select.svelte.d.ts +2 -2
- package/dist/components/primitives/Spinner.svelte +6 -4
- package/dist/components/primitives/Spinner.svelte.d.ts +3 -2
- package/dist/components/primitives/Switch.svelte +6 -4
- package/dist/components/primitives/Switch.svelte.d.ts +3 -2
- package/dist/components/primitives/Tag.svelte +6 -4
- package/dist/components/primitives/Tag.svelte.d.ts +3 -2
- package/dist/components/primitives/Textarea.svelte +34 -19
- package/dist/components/primitives/Textarea.svelte.d.ts +3 -2
- package/dist/components/primitives/ToggleCheckbox.svelte +13 -10
- package/dist/components/primitives/ToggleCheckbox.svelte.d.ts +3 -2
- package/dist/components/primitives/ToggleInput.svelte +13 -10
- package/dist/components/primitives/ToggleInput.svelte.d.ts +3 -2
- package/dist/components/primitives/ToggleRadio.svelte +13 -10
- package/dist/components/primitives/ToggleRadio.svelte.d.ts +3 -2
- package/dist/components/scenes/ai/AiDocumentDownloadMenu.svelte +8 -5
- package/dist/components/scenes/ai/AiDocumentDownloadMenu.svelte.d.ts +3 -1
- package/dist/components/scenes/ai/AiDocumentEditor.svelte +40 -10
- package/dist/components/scenes/ai/AiDocumentEditor.svelte.d.ts +3 -1
- package/dist/components/scenes/ai/MarkdownRenderer.svelte +49 -7
- package/dist/components/scenes/ai/MarkdownRenderer.svelte.d.ts +3 -1
- package/dist/components/scenes/ai/document-parse.d.ts +2 -0
- package/dist/components/scenes/ai/document-parse.js +16 -17
- package/dist/components/scenes/ai/document-types.d.ts +6 -1
- package/dist/components/scenes/ai/markdown-parse.d.ts +2 -0
- package/dist/components/scenes/ai/markdown-parse.js +17 -20
- package/dist/components/scenes/ai/safe-html.d.ts +16 -0
- package/dist/components/scenes/ai/safe-html.js +284 -0
- package/dist/components/scenes/ai/table/AiTableEditor.svelte +6 -3
- package/dist/components/scenes/ai/table/AiTableEditor.svelte.d.ts +3 -1
- package/dist/components/scenes/ai/table/table-types.d.ts +2 -1
- package/dist/components/scenes/app/FeedbackModal.svelte +5 -2
- package/dist/components/scenes/app/FeedbackModal.svelte.d.ts +3 -1
- package/dist/components/scenes/app/LanguageSwitch.svelte +6 -2
- package/dist/components/scenes/app/LanguageSwitch.svelte.d.ts +3 -1
- package/dist/components/scenes/app/SettingsLayout.svelte +6 -3
- package/dist/components/scenes/app/SettingsLayout.svelte.d.ts +2 -1
- package/dist/components/scenes/app/SettingsModal.svelte +6 -3
- package/dist/components/scenes/app/SettingsModal.svelte.d.ts +3 -1
- package/dist/components/scenes/app/ThemeColorPicker.svelte +6 -3
- package/dist/components/scenes/app/ThemeColorPicker.svelte.d.ts +3 -1
- package/dist/components/scenes/app/ThemeSelector.svelte +6 -3
- package/dist/components/scenes/app/ThemeSelector.svelte.d.ts +3 -1
- package/dist/components/scenes/app/ThemeToggle.svelte +6 -2
- package/dist/components/scenes/app/ThemeToggle.svelte.d.ts +3 -1
- package/dist/components/scenes/crud/CrudDeleteConfirm.svelte +16 -11
- package/dist/components/scenes/crud/CrudDeleteConfirm.svelte.d.ts +2 -1
- package/dist/components/scenes/crud/CrudDetailDrawer.svelte +6 -3
- package/dist/components/scenes/crud/CrudDetailDrawer.svelte.d.ts +2 -2
- package/dist/components/scenes/crud/CrudDetailPanel.svelte +6 -3
- package/dist/components/scenes/crud/CrudDetailPanel.svelte.d.ts +2 -2
- package/dist/components/scenes/crud/CrudEditPanel.svelte +6 -3
- package/dist/components/scenes/crud/CrudEditPanel.svelte.d.ts +2 -2
- package/dist/components/scenes/crud/CrudFilterBar.svelte +6 -2
- package/dist/components/scenes/crud/CrudFilterBar.svelte.d.ts +2 -1
- package/dist/components/scenes/crud/CrudPage.svelte +6 -2
- package/dist/components/scenes/crud/CrudPage.svelte.d.ts +2 -1
- package/dist/components/scenes/crud/crud-types.d.ts +2 -2
- package/dist/components/scenes/crypto/EncryptedInput.svelte +6 -3
- package/dist/components/scenes/crypto/EncryptedInput.svelte.d.ts +3 -2
- package/dist/components/scenes/crypto/HashDisplay.svelte +6 -3
- package/dist/components/scenes/crypto/HashDisplay.svelte.d.ts +3 -1
- package/dist/components/scenes/crypto/SignatureDisplay.svelte +6 -3
- package/dist/components/scenes/crypto/SignatureDisplay.svelte.d.ts +3 -1
- package/dist/components/scenes/error/ErrorPage.svelte +6 -3
- package/dist/components/scenes/error/ErrorPage.svelte.d.ts +2 -1
- package/dist/components/scenes/iam/AuthShell.svelte +6 -3
- package/dist/components/scenes/iam/AuthShell.svelte.d.ts +2 -1
- package/dist/components/scenes/iam/ChangePasswordForm.svelte +6 -3
- package/dist/components/scenes/iam/ChangePasswordForm.svelte.d.ts +3 -1
- package/dist/components/scenes/iam/ForgotPasswordForm.svelte +6 -3
- package/dist/components/scenes/iam/ForgotPasswordForm.svelte.d.ts +3 -1
- package/dist/components/scenes/iam/LoginForm.svelte +6 -3
- package/dist/components/scenes/iam/LoginForm.svelte.d.ts +3 -1
- package/dist/components/scenes/iam/PasswordInput.svelte +6 -4
- package/dist/components/scenes/iam/PasswordInput.svelte.d.ts +3 -2
- package/dist/components/scenes/iam/RegisterForm.svelte +6 -4
- package/dist/components/scenes/iam/RegisterForm.svelte.d.ts +3 -1
- package/dist/components/scenes/iam/ResetPasswordForm.svelte +6 -3
- package/dist/components/scenes/iam/ResetPasswordForm.svelte.d.ts +3 -1
- package/dist/components/scenes/iam/UserProfile.svelte +6 -3
- package/dist/components/scenes/iam/UserProfile.svelte.d.ts +3 -1
- package/dist/components/scenes/storage/AvatarUpload.svelte +11 -8
- package/dist/components/scenes/storage/AvatarUpload.svelte.d.ts +3 -1
- package/dist/components/scenes/storage/FileList.svelte +6 -3
- package/dist/components/scenes/storage/FileList.svelte.d.ts +3 -1
- package/dist/components/scenes/storage/FileUpload.svelte +6 -3
- package/dist/components/scenes/storage/FileUpload.svelte.d.ts +3 -1
- package/dist/components/scenes/storage/ImageUpload.svelte +12 -9
- package/dist/components/scenes/storage/ImageUpload.svelte.d.ts +3 -1
- package/dist/components/scenes/types.d.ts +20 -16
- package/dist/styles/global.css +5 -0
- package/dist/types.d.ts +48 -41
- package/dist/utils.d.ts +6 -1
- package/dist/utils.js +16 -1
- package/package.json +2 -2
|
@@ -8,21 +8,14 @@
|
|
|
8
8
|
import { Marked } from 'marked';
|
|
9
9
|
import { createEditorMarkdownExtensions } from './editor-markdown-extensions.js';
|
|
10
10
|
import { highlightCode, isLanguageSupported } from './highlight.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function escapeHtml(text) {
|
|
20
|
-
return text
|
|
21
|
-
.replace(/&/g, '&')
|
|
22
|
-
.replace(/</g, '<')
|
|
23
|
-
.replace(/>/g, '>')
|
|
24
|
-
.replace(/"/g, '"')
|
|
25
|
-
.replace(/'/g, ''');
|
|
11
|
+
import { escapeHtml, isExternalLinkHref, sanitizeImageSrc, sanitizeLinkHref, sanitizeMarkdownHtml, } from './safe-html.js';
|
|
12
|
+
function readHtmlTokenSource(token) {
|
|
13
|
+
if ('raw' in token && typeof token.raw === 'string') {
|
|
14
|
+
return token.raw;
|
|
15
|
+
}
|
|
16
|
+
return 'text' in token && typeof token.text === 'string'
|
|
17
|
+
? token.text
|
|
18
|
+
: '';
|
|
26
19
|
}
|
|
27
20
|
function createTableCellAttrs(cell) {
|
|
28
21
|
const attrs = [];
|
|
@@ -69,14 +62,17 @@ function createRendererObject(options) {
|
|
|
69
62
|
},
|
|
70
63
|
// 原始 HTML 块:转义以防止 XSS
|
|
71
64
|
html(token) {
|
|
72
|
-
|
|
65
|
+
const source = readHtmlTokenSource(token);
|
|
66
|
+
return options.allowHtmlTags
|
|
67
|
+
? sanitizeMarkdownHtml(source)
|
|
68
|
+
: escapeHtml(source);
|
|
73
69
|
},
|
|
74
70
|
// 链接:外部链接自动添加 target="_blank",阻止危险协议
|
|
75
71
|
link({ href, title, tokens }) {
|
|
76
72
|
const text = this.parser.parseInline(tokens);
|
|
77
73
|
// 安全检查:仅允许安全协议
|
|
78
|
-
const safeHref =
|
|
79
|
-
const isExternal = safeHref && (safeHref
|
|
74
|
+
const safeHref = sanitizeLinkHref(href);
|
|
75
|
+
const isExternal = safeHref && isExternalLinkHref(safeHref);
|
|
80
76
|
const attrs = [
|
|
81
77
|
`href="${escapeHtml(safeHref)}"`,
|
|
82
78
|
title ? `title="${escapeHtml(title)}"` : '',
|
|
@@ -87,7 +83,7 @@ function createRendererObject(options) {
|
|
|
87
83
|
// 图片:添加 loading="lazy",验证 src 协议
|
|
88
84
|
image({ href, title, text }) {
|
|
89
85
|
// 安全检查:仅允许安全协议
|
|
90
|
-
const safeSrc =
|
|
86
|
+
const safeSrc = sanitizeImageSrc(href);
|
|
91
87
|
const attrs = [
|
|
92
88
|
`src="${escapeHtml(safeSrc)}"`,
|
|
93
89
|
`alt="${escapeHtml(text || '')}"`,
|
|
@@ -136,6 +132,7 @@ const DEFAULT_OPTIONS = {
|
|
|
136
132
|
enableHighlight: true,
|
|
137
133
|
showCopyButton: true,
|
|
138
134
|
breaks: true,
|
|
135
|
+
allowHtmlTags: false,
|
|
139
136
|
};
|
|
140
137
|
/** 缓存 Marked 实例,避免重复创建 */
|
|
141
138
|
let cachedInstance = null;
|
|
@@ -144,7 +141,7 @@ let cachedOptionsKey = '';
|
|
|
144
141
|
* 获取配置签名,用于实例缓存比较
|
|
145
142
|
*/
|
|
146
143
|
function getOptionsKey(options) {
|
|
147
|
-
return `${options.enableHighlight}-${options.showCopyButton}-${options.breaks}`;
|
|
144
|
+
return `${options.enableHighlight}-${options.showCopyButton}-${options.breaks}-${options.allowHtmlTags}`;
|
|
148
145
|
}
|
|
149
146
|
/**
|
|
150
147
|
* 将 Markdown 文本解析为 HTML
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @h-ai/ui — Safe HTML helpers for Markdown rendering.
|
|
3
|
+
*
|
|
4
|
+
* The Markdown pipeline keeps raw HTML escaped by default.
|
|
5
|
+
* When callers opt in, only a controlled allowlist of safe tags and
|
|
6
|
+
* attributes is reconstructed; unsupported or dangerous markup remains escaped.
|
|
7
|
+
*/
|
|
8
|
+
export declare function escapeHtml(text: string): string;
|
|
9
|
+
export declare function sanitizeLinkHref(href: string | null | undefined): string;
|
|
10
|
+
export declare function isExternalLinkHref(href: string): boolean;
|
|
11
|
+
export declare function sanitizeImageSrc(src: string | null | undefined): string;
|
|
12
|
+
/**
|
|
13
|
+
* Sanitize raw HTML fragments embedded inside Markdown.
|
|
14
|
+
* Safe allowlisted tags are reconstructed, everything else remains escaped.
|
|
15
|
+
*/
|
|
16
|
+
export declare function sanitizeMarkdownHtml(fragment: string): string;
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @h-ai/ui — Safe HTML helpers for Markdown rendering.
|
|
3
|
+
*
|
|
4
|
+
* The Markdown pipeline keeps raw HTML escaped by default.
|
|
5
|
+
* When callers opt in, only a controlled allowlist of safe tags and
|
|
6
|
+
* attributes is reconstructed; unsupported or dangerous markup remains escaped.
|
|
7
|
+
*/
|
|
8
|
+
const SAFE_LINK_HREF_REGEX = /^(?:https?:\/\/|\/|#|mailto:)/i;
|
|
9
|
+
const SAFE_IMAGE_SRC_REGEX = /^(?:https?:\/\/|\/|data:image\/)/i;
|
|
10
|
+
const SAFE_ALIGN_VALUE_REGEX = /^(?:left|center|right|justify)$/i;
|
|
11
|
+
const SAFE_TABLE_SPAN_REGEX = /^[1-9]\d{0,2}$/;
|
|
12
|
+
const HTML_ATTR_REGEX = /([^\s"'=<>`/]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
13
|
+
const ALLOWED_HTML_TAGS = new Set([
|
|
14
|
+
'a',
|
|
15
|
+
'b',
|
|
16
|
+
'blockquote',
|
|
17
|
+
'br',
|
|
18
|
+
'code',
|
|
19
|
+
'del',
|
|
20
|
+
'div',
|
|
21
|
+
'em',
|
|
22
|
+
'h1',
|
|
23
|
+
'h2',
|
|
24
|
+
'h3',
|
|
25
|
+
'h4',
|
|
26
|
+
'h5',
|
|
27
|
+
'h6',
|
|
28
|
+
'hr',
|
|
29
|
+
'i',
|
|
30
|
+
'img',
|
|
31
|
+
'kbd',
|
|
32
|
+
'li',
|
|
33
|
+
'mark',
|
|
34
|
+
'ol',
|
|
35
|
+
'p',
|
|
36
|
+
'pre',
|
|
37
|
+
's',
|
|
38
|
+
'span',
|
|
39
|
+
'strong',
|
|
40
|
+
'sub',
|
|
41
|
+
'sup',
|
|
42
|
+
'table',
|
|
43
|
+
'tbody',
|
|
44
|
+
'td',
|
|
45
|
+
'th',
|
|
46
|
+
'thead',
|
|
47
|
+
'tr',
|
|
48
|
+
'u',
|
|
49
|
+
'ul',
|
|
50
|
+
]);
|
|
51
|
+
const VOID_HTML_TAGS = new Set(['br', 'hr', 'img']);
|
|
52
|
+
const ALIGNABLE_HTML_TAGS = new Set([
|
|
53
|
+
'blockquote',
|
|
54
|
+
'div',
|
|
55
|
+
'h1',
|
|
56
|
+
'h2',
|
|
57
|
+
'h3',
|
|
58
|
+
'h4',
|
|
59
|
+
'h5',
|
|
60
|
+
'h6',
|
|
61
|
+
'li',
|
|
62
|
+
'p',
|
|
63
|
+
'td',
|
|
64
|
+
'th',
|
|
65
|
+
]);
|
|
66
|
+
export function escapeHtml(text) {
|
|
67
|
+
return text
|
|
68
|
+
.replace(/&/g, '&')
|
|
69
|
+
.replace(/</g, '<')
|
|
70
|
+
.replace(/>/g, '>')
|
|
71
|
+
.replace(/"/g, '"')
|
|
72
|
+
.replace(/'/g, ''');
|
|
73
|
+
}
|
|
74
|
+
export function sanitizeLinkHref(href) {
|
|
75
|
+
const normalized = href?.trim() ?? '';
|
|
76
|
+
return SAFE_LINK_HREF_REGEX.test(normalized) ? normalized : '';
|
|
77
|
+
}
|
|
78
|
+
export function isExternalLinkHref(href) {
|
|
79
|
+
return /^https?:\/\//i.test(href);
|
|
80
|
+
}
|
|
81
|
+
export function sanitizeImageSrc(src) {
|
|
82
|
+
const normalized = src?.trim() ?? '';
|
|
83
|
+
return SAFE_IMAGE_SRC_REGEX.test(normalized) ? normalized : '';
|
|
84
|
+
}
|
|
85
|
+
function sanitizeAlignValue(value) {
|
|
86
|
+
const normalized = value?.trim().toLowerCase();
|
|
87
|
+
return normalized && SAFE_ALIGN_VALUE_REGEX.test(normalized)
|
|
88
|
+
? normalized
|
|
89
|
+
: undefined;
|
|
90
|
+
}
|
|
91
|
+
function parseHtmlAttributes(rawAttributes) {
|
|
92
|
+
const attributes = [];
|
|
93
|
+
for (const match of rawAttributes.matchAll(HTML_ATTR_REGEX)) {
|
|
94
|
+
const [, rawName, quotedDouble, quotedSingle, unquoted] = match;
|
|
95
|
+
const attrName = rawName?.trim().toLowerCase();
|
|
96
|
+
if (!attrName) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const attrValue = quotedDouble ?? quotedSingle ?? unquoted ?? '';
|
|
100
|
+
attributes.push([attrName, attrValue]);
|
|
101
|
+
}
|
|
102
|
+
return attributes;
|
|
103
|
+
}
|
|
104
|
+
function isHtmlTagNameChar(char) {
|
|
105
|
+
return typeof char === 'string' && /^[\w:-]$/.test(char);
|
|
106
|
+
}
|
|
107
|
+
function skipWhitespace(source, startIndex) {
|
|
108
|
+
let cursor = startIndex;
|
|
109
|
+
while (cursor < source.length && /\s/.test(source[cursor] ?? '')) {
|
|
110
|
+
cursor += 1;
|
|
111
|
+
}
|
|
112
|
+
return cursor;
|
|
113
|
+
}
|
|
114
|
+
function findHtmlTagEnd(source, startIndex) {
|
|
115
|
+
let quote = null;
|
|
116
|
+
for (let cursor = startIndex; cursor < source.length; cursor += 1) {
|
|
117
|
+
const char = source[cursor];
|
|
118
|
+
if (quote) {
|
|
119
|
+
if (char === quote) {
|
|
120
|
+
quote = null;
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (char === '"' || char === '\'') {
|
|
125
|
+
quote = char;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (char === '>') {
|
|
129
|
+
return cursor;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return -1;
|
|
133
|
+
}
|
|
134
|
+
function readNextHtmlTag(source, startIndex) {
|
|
135
|
+
let cursor = source.indexOf('<', startIndex);
|
|
136
|
+
while (cursor >= 0) {
|
|
137
|
+
const nextChar = source[cursor + 1];
|
|
138
|
+
if (!nextChar || (nextChar !== '/' && !/[a-z]/i.test(nextChar))) {
|
|
139
|
+
cursor = source.indexOf('<', cursor + 1);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const tagEnd = findHtmlTagEnd(source, cursor + 1);
|
|
143
|
+
if (tagEnd < 0) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
rawTag: source.slice(cursor, tagEnd + 1),
|
|
148
|
+
start: cursor,
|
|
149
|
+
end: tagEnd + 1,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
function readSanitizedAttributes(tagName, rawAttributes) {
|
|
155
|
+
const sanitized = {};
|
|
156
|
+
for (const [attrName, attrValue] of parseHtmlAttributes(rawAttributes)) {
|
|
157
|
+
if (tagName === 'a') {
|
|
158
|
+
if (attrName === 'href') {
|
|
159
|
+
sanitized.href = sanitizeLinkHref(attrValue);
|
|
160
|
+
}
|
|
161
|
+
if (attrName === 'title' && attrValue.trim()) {
|
|
162
|
+
sanitized.title = attrValue.trim();
|
|
163
|
+
}
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (tagName === 'img') {
|
|
167
|
+
if (attrName === 'src') {
|
|
168
|
+
sanitized.src = sanitizeImageSrc(attrValue);
|
|
169
|
+
}
|
|
170
|
+
if (attrName === 'alt') {
|
|
171
|
+
sanitized.alt = attrValue;
|
|
172
|
+
}
|
|
173
|
+
if (attrName === 'title' && attrValue.trim()) {
|
|
174
|
+
sanitized.title = attrValue.trim();
|
|
175
|
+
}
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (ALIGNABLE_HTML_TAGS.has(tagName) && attrName === 'align') {
|
|
179
|
+
sanitized.align = sanitizeAlignValue(attrValue);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if ((tagName === 'td' || tagName === 'th') && attrName === 'colspan') {
|
|
183
|
+
const normalized = attrValue.trim();
|
|
184
|
+
if (SAFE_TABLE_SPAN_REGEX.test(normalized)) {
|
|
185
|
+
sanitized.colspan = normalized;
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if ((tagName === 'td' || tagName === 'th') && attrName === 'rowspan') {
|
|
190
|
+
const normalized = attrValue.trim();
|
|
191
|
+
if (SAFE_TABLE_SPAN_REGEX.test(normalized)) {
|
|
192
|
+
sanitized.rowspan = normalized;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return sanitized;
|
|
197
|
+
}
|
|
198
|
+
function sanitizeHtmlTag(rawTag) {
|
|
199
|
+
if (!rawTag.startsWith('<') || !rawTag.endsWith('>')) {
|
|
200
|
+
return escapeHtml(rawTag);
|
|
201
|
+
}
|
|
202
|
+
let cursor = 1;
|
|
203
|
+
cursor = skipWhitespace(rawTag, cursor);
|
|
204
|
+
let isClosingTag = false;
|
|
205
|
+
if (rawTag[cursor] === '/') {
|
|
206
|
+
isClosingTag = true;
|
|
207
|
+
cursor += 1;
|
|
208
|
+
cursor = skipWhitespace(rawTag, cursor);
|
|
209
|
+
}
|
|
210
|
+
const tagNameStart = cursor;
|
|
211
|
+
while (isHtmlTagNameChar(rawTag[cursor])) {
|
|
212
|
+
cursor += 1;
|
|
213
|
+
}
|
|
214
|
+
const tagName = rawTag.slice(tagNameStart, cursor).toLowerCase();
|
|
215
|
+
if (!tagName) {
|
|
216
|
+
return escapeHtml(rawTag);
|
|
217
|
+
}
|
|
218
|
+
let rawAttributes = rawTag.slice(cursor, -1);
|
|
219
|
+
let selfClosing = false;
|
|
220
|
+
const trimmedAttributes = rawAttributes.trimEnd();
|
|
221
|
+
if (trimmedAttributes.endsWith('/')) {
|
|
222
|
+
selfClosing = true;
|
|
223
|
+
rawAttributes = trimmedAttributes.slice(0, -1);
|
|
224
|
+
}
|
|
225
|
+
if (!ALLOWED_HTML_TAGS.has(tagName)) {
|
|
226
|
+
return escapeHtml(rawTag);
|
|
227
|
+
}
|
|
228
|
+
if (isClosingTag) {
|
|
229
|
+
return VOID_HTML_TAGS.has(tagName) ? '' : `</${tagName}>`;
|
|
230
|
+
}
|
|
231
|
+
const attributes = readSanitizedAttributes(tagName, rawAttributes);
|
|
232
|
+
const renderedAttributes = [];
|
|
233
|
+
if (attributes.href !== undefined) {
|
|
234
|
+
renderedAttributes.push(`href="${escapeHtml(attributes.href)}"`);
|
|
235
|
+
if (attributes.href && isExternalLinkHref(attributes.href)) {
|
|
236
|
+
renderedAttributes.push('target="_blank"');
|
|
237
|
+
renderedAttributes.push('rel="noopener noreferrer"');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (attributes.title) {
|
|
241
|
+
renderedAttributes.push(`title="${escapeHtml(attributes.title)}"`);
|
|
242
|
+
}
|
|
243
|
+
if (attributes.src !== undefined) {
|
|
244
|
+
renderedAttributes.push(`src="${escapeHtml(attributes.src)}"`);
|
|
245
|
+
renderedAttributes.push('loading="lazy"');
|
|
246
|
+
renderedAttributes.push('class="hai-md-img"');
|
|
247
|
+
}
|
|
248
|
+
if (attributes.alt !== undefined) {
|
|
249
|
+
renderedAttributes.push(`alt="${escapeHtml(attributes.alt)}"`);
|
|
250
|
+
}
|
|
251
|
+
if (attributes.align) {
|
|
252
|
+
renderedAttributes.push(`style="text-align:${escapeHtml(attributes.align)}"`);
|
|
253
|
+
}
|
|
254
|
+
if (attributes.colspan) {
|
|
255
|
+
renderedAttributes.push(`colspan="${escapeHtml(attributes.colspan)}"`);
|
|
256
|
+
}
|
|
257
|
+
if (attributes.rowspan) {
|
|
258
|
+
renderedAttributes.push(`rowspan="${escapeHtml(attributes.rowspan)}"`);
|
|
259
|
+
}
|
|
260
|
+
const attributeText = renderedAttributes.length > 0
|
|
261
|
+
? ` ${renderedAttributes.join(' ')}`
|
|
262
|
+
: '';
|
|
263
|
+
return VOID_HTML_TAGS.has(tagName) || selfClosing
|
|
264
|
+
? `<${tagName}${attributeText} />`
|
|
265
|
+
: `<${tagName}${attributeText}>`;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Sanitize raw HTML fragments embedded inside Markdown.
|
|
269
|
+
* Safe allowlisted tags are reconstructed, everything else remains escaped.
|
|
270
|
+
*/
|
|
271
|
+
export function sanitizeMarkdownHtml(fragment) {
|
|
272
|
+
if (!fragment) {
|
|
273
|
+
return '';
|
|
274
|
+
}
|
|
275
|
+
let sanitized = '';
|
|
276
|
+
let lastIndex = 0;
|
|
277
|
+
for (let match = readNextHtmlTag(fragment, lastIndex); match; match = readNextHtmlTag(fragment, lastIndex)) {
|
|
278
|
+
sanitized += escapeHtml(fragment.slice(lastIndex, match.start));
|
|
279
|
+
sanitized += sanitizeHtmlTag(match.rawTag);
|
|
280
|
+
lastIndex = match.end;
|
|
281
|
+
}
|
|
282
|
+
sanitized += escapeHtml(fragment.slice(lastIndex));
|
|
283
|
+
return sanitized;
|
|
284
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
=============================================================================
|
|
13
13
|
-->
|
|
14
14
|
<script lang='ts'>
|
|
15
|
+
import type { DataAttributes } from '../../../../types.js'
|
|
15
16
|
import type { MarkdownToolbarDownloadAction } from '../document-types.js'
|
|
16
17
|
import type {
|
|
17
18
|
AiTableColumn,
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
} from './table-types.js'
|
|
24
25
|
import { SvelteSet } from 'svelte/reactivity'
|
|
25
26
|
import { uiM } from '../../../../messages.js'
|
|
26
|
-
import { cn } from '../../../../utils.js'
|
|
27
|
+
import { cn, getDataAttributes } from '../../../../utils.js'
|
|
27
28
|
import AiDocumentDownloadMenu from '../AiDocumentDownloadMenu.svelte'
|
|
28
29
|
|
|
29
30
|
interface ExtractedArrayChunk {
|
|
@@ -92,8 +93,10 @@
|
|
|
92
93
|
oncopytable,
|
|
93
94
|
// 表格下载回调。
|
|
94
95
|
ondownloadtable,
|
|
95
|
-
|
|
96
|
+
...restProps
|
|
97
|
+
}: AiTableEditorProps & DataAttributes = $props()
|
|
96
98
|
|
|
99
|
+
const dataAttributes = $derived(getDataAttributes(restProps))
|
|
97
100
|
// 复制反馈只影响右上角复制按钮,不和业务数据混用。
|
|
98
101
|
let copied = $state(false)
|
|
99
102
|
// 连续复制时按最后一次点击重新计时。
|
|
@@ -1029,7 +1032,7 @@
|
|
|
1029
1032
|
}
|
|
1030
1033
|
</script>
|
|
1031
1034
|
|
|
1032
|
-
<section class={tablePaneClass}>
|
|
1035
|
+
<section {...dataAttributes} class={tablePaneClass}>
|
|
1033
1036
|
<header class='hai-ai-table-header'>
|
|
1034
1037
|
<div class='hai-ai-table-heading'>
|
|
1035
1038
|
<div class='hai-ai-table-eyebrow'>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { DataAttributes } from '../../../../types.js';
|
|
1
2
|
import type { AiTableEditorProps } from './table-types.js';
|
|
2
|
-
|
|
3
|
+
type $$ComponentProps = AiTableEditorProps & DataAttributes;
|
|
4
|
+
declare const AiTableEditor: import("svelte").Component<$$ComponentProps, {
|
|
3
5
|
flushPendingTableChange: () => void;
|
|
4
6
|
}, "editorScrollHost">;
|
|
5
7
|
type AiTableEditor = ReturnType<typeof AiTableEditor>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DataAttributes } from '../../../../types.js';
|
|
1
2
|
/**
|
|
2
3
|
* 表格列支持的数据类型。
|
|
3
4
|
* - `text`: 普通文本输入
|
|
@@ -67,7 +68,7 @@ export interface AiTableDownloadPayload {
|
|
|
67
68
|
/** 推荐文件名(已带 `.csv` 后缀)。 */
|
|
68
69
|
filename: string;
|
|
69
70
|
}
|
|
70
|
-
export interface AiTableEditorProps {
|
|
71
|
+
export interface AiTableEditorProps extends DataAttributes {
|
|
71
72
|
/** 原始表格内容字符串,通常是 JSON 文本,支持流式残缺片段。 */
|
|
72
73
|
content?: string;
|
|
73
74
|
/** 结构化表格数据;有值时优先于 `content`。 */
|
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
import type { DataAttributes } from '../../../types.js'
|
|
50
51
|
import { uiM } from '../../../messages.js'
|
|
52
|
+
import { getDataAttributes } from '../../../utils.js'
|
|
51
53
|
|
|
52
54
|
interface Props {
|
|
53
55
|
open?: boolean
|
|
@@ -55,8 +57,9 @@
|
|
|
55
57
|
onsubmit?: (data: FeedbackData) => Promise<void>
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
let { open = $bindable(false), labels = {}, onsubmit }: Props = $props()
|
|
60
|
+
let { open = $bindable(false), labels = {}, onsubmit, ...restProps }: Props & DataAttributes = $props()
|
|
59
61
|
|
|
62
|
+
const dataAttributes = $derived(getDataAttributes(restProps))
|
|
60
63
|
// 文案优先使用传入的 labels,缺省回退到内置消息 uiM(...)
|
|
61
64
|
|
|
62
65
|
let feedbackType = $state<FeedbackType>('bug')
|
|
@@ -106,7 +109,7 @@
|
|
|
106
109
|
}
|
|
107
110
|
</script>
|
|
108
111
|
|
|
109
|
-
<Modal bind:open title={labels.title ?? uiM('feedback_title')}>
|
|
112
|
+
<Modal {...dataAttributes} bind:open title={labels.title ?? uiM('feedback_title')}>
|
|
110
113
|
<p class='text-base-content/70 mb-4'>{labels.description ?? uiM('feedback_description')}</p>
|
|
111
114
|
|
|
112
115
|
{#if error}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DataAttributes } from '../../../types.js';
|
|
1
2
|
type FeedbackType = 'bug' | 'feature' | 'question' | 'other';
|
|
2
3
|
interface FeedbackData {
|
|
3
4
|
type: FeedbackType;
|
|
@@ -29,6 +30,7 @@ interface Props {
|
|
|
29
30
|
labels?: FeedbackLabels;
|
|
30
31
|
onsubmit?: (data: FeedbackData) => Promise<void>;
|
|
31
32
|
}
|
|
33
|
+
type $$ComponentProps = Props & DataAttributes;
|
|
32
34
|
/**
|
|
33
35
|
* FeedbackModal
|
|
34
36
|
* 用户反馈模态框,支持 Bug 报告、功能建议等多种反馈类型。
|
|
@@ -42,6 +44,6 @@ interface Props {
|
|
|
42
44
|
* onsubmit={async (data) => { await submitFeedback(data) }}
|
|
43
45
|
* />
|
|
44
46
|
*/
|
|
45
|
-
declare const FeedbackModal: import("svelte").Component
|
|
47
|
+
declare const FeedbackModal: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
46
48
|
type FeedbackModal = ReturnType<typeof FeedbackModal>;
|
|
47
49
|
export default FeedbackModal;
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
```
|
|
30
30
|
-->
|
|
31
31
|
<script lang='ts'>
|
|
32
|
+
import type { DataAttributes } from '../../../types.js'
|
|
32
33
|
import { uiM } from '../../../messages.js'
|
|
34
|
+
import { getDataAttributes } from '../../../utils.js'
|
|
33
35
|
import BareButton from '../../primitives/BareButton.svelte'
|
|
34
36
|
|
|
35
37
|
interface Language {
|
|
@@ -63,8 +65,10 @@
|
|
|
63
65
|
align = 'end',
|
|
64
66
|
compact = false,
|
|
65
67
|
class: className = '',
|
|
66
|
-
|
|
68
|
+
...restProps
|
|
69
|
+
}: Props & DataAttributes = $props()
|
|
67
70
|
|
|
71
|
+
const dataAttributes = $derived(getDataAttributes(restProps))
|
|
68
72
|
let open = $state(false)
|
|
69
73
|
let containerRef = $state<HTMLDivElement | null>(null)
|
|
70
74
|
|
|
@@ -94,7 +98,7 @@
|
|
|
94
98
|
})
|
|
95
99
|
</script>
|
|
96
100
|
|
|
97
|
-
<div bind:this={containerRef} class='dropdown {align === 'end' ? 'dropdown-end' : ''} {open ? 'dropdown-open' : ''} {className}'>
|
|
101
|
+
<div {...dataAttributes} bind:this={containerRef} class='dropdown {align === 'end' ? 'dropdown-end' : ''} {open ? 'dropdown-open' : ''} {className}'>
|
|
98
102
|
<BareButton
|
|
99
103
|
type='button'
|
|
100
104
|
class='btn btn-ghost {compact ? 'btn-sm btn-square' : 'btn-sm gap-2 min-w-fit'}'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DataAttributes } from '../../../types.js';
|
|
1
2
|
interface Language {
|
|
2
3
|
value: string;
|
|
3
4
|
label: string;
|
|
@@ -17,6 +18,7 @@ interface Props {
|
|
|
17
18
|
/** 自定义类名 */
|
|
18
19
|
class?: string;
|
|
19
20
|
}
|
|
21
|
+
type $$ComponentProps = Props & DataAttributes;
|
|
20
22
|
/**
|
|
21
23
|
* LanguageSwitch
|
|
22
24
|
* 语言切换下拉组件,支持多语言切换。
|
|
@@ -47,6 +49,6 @@ interface Props {
|
|
|
47
49
|
* />
|
|
48
50
|
* ```
|
|
49
51
|
*/
|
|
50
|
-
declare const LanguageSwitch: import("svelte").Component
|
|
52
|
+
declare const LanguageSwitch: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
51
53
|
type LanguageSwitch = ReturnType<typeof LanguageSwitch>;
|
|
52
54
|
export default LanguageSwitch;
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
-->
|
|
21
21
|
<script lang='ts'>
|
|
22
22
|
import type { Snippet } from 'svelte'
|
|
23
|
-
import {
|
|
23
|
+
import type { DataAttributes } from '../../../types.js'
|
|
24
|
+
import { cn, getDataAttributes } from '../../../utils.js'
|
|
24
25
|
import BareButton from '../../primitives/BareButton.svelte'
|
|
25
26
|
|
|
26
27
|
type SettingsSection = {
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
children,
|
|
41
42
|
headerActions,
|
|
42
43
|
class: className = '',
|
|
44
|
+
...restProps
|
|
43
45
|
}: {
|
|
44
46
|
title?: string
|
|
45
47
|
description?: string
|
|
@@ -49,8 +51,9 @@
|
|
|
49
51
|
children?: Snippet
|
|
50
52
|
headerActions?: Snippet
|
|
51
53
|
class?: string
|
|
52
|
-
} = $props()
|
|
54
|
+
} & DataAttributes = $props()
|
|
53
55
|
|
|
56
|
+
const dataAttributes = $derived(getDataAttributes(restProps))
|
|
54
57
|
function navItemClass(id: string): string {
|
|
55
58
|
return cn(
|
|
56
59
|
'flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
|
@@ -61,7 +64,7 @@
|
|
|
61
64
|
}
|
|
62
65
|
</script>
|
|
63
66
|
|
|
64
|
-
<div class={cn('space-y-5', className)}>
|
|
67
|
+
<div {...dataAttributes} class={cn('space-y-5', className)}>
|
|
65
68
|
<!-- 标题区 -->
|
|
66
69
|
{#if title || description || headerActions}
|
|
67
70
|
<div class='flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between'>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { DataAttributes } from '../../../types.js';
|
|
2
3
|
type SettingsSection = {
|
|
3
4
|
id: string;
|
|
4
5
|
label: string;
|
|
@@ -15,7 +16,7 @@ type $$ComponentProps = {
|
|
|
15
16
|
children?: Snippet;
|
|
16
17
|
headerActions?: Snippet;
|
|
17
18
|
class?: string;
|
|
18
|
-
};
|
|
19
|
+
} & DataAttributes;
|
|
19
20
|
/**
|
|
20
21
|
* SettingsLayout
|
|
21
22
|
* 设置页布局场景组件
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
@prop {function} onthemechange - 主题变更回调
|
|
11
11
|
-->
|
|
12
12
|
<script lang='ts'>
|
|
13
|
+
import type { DataAttributes } from '../../../types.js'
|
|
13
14
|
import { uiM } from '../../../messages.js'
|
|
15
|
+
import { getDataAttributes } from '../../../utils.js'
|
|
14
16
|
import BareButton from '../../primitives/BareButton.svelte'
|
|
15
17
|
import IconButton from '../../primitives/IconButton.svelte'
|
|
16
18
|
|
|
@@ -43,8 +45,10 @@
|
|
|
43
45
|
onlanguagechange,
|
|
44
46
|
onthemechange,
|
|
45
47
|
onclose,
|
|
46
|
-
|
|
48
|
+
...restProps
|
|
49
|
+
}: Props & DataAttributes = $props()
|
|
47
50
|
|
|
51
|
+
const dataAttributes = $derived(getDataAttributes(restProps))
|
|
48
52
|
function handleClose() {
|
|
49
53
|
open = false
|
|
50
54
|
onclose?.()
|
|
@@ -66,9 +70,8 @@
|
|
|
66
70
|
<svelte:window onkeydown={handleKeydown} />
|
|
67
71
|
|
|
68
72
|
{#if open}
|
|
69
|
-
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
70
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
71
73
|
<div
|
|
74
|
+
{...dataAttributes}
|
|
72
75
|
class='fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm transition-opacity'
|
|
73
76
|
onclick={handleBackdropClick}
|
|
74
77
|
>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DataAttributes } from '../../../types.js';
|
|
1
2
|
interface Props {
|
|
2
3
|
open?: boolean;
|
|
3
4
|
currentLanguage?: string;
|
|
@@ -15,6 +16,7 @@ interface Props {
|
|
|
15
16
|
onthemechange?: (theme: string) => void;
|
|
16
17
|
onclose?: () => void;
|
|
17
18
|
}
|
|
19
|
+
type $$ComponentProps = Props & DataAttributes;
|
|
18
20
|
/**
|
|
19
21
|
* SettingsModal
|
|
20
22
|
* 应用设置模态框,包含语言和主题切换功能。
|
|
@@ -26,6 +28,6 @@ interface Props {
|
|
|
26
28
|
* @prop {function} onlanguagechange - 语言变更回调
|
|
27
29
|
* @prop {function} onthemechange - 主题变更回调
|
|
28
30
|
*/
|
|
29
|
-
declare const SettingsModal: import("svelte").Component
|
|
31
|
+
declare const SettingsModal: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
30
32
|
type SettingsModal = ReturnType<typeof SettingsModal>;
|
|
31
33
|
export default SettingsModal;
|