@h-ai/ui 0.1.0-alpha5

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.
Files changed (234) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +488 -0
  3. package/auto-import.d.ts +96 -0
  4. package/auto-import.js +259 -0
  5. package/dist/components/compounds/Accordion.svelte +142 -0
  6. package/dist/components/compounds/Accordion.svelte.d.ts +25 -0
  7. package/dist/components/compounds/ActionSheet.svelte +116 -0
  8. package/dist/components/compounds/ActionSheet.svelte.d.ts +22 -0
  9. package/dist/components/compounds/Alert.svelte +67 -0
  10. package/dist/components/compounds/Alert.svelte.d.ts +8 -0
  11. package/dist/components/compounds/AppBar.svelte +77 -0
  12. package/dist/components/compounds/AppBar.svelte.d.ts +20 -0
  13. package/dist/components/compounds/BottomNav.svelte +75 -0
  14. package/dist/components/compounds/BottomNav.svelte.d.ts +18 -0
  15. package/dist/components/compounds/Breadcrumb.svelte +49 -0
  16. package/dist/components/compounds/Breadcrumb.svelte.d.ts +4 -0
  17. package/dist/components/compounds/Calendar.svelte +116 -0
  18. package/dist/components/compounds/Calendar.svelte.d.ts +47 -0
  19. package/dist/components/compounds/Card.svelte +77 -0
  20. package/dist/components/compounds/Card.svelte.d.ts +8 -0
  21. package/dist/components/compounds/Combobox.svelte +327 -0
  22. package/dist/components/compounds/Combobox.svelte.d.ts +69 -0
  23. package/dist/components/compounds/Confirm.svelte +90 -0
  24. package/dist/components/compounds/Confirm.svelte.d.ts +4 -0
  25. package/dist/components/compounds/DataTable.svelte +133 -0
  26. package/dist/components/compounds/DataTable.svelte.d.ts +69 -0
  27. package/dist/components/compounds/DatePicker.svelte +135 -0
  28. package/dist/components/compounds/DatePicker.svelte.d.ts +46 -0
  29. package/dist/components/compounds/Drawer.svelte +113 -0
  30. package/dist/components/compounds/Drawer.svelte.d.ts +4 -0
  31. package/dist/components/compounds/Dropdown.svelte +91 -0
  32. package/dist/components/compounds/Dropdown.svelte.d.ts +8 -0
  33. package/dist/components/compounds/Empty.svelte +83 -0
  34. package/dist/components/compounds/Empty.svelte.d.ts +4 -0
  35. package/dist/components/compounds/Form.svelte +96 -0
  36. package/dist/components/compounds/Form.svelte.d.ts +7 -0
  37. package/dist/components/compounds/FormField.svelte +56 -0
  38. package/dist/components/compounds/FormField.svelte.d.ts +4 -0
  39. package/dist/components/compounds/InfiniteScroll.svelte +82 -0
  40. package/dist/components/compounds/InfiniteScroll.svelte.d.ts +22 -0
  41. package/dist/components/compounds/Modal.svelte +123 -0
  42. package/dist/components/compounds/Modal.svelte.d.ts +8 -0
  43. package/dist/components/compounds/PageHeader.svelte +44 -0
  44. package/dist/components/compounds/PageHeader.svelte.d.ts +28 -0
  45. package/dist/components/compounds/Pagination.svelte +157 -0
  46. package/dist/components/compounds/Pagination.svelte.d.ts +4 -0
  47. package/dist/components/compounds/Popover.svelte +137 -0
  48. package/dist/components/compounds/Popover.svelte.d.ts +4 -0
  49. package/dist/components/compounds/PullRefresh.svelte +98 -0
  50. package/dist/components/compounds/PullRefresh.svelte.d.ts +16 -0
  51. package/dist/components/compounds/Result.svelte +84 -0
  52. package/dist/components/compounds/Result.svelte.d.ts +4 -0
  53. package/dist/components/compounds/SafeArea.svelte +37 -0
  54. package/dist/components/compounds/SafeArea.svelte.d.ts +12 -0
  55. package/dist/components/compounds/Skeleton.svelte +75 -0
  56. package/dist/components/compounds/Skeleton.svelte.d.ts +8 -0
  57. package/dist/components/compounds/Steps.svelte +111 -0
  58. package/dist/components/compounds/Steps.svelte.d.ts +4 -0
  59. package/dist/components/compounds/SwipeCell.svelte +115 -0
  60. package/dist/components/compounds/SwipeCell.svelte.d.ts +19 -0
  61. package/dist/components/compounds/Tabs.svelte +79 -0
  62. package/dist/components/compounds/Tabs.svelte.d.ts +8 -0
  63. package/dist/components/compounds/TagInput.svelte +108 -0
  64. package/dist/components/compounds/TagInput.svelte.d.ts +4 -0
  65. package/dist/components/compounds/Timeline.svelte +167 -0
  66. package/dist/components/compounds/Timeline.svelte.d.ts +25 -0
  67. package/dist/components/compounds/ToastContainer.svelte +59 -0
  68. package/dist/components/compounds/ToastContainer.svelte.d.ts +3 -0
  69. package/dist/components/compounds/Tooltip.svelte +41 -0
  70. package/dist/components/compounds/Tooltip.svelte.d.ts +4 -0
  71. package/dist/components/compounds/accordion-types.d.ts +16 -0
  72. package/dist/components/compounds/accordion-types.js +5 -0
  73. package/dist/components/compounds/action-sheet-types.d.ts +14 -0
  74. package/dist/components/compounds/action-sheet-types.js +5 -0
  75. package/dist/components/compounds/bottom-nav-types.d.ts +17 -0
  76. package/dist/components/compounds/bottom-nav-types.js +5 -0
  77. package/dist/components/compounds/index.d.ts +44 -0
  78. package/dist/components/compounds/index.js +54 -0
  79. package/dist/components/compounds/swipe-cell-types.d.ts +14 -0
  80. package/dist/components/compounds/swipe-cell-types.js +5 -0
  81. package/dist/components/compounds/timeline-types.d.ts +20 -0
  82. package/dist/components/compounds/timeline-types.js +5 -0
  83. package/dist/components/index.d.ts +9 -0
  84. package/dist/components/index.js +12 -0
  85. package/dist/components/primitives/Avatar.svelte +102 -0
  86. package/dist/components/primitives/Avatar.svelte.d.ts +8 -0
  87. package/dist/components/primitives/Badge.svelte +33 -0
  88. package/dist/components/primitives/Badge.svelte.d.ts +8 -0
  89. package/dist/components/primitives/BareButton.svelte +45 -0
  90. package/dist/components/primitives/BareButton.svelte.d.ts +4 -0
  91. package/dist/components/primitives/BareInput.svelte +101 -0
  92. package/dist/components/primitives/BareInput.svelte.d.ts +4 -0
  93. package/dist/components/primitives/Button.svelte +60 -0
  94. package/dist/components/primitives/Button.svelte.d.ts +8 -0
  95. package/dist/components/primitives/Checkbox.svelte +58 -0
  96. package/dist/components/primitives/Checkbox.svelte.d.ts +4 -0
  97. package/dist/components/primitives/IconButton.svelte +105 -0
  98. package/dist/components/primitives/IconButton.svelte.d.ts +4 -0
  99. package/dist/components/primitives/Input.svelte +130 -0
  100. package/dist/components/primitives/Input.svelte.d.ts +8 -0
  101. package/dist/components/primitives/Progress.svelte +58 -0
  102. package/dist/components/primitives/Progress.svelte.d.ts +4 -0
  103. package/dist/components/primitives/Radio.svelte +60 -0
  104. package/dist/components/primitives/Radio.svelte.d.ts +25 -0
  105. package/dist/components/primitives/Range.svelte +117 -0
  106. package/dist/components/primitives/Range.svelte.d.ts +27 -0
  107. package/dist/components/primitives/Rating.svelte +148 -0
  108. package/dist/components/primitives/Rating.svelte.d.ts +27 -0
  109. package/dist/components/primitives/Select.svelte +95 -0
  110. package/dist/components/primitives/Select.svelte.d.ts +25 -0
  111. package/dist/components/primitives/Spinner.svelte +54 -0
  112. package/dist/components/primitives/Spinner.svelte.d.ts +4 -0
  113. package/dist/components/primitives/Switch.svelte +53 -0
  114. package/dist/components/primitives/Switch.svelte.d.ts +4 -0
  115. package/dist/components/primitives/Tag.svelte +64 -0
  116. package/dist/components/primitives/Tag.svelte.d.ts +4 -0
  117. package/dist/components/primitives/Textarea.svelte +92 -0
  118. package/dist/components/primitives/Textarea.svelte.d.ts +4 -0
  119. package/dist/components/primitives/ToggleCheckbox.svelte +36 -0
  120. package/dist/components/primitives/ToggleCheckbox.svelte.d.ts +4 -0
  121. package/dist/components/primitives/ToggleInput.svelte +36 -0
  122. package/dist/components/primitives/ToggleInput.svelte.d.ts +4 -0
  123. package/dist/components/primitives/ToggleRadio.svelte +36 -0
  124. package/dist/components/primitives/ToggleRadio.svelte.d.ts +4 -0
  125. package/dist/components/primitives/index.d.ts +26 -0
  126. package/dist/components/primitives/index.js +30 -0
  127. package/dist/components/scenes/ai/AiDocumentDownloadMenu.svelte +387 -0
  128. package/dist/components/scenes/ai/AiDocumentDownloadMenu.svelte.d.ts +38 -0
  129. package/dist/components/scenes/ai/AiDocumentEditor.svelte +2259 -0
  130. package/dist/components/scenes/ai/AiDocumentEditor.svelte.d.ts +4 -0
  131. package/dist/components/scenes/ai/MarkdownRenderer.svelte +460 -0
  132. package/dist/components/scenes/ai/MarkdownRenderer.svelte.d.ts +4 -0
  133. package/dist/components/scenes/ai/document-download.d.ts +40 -0
  134. package/dist/components/scenes/ai/document-download.js +300 -0
  135. package/dist/components/scenes/ai/document-parse.d.ts +28 -0
  136. package/dist/components/scenes/ai/document-parse.js +243 -0
  137. package/dist/components/scenes/ai/document-types.d.ts +164 -0
  138. package/dist/components/scenes/ai/document-types.js +1 -0
  139. package/dist/components/scenes/ai/index.d.ts +17 -0
  140. package/dist/components/scenes/ai/index.js +13 -0
  141. package/dist/components/scenes/ai/markdown-parse.d.ts +32 -0
  142. package/dist/components/scenes/ai/markdown-parse.js +161 -0
  143. package/dist/components/scenes/app/FeedbackModal.svelte +160 -0
  144. package/dist/components/scenes/app/FeedbackModal.svelte.d.ts +47 -0
  145. package/dist/components/scenes/app/LanguageSwitch.svelte +136 -0
  146. package/dist/components/scenes/app/LanguageSwitch.svelte.d.ts +50 -0
  147. package/dist/components/scenes/app/SettingsModal.svelte +174 -0
  148. package/dist/components/scenes/app/SettingsModal.svelte.d.ts +31 -0
  149. package/dist/components/scenes/app/ThemeSelector.svelte +176 -0
  150. package/dist/components/scenes/app/ThemeSelector.svelte.d.ts +31 -0
  151. package/dist/components/scenes/app/ThemeToggle.svelte +76 -0
  152. package/dist/components/scenes/app/ThemeToggle.svelte.d.ts +29 -0
  153. package/dist/components/scenes/app/index.d.ts +11 -0
  154. package/dist/components/scenes/app/index.js +11 -0
  155. package/dist/components/scenes/crud/CrudDeleteConfirm.svelte +39 -0
  156. package/dist/components/scenes/crud/CrudDeleteConfirm.svelte.d.ts +18 -0
  157. package/dist/components/scenes/crud/CrudDetailDrawer.svelte +155 -0
  158. package/dist/components/scenes/crud/CrudDetailDrawer.svelte.d.ts +38 -0
  159. package/dist/components/scenes/crud/CrudEditDrawer.svelte +222 -0
  160. package/dist/components/scenes/crud/CrudEditDrawer.svelte.d.ts +48 -0
  161. package/dist/components/scenes/crud/CrudFilterBar.svelte +96 -0
  162. package/dist/components/scenes/crud/CrudFilterBar.svelte.d.ts +31 -0
  163. package/dist/components/scenes/crud/CrudPage.svelte +499 -0
  164. package/dist/components/scenes/crud/CrudPage.svelte.d.ts +114 -0
  165. package/dist/components/scenes/crud/crud-types.d.ts +135 -0
  166. package/dist/components/scenes/crud/crud-types.js +7 -0
  167. package/dist/components/scenes/crud/index.d.ts +12 -0
  168. package/dist/components/scenes/crud/index.js +11 -0
  169. package/dist/components/scenes/crypto/EncryptedInput.svelte +156 -0
  170. package/dist/components/scenes/crypto/EncryptedInput.svelte.d.ts +32 -0
  171. package/dist/components/scenes/crypto/HashDisplay.svelte +97 -0
  172. package/dist/components/scenes/crypto/HashDisplay.svelte.d.ts +4 -0
  173. package/dist/components/scenes/crypto/SignatureDisplay.svelte +161 -0
  174. package/dist/components/scenes/crypto/SignatureDisplay.svelte.d.ts +4 -0
  175. package/dist/components/scenes/crypto/index.d.ts +9 -0
  176. package/dist/components/scenes/crypto/index.js +11 -0
  177. package/dist/components/scenes/iam/ChangePasswordForm.svelte +134 -0
  178. package/dist/components/scenes/iam/ChangePasswordForm.svelte.d.ts +8 -0
  179. package/dist/components/scenes/iam/ForgotPasswordForm.svelte +147 -0
  180. package/dist/components/scenes/iam/ForgotPasswordForm.svelte.d.ts +4 -0
  181. package/dist/components/scenes/iam/LoginForm.svelte +191 -0
  182. package/dist/components/scenes/iam/LoginForm.svelte.d.ts +8 -0
  183. package/dist/components/scenes/iam/PasswordInput.svelte +203 -0
  184. package/dist/components/scenes/iam/PasswordInput.svelte.d.ts +49 -0
  185. package/dist/components/scenes/iam/PermGuard.svelte +69 -0
  186. package/dist/components/scenes/iam/PermGuard.svelte.d.ts +16 -0
  187. package/dist/components/scenes/iam/RegisterForm.svelte +269 -0
  188. package/dist/components/scenes/iam/RegisterForm.svelte.d.ts +8 -0
  189. package/dist/components/scenes/iam/ResetPasswordForm.svelte +153 -0
  190. package/dist/components/scenes/iam/ResetPasswordForm.svelte.d.ts +8 -0
  191. package/dist/components/scenes/iam/UserProfile.svelte +251 -0
  192. package/dist/components/scenes/iam/UserProfile.svelte.d.ts +4 -0
  193. package/dist/components/scenes/iam/index.d.ts +16 -0
  194. package/dist/components/scenes/iam/index.js +18 -0
  195. package/dist/components/scenes/iam/password-utils.d.ts +15 -0
  196. package/dist/components/scenes/iam/password-utils.js +19 -0
  197. package/dist/components/scenes/iam/permission-context.svelte.d.ts +90 -0
  198. package/dist/components/scenes/iam/permission-context.svelte.js +114 -0
  199. package/dist/components/scenes/index.d.ts +13 -0
  200. package/dist/components/scenes/index.js +20 -0
  201. package/dist/components/scenes/storage/AvatarUpload.svelte +218 -0
  202. package/dist/components/scenes/storage/AvatarUpload.svelte.d.ts +4 -0
  203. package/dist/components/scenes/storage/FileList.svelte +260 -0
  204. package/dist/components/scenes/storage/FileList.svelte.d.ts +4 -0
  205. package/dist/components/scenes/storage/FileUpload.svelte +400 -0
  206. package/dist/components/scenes/storage/FileUpload.svelte.d.ts +7 -0
  207. package/dist/components/scenes/storage/ImageUpload.svelte +285 -0
  208. package/dist/components/scenes/storage/ImageUpload.svelte.d.ts +4 -0
  209. package/dist/components/scenes/storage/index.d.ts +10 -0
  210. package/dist/components/scenes/storage/index.js +12 -0
  211. package/dist/components/scenes/types.d.ts +583 -0
  212. package/dist/components/scenes/types.js +7 -0
  213. package/dist/i18n.svelte.d.ts +44 -0
  214. package/dist/i18n.svelte.js +108 -0
  215. package/dist/index.d.ts +13 -0
  216. package/dist/index.js +19 -0
  217. package/dist/messages/en-US.json +242 -0
  218. package/dist/messages/zh-CN.json +242 -0
  219. package/dist/messages.d.ts +29 -0
  220. package/dist/messages.js +42 -0
  221. package/dist/styles/design-tokens.css +58 -0
  222. package/dist/styles/global.css +218 -0
  223. package/dist/styles/mobile.css +105 -0
  224. package/dist/theme-config.d.ts +92 -0
  225. package/dist/theme-config.js +122 -0
  226. package/dist/toast.svelte.d.ts +45 -0
  227. package/dist/toast.svelte.js +77 -0
  228. package/dist/types.d.ts +944 -0
  229. package/dist/types.js +7 -0
  230. package/dist/utils/platform.svelte.d.ts +54 -0
  231. package/dist/utils/platform.svelte.js +87 -0
  232. package/dist/utils.d.ts +46 -0
  233. package/dist/utils.js +100 -0
  234. package/package.json +69 -0
@@ -0,0 +1,2259 @@
1
+ <!--
2
+ =============================================================================
3
+ @h-ai/ui - AiDocumentEditor 组件
4
+ =============================================================================
5
+ 通用 Markdown 渲染器,支持两种模式:
6
+ - document:带目录、工具栏、代码运行预览的文档面板
7
+ - code: 仅渲染代码块,适用于 AI 代码产物的展示和交互。
8
+
9
+ 适用于 AI 输出显示、文档预览、代码说明面板等场景。
10
+ 使用 Svelte 5 Runes ($props, $state, $derived, $effect)
11
+ =============================================================================
12
+ -->
13
+ <script lang='ts'>
14
+ import type {
15
+ AiDocumentEditorProps,
16
+ MarkdownBlockFormatKind,
17
+ MarkdownCodeBlockItem,
18
+ MarkdownCodeRunRequest,
19
+ MarkdownCodeRunResult,
20
+ MarkdownInlineFormatKind,
21
+ MarkdownRewriteAction,
22
+ } from './document-types.js'
23
+ import { uiM } from '../../../messages.js'
24
+ import { cn } from '../../../utils.js'
25
+ import AiDocumentDownloadMenu from './AiDocumentDownloadMenu.svelte'
26
+ import { resolveDocumentMarkdownContent } from './document-download.js'
27
+ import { renderMarkdownDocument } from './document-parse.js'
28
+ import { parseMarkdown } from './markdown-parse.js'
29
+
30
+ interface SelectionToolbarPosition {
31
+ /** 选区工具条相对滚动容器的 top 坐标。 */
32
+ top: number
33
+ /** 选区工具条中心点相对滚动容器的 left 坐标。 */
34
+ left: number
35
+ /** 选区工具条应该出现在选区上方还是下方,避免遮挡正文。 */
36
+ placement: 'top' | 'bottom'
37
+ /** 工具条在主内容中的水平贴边方式,避免浮层越过正文区域。 */
38
+ alignment: 'left' | 'center' | 'right'
39
+ }
40
+
41
+ interface CodePreviewState {
42
+ /** 当前代码块预览所处的生命周期阶段。 */
43
+ status: 'running' | 'ready' | 'error'
44
+ /** 运行成功后用于渲染预览区的结构化结果。 */
45
+ result?: MarkdownCodeRunResult
46
+ /** 运行失败时展示在预览区的错误摘要。 */
47
+ error?: string
48
+ }
49
+
50
+ // 复制前后的图标以内联 SVG 缓存,避免每次点击都重新拼接按钮内容。
51
+ const COPY_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`
52
+ const CHECK_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`
53
+
54
+ let {
55
+ // 暴露给外层的滚动容器引用,用于同步滚动或定位选区工具条。
56
+ editorScrollHost = $bindable<HTMLDivElement | null>(null),
57
+ // 暴露给外层的正文容器引用,contenteditable 时用于读取 DOM。
58
+ previewHost = $bindable<HTMLElement | null>(null),
59
+ // 文档原始 Markdown 内容。
60
+ content = '',
61
+ // 文档标题展示文案。
62
+ title = '',
63
+ // 内容来源类型,决定 document/code 渲染分支。
64
+ sourceKind = 'markdown',
65
+ // sourceKind=code 时的默认语言提示。
66
+ codeLanguage,
67
+ // 外层自定义类名。
68
+ class: className = '',
69
+ // 是否显示代码块复制按钮。
70
+ showCopyButton = true,
71
+ // 是否显示代码块运行按钮与预览占位。
72
+ showRunButton = false,
73
+ // 是否启用语法高亮。
74
+ enableHighlight = true,
75
+ // 是否把换行渲染为 <br>。
76
+ breaks = true,
77
+ // 是否显示左侧目录。
78
+ showOutline = true,
79
+ // 是否显示顶部工具栏。
80
+ showToolbar = true,
81
+ // 目录面板初始是否折叠。
82
+ initialOutlineCollapsed = false,
83
+ // 是否处于改写中状态。
84
+ rewritePending = false,
85
+ // 改写动作列表。
86
+ rewriteActions = [],
87
+ // 是否启用正文编辑能力。
88
+ editable = false,
89
+ // 顶部下载菜单动作列表。
90
+ downloadActions = [],
91
+ // “历史版本”按钮文案。
92
+ historyActionLabel = uiM('markdown_history'),
93
+ // 关闭回调。
94
+ onclose,
95
+ // 返回按钮是否禁用。
96
+ closeDisabled = false,
97
+ // 撤销回调。
98
+ onundo,
99
+ // 撤销按钮是否禁用。
100
+ undoDisabled = false,
101
+ // 重做回调。
102
+ onredo,
103
+ // 重做按钮是否禁用。
104
+ redoDisabled = false,
105
+ // 代码运行回调。
106
+ oncoderun,
107
+ // 选区改写回调。
108
+ onrewrite,
109
+ // 文档滚动回调。
110
+ ondocumentscroll,
111
+ // 选区 mouseup 回调。
112
+ onpreviewmouseup,
113
+ // 编辑输入回调。
114
+ onpreviewinput,
115
+ // 编辑失焦回调。
116
+ onpreviewblur,
117
+ // 下载动作回调。
118
+ ondownload,
119
+ // 历史版本入口回调。
120
+ onhistory,
121
+ // 全文复制回调。
122
+ oncopydocument,
123
+ // 块级格式动作回调。
124
+ onapplyblockformat,
125
+ // 行内格式动作回调。
126
+ onapplyinlineformat,
127
+ // 选区复制回调。
128
+ oncopyselection,
129
+ // 选区注释回调。
130
+ onannotation,
131
+ }: AiDocumentEditorProps = $props()
132
+
133
+ // outlineCollapsedInitialized 用来把 `initialOutlineCollapsed` 只消费一次,避免用户手动展开后又被 props 回写覆盖。
134
+ let outlineCollapsedInitialized = $state(false)
135
+ // 当前目录是否折叠。
136
+ let outlineCollapsed = $state(false)
137
+ // 当前滚动视口对应的高亮标题 id。
138
+ let activeHeadingId = $state('')
139
+ // 当前选中的纯文本,会原样传给 AI 改写动作。
140
+ let selectedText = $state('')
141
+ // 选区工具条显示开关。
142
+ let selectionToolbarVisible = $state(false)
143
+ // 改写菜单显示开关。
144
+ let rewriteMenuOpen = $state(false)
145
+ // 文档头部标题只有在正文首个标题滚出可视区后才显示,避免双标题并排出现。
146
+ let showPinnedTitle = $state(Boolean(title))
147
+ // 选区工具条在滚动容器中的定位坐标。
148
+ let toolbarPosition = $state<SelectionToolbarPosition>({
149
+ top: 0,
150
+ left: 0,
151
+ placement: 'top',
152
+ alignment: 'center',
153
+ })
154
+ // documentCopied 只负责顶部复制按钮的瞬时反馈,不和正文内容状态混用。
155
+ let documentCopied = $state(false)
156
+ // copyFeedbackTimer 用来保证连续点击复制时,成功态能按最后一次操作重新计时。
157
+ let copyFeedbackTimer: ReturnType<typeof window.setTimeout> | undefined = $state()
158
+ // 每个代码块的运行状态和预览结果,key 为 codeBlockId。
159
+ let codePreviews = $state<Record<string, CodePreviewState>>({})
160
+
161
+ // code 类型产物通常只有裸代码文本,这里统一包成 fenced block 进入同一条渲染链路。
162
+ const documentContent = $derived(
163
+ resolveDocumentMarkdownContent(content, sourceKind, codeLanguage),
164
+ )
165
+ // 渲染结果同时提供 HTML、目录和代码块元数据,供顶部目录与代码预览共用。
166
+ const renderResult = $derived(
167
+ renderMarkdownDocument(documentContent, {
168
+ enableHighlight,
169
+ showCopyButton,
170
+ showRunButton,
171
+ breaks,
172
+ }),
173
+ )
174
+ // html 是最终注入正文的内容。
175
+ const html = $derived(renderResult.html)
176
+ // outline 是左侧目录的原始数据源。
177
+ const outline = $derived(renderResult.outline)
178
+ // outlineHasContent 用来区分“目录被收起”和“正文确实没有标题”。
179
+ const outlineHasContent = $derived(outline.length > 0)
180
+ // 未显式传入动作时,按内置动作列表补齐 AI 改写菜单。
181
+ const resolvedRewriteActions = $derived(
182
+ resolveRewriteActions(rewriteActions, onrewrite),
183
+ )
184
+ // selectionToolsEnabled 统一判断选区工具条是否值得出现,避免正文只读展示时还露出空浮层。
185
+ const selectionToolsEnabled = $derived(
186
+ [
187
+ onrewrite,
188
+ onapplyblockformat,
189
+ onapplyinlineformat,
190
+ oncopyselection,
191
+ onannotation,
192
+ ].some(Boolean),
193
+ )
194
+ // readerDocumentClass 只负责正文文章区域,不和外层容器类名混用。
195
+ const readerDocumentClass = $derived(
196
+ cn(
197
+ 'hai-markdown',
198
+ 'hai-markdown-document',
199
+ editable ? 'hai-markdown-editable' : '',
200
+ ),
201
+ )
202
+
203
+ $effect(() => {
204
+ void html
205
+ if (typeof window === 'undefined' || !previewHost) {
206
+ return
207
+ }
208
+
209
+ requestAnimationFrame(() => {
210
+ syncCodePreviewHosts()
211
+ syncActiveHeadingFromScroll()
212
+ syncPinnedTitleVisibility()
213
+ })
214
+ })
215
+
216
+ $effect(() => {
217
+ void codePreviews
218
+ if (typeof window === 'undefined' || !previewHost) {
219
+ return
220
+ }
221
+
222
+ requestAnimationFrame(() => {
223
+ syncCodePreviewHosts()
224
+ })
225
+ })
226
+
227
+ $effect(() => {
228
+ void outline
229
+ if (!outlineHasContent) {
230
+ activeHeadingId = ''
231
+ return
232
+ }
233
+
234
+ if (
235
+ !activeHeadingId
236
+ || !outline.some(item => item.id === activeHeadingId)
237
+ ) {
238
+ activeHeadingId = outline[0]?.id ?? ''
239
+ }
240
+ })
241
+
242
+ $effect(() => {
243
+ void documentContent
244
+ closeSelectionToolbar()
245
+ })
246
+
247
+ $effect(() => {
248
+ if (outlineCollapsedInitialized) {
249
+ return
250
+ }
251
+
252
+ outlineCollapsed = initialOutlineCollapsed
253
+ outlineCollapsedInitialized = true
254
+ })
255
+
256
+ $effect(() => {
257
+ return () => {
258
+ if (copyFeedbackTimer) {
259
+ window.clearTimeout(copyFeedbackTimer)
260
+ }
261
+ }
262
+ })
263
+
264
+ function resolveRewriteActions(
265
+ actions: MarkdownRewriteAction[],
266
+ handler?: AiDocumentEditorProps['onrewrite'],
267
+ ): MarkdownRewriteAction[] {
268
+ if (actions.length > 0) {
269
+ return actions
270
+ }
271
+
272
+ if (!handler) {
273
+ return []
274
+ }
275
+
276
+ return [
277
+ { id: 'polish', label: uiM('markdown_rewrite_polish') },
278
+ { id: 'expand', label: uiM('markdown_rewrite_expand') },
279
+ { id: 'shorten', label: uiM('markdown_rewrite_shorten') },
280
+ { id: 'explain', label: uiM('markdown_rewrite_explain') },
281
+ ]
282
+ }
283
+
284
+ async function copyRawContent(): Promise<boolean> {
285
+ try {
286
+ await navigator.clipboard.writeText(content)
287
+ return true
288
+ }
289
+ catch {
290
+ // clipboard API 可能被安全策略禁用;保持静默避免打断阅读。
291
+ return false
292
+ }
293
+ }
294
+
295
+ function triggerDocumentCopiedFeedback(): void {
296
+ documentCopied = true
297
+ if (copyFeedbackTimer) {
298
+ window.clearTimeout(copyFeedbackTimer)
299
+ }
300
+
301
+ copyFeedbackTimer = window.setTimeout(() => {
302
+ documentCopied = false
303
+ copyFeedbackTimer = undefined
304
+ }, 1800)
305
+ }
306
+
307
+ async function handleCopyDocument(): Promise<void> {
308
+ if (oncopydocument) {
309
+ await oncopydocument()
310
+ triggerDocumentCopiedFeedback()
311
+ return
312
+ }
313
+
314
+ if (await copyRawContent()) {
315
+ triggerDocumentCopiedFeedback()
316
+ }
317
+ }
318
+
319
+ function updateCopyButtonState(button: HTMLButtonElement): void {
320
+ button.classList.add('hai-md-copied')
321
+ button.innerHTML = CHECK_ICON
322
+
323
+ window.setTimeout(() => {
324
+ button.classList.remove('hai-md-copied')
325
+ button.innerHTML = COPY_ICON
326
+ }, 2000)
327
+ }
328
+
329
+ async function copyCodeFromButton(button: HTMLButtonElement): Promise<void> {
330
+ const codeBlock = button.closest('.hai-md-code-block')
331
+ const codeEl = codeBlock?.querySelector('code')
332
+ if (!codeEl) {
333
+ return
334
+ }
335
+
336
+ try {
337
+ await navigator.clipboard.writeText(codeEl.textContent ?? '')
338
+ updateCopyButtonState(button)
339
+ }
340
+ catch {
341
+ // clipboard API 可能被安全策略禁用;不抛错以免阻断其他交互。
342
+ }
343
+ }
344
+
345
+ function lookupCodeBlock(blockId: string): MarkdownCodeBlockItem | undefined {
346
+ return renderResult.codeBlocks.find(item => item.id === blockId)
347
+ }
348
+
349
+ function looksLikeHtml(code: string): boolean {
350
+ return /<!doctype html>|<html[\s>]|<body[\s>]|<div[\s>]|<main[\s>]/i.test(
351
+ code,
352
+ )
353
+ }
354
+
355
+ function buildJavaScriptPreview(code: string): string {
356
+ const safeCode = code.replace(/<\/script/gi, '<\\/script')
357
+
358
+ return `<!doctype html>
359
+ <html>
360
+ <head>
361
+ <meta charset="utf-8" />
362
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
363
+ <style>
364
+ body {
365
+ margin: 0;
366
+ padding: 16px;
367
+ font: 14px/1.6 system-ui, sans-serif;
368
+ color: #1f2937;
369
+ background: #f8fafc;
370
+ }
371
+
372
+ #app {
373
+ min-height: 48px;
374
+ }
375
+ </style>
376
+ </head>
377
+ <body>
378
+ <div id="app"></div>
379
+ <script>
380
+ ${safeCode}
381
+ <\/script>
382
+ </body>
383
+ </html>`
384
+ }
385
+
386
+ function buildCssPreview(code: string): string {
387
+ return `<!doctype html>
388
+ <html>
389
+ <head>
390
+ <meta charset="utf-8" />
391
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
392
+ <style>${code}</style>
393
+ <style>
394
+ body {
395
+ margin: 0;
396
+ padding: 20px;
397
+ font: 14px/1.6 system-ui, sans-serif;
398
+ background: #f8fafc;
399
+ }
400
+
401
+ .preview-card {
402
+ border-radius: 16px;
403
+ padding: 24px;
404
+ border: 1px dashed #cbd5e1;
405
+ background: white;
406
+ }
407
+ </style>
408
+ </head>
409
+ <body>
410
+ <div class="preview-card">
411
+ <h3>CSS Preview</h3>
412
+ <p>This surface is provided so the stylesheet can render immediately.</p>
413
+ <button type="button">Action</button>
414
+ </div>
415
+ </body>
416
+ </html>`
417
+ }
418
+
419
+ /**
420
+ * 没有外部运行器时,组件只为可直接在浏览器里安全预览的语言提供兜底能力。
421
+ * 其余语言返回 undefined,让上层明确知道需要后端或沙箱参与执行。
422
+ */
423
+ function createBuiltInCodePreview(
424
+ request: MarkdownCodeRunRequest,
425
+ ): MarkdownCodeRunResult | undefined {
426
+ const language = request.language?.trim().toLocaleLowerCase()
427
+
428
+ if (
429
+ language === 'html'
430
+ || language === 'htm'
431
+ || language === 'xml'
432
+ || language === 'svg'
433
+ || (!language && looksLikeHtml(request.code))
434
+ ) {
435
+ return {
436
+ kind: 'html',
437
+ title: uiM('markdown_run_preview'),
438
+ content: request.code,
439
+ }
440
+ }
441
+
442
+ if (language === 'javascript' || language === 'js' || language === 'mjs') {
443
+ return {
444
+ kind: 'html',
445
+ title: uiM('markdown_run_preview'),
446
+ content: buildJavaScriptPreview(request.code),
447
+ }
448
+ }
449
+
450
+ if (language === 'css') {
451
+ return {
452
+ kind: 'html',
453
+ title: uiM('markdown_run_preview'),
454
+ content: buildCssPreview(request.code),
455
+ }
456
+ }
457
+
458
+ if (language === 'markdown' || language === 'md') {
459
+ return {
460
+ kind: 'markdown',
461
+ title: uiM('markdown_run_preview'),
462
+ content: request.code,
463
+ }
464
+ }
465
+
466
+ return undefined
467
+ }
468
+
469
+ async function runCodeBlock(blockId: string): Promise<void> {
470
+ const codeBlock = lookupCodeBlock(blockId)
471
+ if (!codeBlock) {
472
+ return
473
+ }
474
+
475
+ codePreviews = {
476
+ ...codePreviews,
477
+ [blockId]: {
478
+ status: 'running',
479
+ },
480
+ }
481
+
482
+ const request: MarkdownCodeRunRequest = {
483
+ blockId,
484
+ code: codeBlock.code,
485
+ language: codeBlock.language,
486
+ title: title || undefined,
487
+ sourceKind,
488
+ }
489
+
490
+ try {
491
+ const preview = await (oncoderun?.(request)
492
+ ?? createBuiltInCodePreview(request))
493
+
494
+ if (!preview) {
495
+ codePreviews = {
496
+ ...codePreviews,
497
+ [blockId]: {
498
+ status: 'error',
499
+ error: uiM('markdown_run_unavailable'),
500
+ },
501
+ }
502
+ return
503
+ }
504
+
505
+ codePreviews = {
506
+ ...codePreviews,
507
+ [blockId]: {
508
+ status: 'ready',
509
+ result: preview,
510
+ },
511
+ }
512
+ }
513
+ catch (error) {
514
+ codePreviews = {
515
+ ...codePreviews,
516
+ [blockId]: {
517
+ status: 'error',
518
+ error:
519
+ error instanceof Error ? error.message : uiM('markdown_run_failed'),
520
+ },
521
+ }
522
+ }
523
+ }
524
+
525
+ async function handleClick(event: MouseEvent): Promise<void> {
526
+ const target = event.target as HTMLElement
527
+ const copyButton = target.closest(
528
+ '[data-copy-code]',
529
+ ) as HTMLButtonElement | null
530
+ if (copyButton) {
531
+ await copyCodeFromButton(copyButton)
532
+ return
533
+ }
534
+
535
+ const runButton = target.closest(
536
+ '[data-run-code]',
537
+ ) as HTMLButtonElement | null
538
+ const codeBlockId = runButton?.dataset.codeBlockId
539
+ if (runButton && codeBlockId) {
540
+ await runCodeBlock(codeBlockId)
541
+ }
542
+ }
543
+
544
+ /**
545
+ * 代码预览宿主位于 `{@html}` 注入的 DOM 内部,无法直接用 Svelte 子组件管理。
546
+ * 这里统一在 effect 中把运行结果写回这些占位节点,兼顾运行中、成功和失败三种状态。
547
+ */
548
+ function syncCodePreviewHosts(): void {
549
+ if (!previewHost) {
550
+ return
551
+ }
552
+
553
+ const hosts = previewHost.querySelectorAll<HTMLElement>(
554
+ '[data-code-preview-host]',
555
+ )
556
+ for (const host of hosts) {
557
+ const blockId = host.dataset.codePreviewHost
558
+ if (!blockId) {
559
+ continue
560
+ }
561
+
562
+ renderCodePreviewHost(host, codePreviews[blockId])
563
+ }
564
+ }
565
+
566
+ function renderCodePreviewHost(
567
+ host: HTMLElement,
568
+ preview: CodePreviewState | undefined,
569
+ ): void {
570
+ if (!preview) {
571
+ host.innerHTML = ''
572
+ return
573
+ }
574
+
575
+ if (preview.status === 'running') {
576
+ host.innerHTML = `<div class="hai-md-preview-card"><div class="hai-md-preview-head">${escapePreviewText(uiM('markdown_running'))}</div><div class="hai-md-preview-loading"></div></div>`
577
+ return
578
+ }
579
+
580
+ if (preview.status === 'error') {
581
+ host.innerHTML = `<div class="hai-md-preview-card hai-md-preview-error"><div class="hai-md-preview-head">${escapePreviewText(uiM('markdown_run_failed'))}</div><pre>${escapePreviewText(preview.error ?? uiM('markdown_run_failed'))}</pre></div>`
582
+ return
583
+ }
584
+
585
+ const result = preview.result
586
+ if (!result) {
587
+ host.innerHTML = ''
588
+ return
589
+ }
590
+
591
+ const previewTitle = escapePreviewText(
592
+ result.title ?? uiM('markdown_run_preview'),
593
+ )
594
+ const previewDesc = result.description
595
+ ? `<p class="hai-md-preview-desc">${escapePreviewText(result.description)}</p>`
596
+ : ''
597
+
598
+ if (result.kind === 'html') {
599
+ host.innerHTML = `<div class="hai-md-preview-card"><div class="hai-md-preview-head">${previewTitle}</div>${previewDesc}<iframe class="hai-md-preview-frame" sandbox="allow-scripts" srcdoc="${escapePreviewAttribute(result.content)}" title="${previewTitle}"></iframe></div>`
600
+ return
601
+ }
602
+
603
+ if (result.kind === 'markdown') {
604
+ const markdownHtml = parseMarkdown(result.content, {
605
+ enableHighlight,
606
+ showCopyButton: false,
607
+ showRunButton: false,
608
+ breaks,
609
+ })
610
+
611
+ host.innerHTML = `<div class="hai-md-preview-card"><div class="hai-md-preview-head">${previewTitle}</div>${previewDesc}<div class="hai-md-preview-rendered">${markdownHtml}</div></div>`
612
+ return
613
+ }
614
+
615
+ host.innerHTML = `<div class="hai-md-preview-card"><div class="hai-md-preview-head">${previewTitle}</div>${previewDesc}<pre>${escapePreviewText(result.content)}</pre></div>`
616
+ }
617
+
618
+ function escapePreviewText(value: string): string {
619
+ return value
620
+ .replace(/&/g, '&amp;')
621
+ .replace(/</g, '&lt;')
622
+ .replace(/>/g, '&gt;')
623
+ .replace(/"/g, '&quot;')
624
+ .replace(/'/g, '&#39;')
625
+ }
626
+
627
+ function escapePreviewAttribute(value: string): string {
628
+ return escapePreviewText(value).replace(/\n/g, '&#10;')
629
+ }
630
+
631
+ function closeSelectionToolbar(): void {
632
+ selectionToolbarVisible = false
633
+ rewriteMenuOpen = false
634
+ selectedText = ''
635
+ }
636
+
637
+ /**
638
+ * 目录高亮不依赖 IntersectionObserver,而是按滚动容器内标题的相对位置推断。
639
+ * 这样正文持续流式刷新时不需要反复重建 observer,也更容易和目录点击滚动保持一致。
640
+ */
641
+ function syncActiveHeadingFromScroll(): void {
642
+ if (!editorScrollHost || !previewHost || !outlineHasContent) {
643
+ return
644
+ }
645
+
646
+ const headings = [
647
+ ...previewHost.querySelectorAll<HTMLElement>('[data-heading-id]'),
648
+ ]
649
+ if (headings.length === 0) {
650
+ return
651
+ }
652
+
653
+ const hostRect = editorScrollHost.getBoundingClientRect()
654
+ let nextActiveId = headings[0]?.dataset.headingId ?? ''
655
+
656
+ for (const heading of headings) {
657
+ const headingId = heading.dataset.headingId
658
+ if (!headingId) {
659
+ continue
660
+ }
661
+
662
+ const top = heading.getBoundingClientRect().top - hostRect.top
663
+ if (top <= 72) {
664
+ nextActiveId = headingId
665
+ }
666
+ else {
667
+ break
668
+ }
669
+ }
670
+
671
+ activeHeadingId = nextActiveId
672
+ }
673
+
674
+ function syncPinnedTitleVisibility(): void {
675
+ if (!title) {
676
+ showPinnedTitle = false
677
+ return
678
+ }
679
+
680
+ if (!editorScrollHost || !previewHost) {
681
+ showPinnedTitle = true
682
+ return
683
+ }
684
+
685
+ const firstHeading = previewHost.querySelector<HTMLElement>(
686
+ '[data-heading-id]',
687
+ )
688
+ if (!firstHeading) {
689
+ showPinnedTitle = true
690
+ return
691
+ }
692
+
693
+ const headingTop = firstHeading.offsetTop - editorScrollHost.scrollTop
694
+ const headingBottom = headingTop + firstHeading.offsetHeight
695
+ showPinnedTitle = headingBottom <= 20
696
+ }
697
+
698
+ function handleDocumentScroll(event: Event): void {
699
+ syncActiveHeadingFromScroll()
700
+ syncPinnedTitleVisibility()
701
+ if (selectionToolbarVisible) {
702
+ closeSelectionToolbar()
703
+ }
704
+ ondocumentscroll?.(event)
705
+ }
706
+
707
+ function scrollToHeading(id: string): void {
708
+ if (!editorScrollHost || !previewHost) {
709
+ return
710
+ }
711
+
712
+ const target = previewHost.querySelector<HTMLElement>(`#${CSS.escape(id)}`)
713
+ if (!target) {
714
+ return
715
+ }
716
+
717
+ activeHeadingId = id
718
+ const offsetTop = Math.max(0, target.offsetTop - 16)
719
+ editorScrollHost.scrollTo({ top: offsetTop, behavior: 'smooth' })
720
+ }
721
+
722
+ /**
723
+ * 选区工具条依赖真实 DOM 选区范围,因此只在 selection 落在当前正文容器内时显示。
724
+ * 一旦内容刷新或滚动位置变化,就主动关闭,避免把旧选区动作误用到新内容上。
725
+ */
726
+ function handleSelectionChange(): void {
727
+ if (
728
+ !selectionToolsEnabled
729
+ || typeof window === 'undefined'
730
+ || !previewHost
731
+ || !editorScrollHost
732
+ ) {
733
+ return
734
+ }
735
+
736
+ const selection = window.getSelection()
737
+ if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
738
+ closeSelectionToolbar()
739
+ return
740
+ }
741
+
742
+ const anchorNode = selection.anchorNode
743
+ const focusNode = selection.focusNode
744
+ if (
745
+ !anchorNode
746
+ || !focusNode
747
+ || !previewHost.contains(anchorNode)
748
+ || !previewHost.contains(focusNode)
749
+ ) {
750
+ closeSelectionToolbar()
751
+ return
752
+ }
753
+
754
+ const text = selection.toString().trim()
755
+ if (!text) {
756
+ closeSelectionToolbar()
757
+ return
758
+ }
759
+
760
+ const range = selection.getRangeAt(0)
761
+ const rect = range.getBoundingClientRect()
762
+ const hostRect = editorScrollHost.getBoundingClientRect()
763
+ // 浮层尺寸在首次渲染前拿不到精确高度,这里用稳定的经验值决定上下避让方向,避免直接盖住选中文本。
764
+ const estimatedToolbarHeight = rewriteMenuOpen ? 132 : 56
765
+ const offset = 12
766
+ const topSpace = rect.top - hostRect.top
767
+ const bottomSpace = hostRect.bottom - rect.bottom
768
+ const placement
769
+ = topSpace >= estimatedToolbarHeight + offset || bottomSpace < estimatedToolbarHeight
770
+ ? 'top'
771
+ : 'bottom'
772
+ const center = rect.left - hostRect.left + rect.width / 2
773
+ const horizontalPadding = 16
774
+ const estimatedToolbarHalfWidth = Math.min(
775
+ 272,
776
+ Math.max(
777
+ 132,
778
+ (editorScrollHost.clientWidth - horizontalPadding * 2) / 2,
779
+ ),
780
+ )
781
+ const minCenter = horizontalPadding + estimatedToolbarHalfWidth
782
+ const maxCenter = editorScrollHost.clientWidth - minCenter
783
+ const alignment
784
+ = center <= minCenter
785
+ ? 'left'
786
+ : center >= maxCenter
787
+ ? 'right'
788
+ : 'center'
789
+ const toolbarLeft
790
+ = alignment === 'left'
791
+ ? horizontalPadding
792
+ : alignment === 'right'
793
+ ? editorScrollHost.clientWidth - horizontalPadding
794
+ : center
795
+ const toolbarTop
796
+ = placement === 'top'
797
+ ? editorScrollHost.scrollTop + rect.top - hostRect.top - estimatedToolbarHeight - offset
798
+ : editorScrollHost.scrollTop + rect.bottom - hostRect.top + offset
799
+
800
+ selectedText = text
801
+ selectionToolbarVisible = true
802
+ rewriteMenuOpen = false
803
+ toolbarPosition = {
804
+ top: Math.max(12, toolbarTop),
805
+ left: Math.max(horizontalPadding, toolbarLeft),
806
+ placement,
807
+ alignment,
808
+ }
809
+ }
810
+
811
+ async function applyRewrite(actionId: string): Promise<void> {
812
+ if (!onrewrite || !selectedText.trim()) {
813
+ return
814
+ }
815
+
816
+ rewriteMenuOpen = false
817
+ await onrewrite({
818
+ actionId,
819
+ selectedText,
820
+ content,
821
+ title: title || undefined,
822
+ })
823
+ }
824
+
825
+ function applyBlockFormat(kind: MarkdownBlockFormatKind): void {
826
+ onapplyblockformat?.(kind)
827
+ closeSelectionToolbar()
828
+ }
829
+
830
+ function applyInlineFormat(kind: MarkdownInlineFormatKind): void {
831
+ onapplyinlineformat?.(kind)
832
+ closeSelectionToolbar()
833
+ }
834
+
835
+ async function copySelection(): Promise<void> {
836
+ await oncopyselection?.()
837
+ closeSelectionToolbar()
838
+ }
839
+
840
+ async function annotateSelection(): Promise<void> {
841
+ await onannotation?.()
842
+ closeSelectionToolbar()
843
+ }
844
+
845
+ function handlePreviewMouseUp(): void {
846
+ handleSelectionChange()
847
+ onpreviewmouseup?.()
848
+ }
849
+
850
+ function handlePreviewInput(event: Event): void {
851
+ onpreviewinput?.(event)
852
+ }
853
+
854
+ function handlePreviewBlur(): void {
855
+ closeSelectionToolbar()
856
+ onpreviewblur?.()
857
+ }
858
+ </script>
859
+
860
+ <section class={cn('hai-ai-doc-pane', className)}>
861
+ <div class='hai-ai-doc-shell'>
862
+ {#if showToolbar}
863
+ <header class='hai-ai-doc-topbar'>
864
+ <div class='hai-ai-doc-meta-bar'>
865
+ <div class='hai-ai-doc-toolbar-heading'>
866
+ {#if showOutline && outlineCollapsed}
867
+ <button
868
+ type='button'
869
+ class='hai-ai-doc-outline-open'
870
+ aria-label={uiM('markdown_show_outline')}
871
+ title={uiM('markdown_show_outline')}
872
+ onclick={() => (outlineCollapsed = false)}
873
+ >
874
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
875
+ <path
876
+ d='M5.75 7.25a.75.75 0 0 1 .75-.75h11a.75.75 0 0 1 0 1.5h-11a.75.75 0 0 1-.75-.75Zm0 4.75a.75.75 0 0 1 .75-.75h11a.75.75 0 0 1 0 1.5h-11a.75.75 0 0 1-.75-.75Zm0 4.75a.75.75 0 0 1 .75-.75h11a.75.75 0 0 1 0 1.5h-11a.75.75 0 0 1-.75-.75Z'
877
+ ></path>
878
+ </svg>
879
+ </button>
880
+ {/if}
881
+
882
+ {#if showPinnedTitle && title}
883
+ <div class='hai-ai-doc-title-block'>
884
+ <h2>{title}</h2>
885
+ </div>
886
+ {/if}
887
+ </div>
888
+
889
+ <div class='hai-ai-doc-toolbar'>
890
+ {#if onundo}
891
+ <button
892
+ type='button'
893
+ class='hai-ai-doc-toolbar-icon'
894
+ aria-label={uiM('markdown_undo')}
895
+ disabled={undoDisabled}
896
+ onclick={onundo}
897
+ >
898
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
899
+ <path
900
+ d='M10.2 6.05a7.25 7.25 0 1 1-4.82 6.83.75.75 0 0 1 1.5 0 5.75 5.75 0 1 0 3.82-5.42V10a.75.75 0 1 1-1.5 0V4a.75.75 0 0 1 .75-.75h6a.75.75 0 0 1 0 1.5H10.2v1.3Z'
901
+ ></path>
902
+ </svg>
903
+ </button>
904
+ {/if}
905
+
906
+ {#if onredo}
907
+ <button
908
+ type='button'
909
+ class='hai-ai-doc-toolbar-icon'
910
+ aria-label={uiM('markdown_redo')}
911
+ disabled={redoDisabled}
912
+ onclick={onredo}
913
+ >
914
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
915
+ <path
916
+ d='M13.8 6.05V4.75h-5.75a.75.75 0 0 1 0-1.5h6A.75.75 0 0 1 14.8 4v6a.75.75 0 1 1-1.5 0V7.46a5.75 5.75 0 1 0 3.82 5.42.75.75 0 0 1 1.5 0 7.25 7.25 0 1 1-4.82-6.83Z'
917
+ ></path>
918
+ </svg>
919
+ </button>
920
+ {/if}
921
+
922
+ {#if onundo || onredo}
923
+ <span class='hai-ai-doc-toolbar-divider'></span>
924
+ {/if}
925
+
926
+ <button
927
+ type='button'
928
+ class={cn(
929
+ 'hai-ai-doc-toolbar-pill',
930
+ documentCopied ? 'hai-ai-doc-toolbar-pill--success' : '',
931
+ )}
932
+ aria-label={uiM('markdown_copy_document')}
933
+ title={uiM('markdown_copy_document')}
934
+ onclick={handleCopyDocument}
935
+ >
936
+ {#if documentCopied}
937
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
938
+ <path
939
+ d='M20.3 6.28a.75.75 0 0 1 .02 1.06l-8.06 8.38a.75.75 0 0 1-1.07.01L7.7 12.3a.75.75 0 1 1 1.06-1.06l2.9 2.9 7.58-7.88a.75.75 0 0 1 1.06.02Z'
940
+ ></path>
941
+ </svg>
942
+ {:else}
943
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
944
+ <path
945
+ d='M8.75 4.25A2.75 2.75 0 0 0 6 7v8.25A2.75 2.75 0 0 0 8.75 18h7.5A2.75 2.75 0 0 0 19 15.25V7a2.75 2.75 0 0 0-2.75-2.75h-7.5Zm-4 3A2.75 2.75 0 0 1 7.5 4.5a.75.75 0 0 0 0-1.5A4.25 4.25 0 0 0 3.25 7.25v8.5A4.25 4.25 0 0 0 7.5 20a.75.75 0 0 0 0-1.5 2.75 2.75 0 0 1-2.75-2.75v-8.5Z'
946
+ ></path>
947
+ </svg>
948
+ {/if}
949
+ <span>{uiM('markdown_copy_document')}</span>
950
+ </button>
951
+
952
+ <AiDocumentDownloadMenu
953
+ content={content}
954
+ title={title}
955
+ sourceKind={sourceKind}
956
+ codeLanguage={codeLanguage}
957
+ actions={downloadActions}
958
+ ondownload={ondownload}
959
+ showLabel={true}
960
+ iconOnly={false}
961
+ triggerTitle={uiM('markdown_download')}
962
+ triggerClass='hai-ai-doc-toolbar-pill'
963
+ />
964
+
965
+ {#if onhistory}
966
+ <span class='hai-ai-doc-toolbar-divider'></span>
967
+ <button
968
+ type='button'
969
+ class='hai-ai-doc-toolbar-action'
970
+ onclick={onhistory}
971
+ >
972
+ {historyActionLabel}
973
+ </button>
974
+ {/if}
975
+
976
+ {#if onclose}
977
+ <button
978
+ type='button'
979
+ class='hai-ai-doc-toolbar-close'
980
+ aria-label={uiM('markdown_close')}
981
+ disabled={closeDisabled}
982
+ title={uiM('markdown_close')}
983
+ onclick={onclose}
984
+ >
985
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
986
+ <path
987
+ d='M6.97 5.91a.75.75 0 0 1 1.06 0L12 9.88l3.97-3.97a.75.75 0 1 1 1.06 1.06L13.06 10.94l3.97 3.97a.75.75 0 1 1-1.06 1.06L12 12l-3.97 3.97a.75.75 0 1 1-1.06-1.06l3.97-3.97-3.97-3.97a.75.75 0 0 1 0-1.06Z'
988
+ ></path>
989
+ </svg>
990
+ </button>
991
+ {/if}
992
+ </div>
993
+ </div>
994
+ </header>
995
+ {/if}
996
+
997
+ <div
998
+ class:hai-ai-doc-layout-collapsed={outlineCollapsed}
999
+ class='hai-ai-doc-layout'
1000
+ >
1001
+ {#if showOutline && !outlineCollapsed}
1002
+ <aside class='hai-ai-doc-outline'>
1003
+ <div class='hai-ai-doc-outline-head'>
1004
+ <strong>{uiM('markdown_outline')}</strong>
1005
+
1006
+ <button
1007
+ type='button'
1008
+ class='hai-ai-doc-outline-toggle'
1009
+ aria-label={uiM('markdown_hide_outline')}
1010
+ title={uiM('markdown_hide_outline')}
1011
+ onclick={() => (outlineCollapsed = true)}
1012
+ >
1013
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
1014
+ <path
1015
+ d='M11.78 6.22a.75.75 0 0 1 0 1.06L7.06 12l4.72 4.72a.75.75 0 0 1-1.06 1.06l-5.25-5.25a.75.75 0 0 1 0-1.06l5.25-5.25a.75.75 0 0 1 1.06 0Zm6 0a.75.75 0 0 1 0 1.06L13.06 12l4.72 4.72a.75.75 0 1 1-1.06 1.06l-5.25-5.25a.75.75 0 0 1 0-1.06l5.25-5.25a.75.75 0 0 1 1.06 0Z'
1016
+ ></path>
1017
+ </svg>
1018
+ </button>
1019
+ </div>
1020
+
1021
+ {#if outlineHasContent}
1022
+ <nav class='hai-ai-doc-outline-list'>
1023
+ {#each outline as item}
1024
+ <button
1025
+ type='button'
1026
+ class:active={activeHeadingId === item.id}
1027
+ class='hai-ai-doc-outline-item'
1028
+ style={`padding-left:${0.65 + (item.level - 1) * 0.56}rem`}
1029
+ onclick={() => scrollToHeading(item.id)}
1030
+ >
1031
+ {item.numberedTitle}
1032
+ </button>
1033
+ {/each}
1034
+ </nav>
1035
+ {:else}
1036
+ <p class='hai-ai-doc-outline-empty'>{uiM('markdown_no_outline')}</p>
1037
+ {/if}
1038
+ </aside>
1039
+ {/if}
1040
+
1041
+ <section class='hai-ai-doc-reader'>
1042
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
1043
+ <div
1044
+ bind:this={editorScrollHost}
1045
+ class='hai-ai-doc-scroll'
1046
+ onclick={handleClick}
1047
+ onscroll={handleDocumentScroll}
1048
+ onkeyup={handleSelectionChange}
1049
+ onfocusout={() => {
1050
+ if (typeof window !== 'undefined') {
1051
+ window.setTimeout(() => {
1052
+ handleSelectionChange()
1053
+ }, 0)
1054
+ }
1055
+ }}
1056
+ >
1057
+ {#if selectionToolbarVisible}
1058
+ <div
1059
+ class='hai-ai-doc-selection-layer'
1060
+ data-alignment={toolbarPosition.alignment}
1061
+ data-placement={toolbarPosition.placement}
1062
+ style={`top:${toolbarPosition.top}px; left:${toolbarPosition.left}px;`}
1063
+ >
1064
+ <div
1065
+ class='hai-ai-doc-selection-toolbar'
1066
+ role='toolbar'
1067
+ tabindex='-1'
1068
+ onmousedown={event => event.preventDefault()}
1069
+ >
1070
+ {#if onrewrite}
1071
+ <button
1072
+ type='button'
1073
+ class='hai-ai-doc-selection-chip'
1074
+ disabled={rewritePending}
1075
+ onclick={() => (rewriteMenuOpen = !rewriteMenuOpen)}
1076
+ >
1077
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
1078
+ <path
1079
+ d='M12 3.25a.75.75 0 0 1 .72.55l1.06 3.62 3.63 1.06a.75.75 0 0 1 0 1.44l-3.63 1.06-1.06 3.63a.75.75 0 0 1-1.44 0l-1.06-3.63-3.63-1.06a.75.75 0 0 1 0-1.44l3.63-1.06 1.06-3.62a.75.75 0 0 1 .72-.55Zm6.5 11.5a.75.75 0 0 1 .72.55l.42 1.43 1.43.42a.75.75 0 0 1 0 1.44l-1.43.42-.42 1.43a.75.75 0 0 1-1.44 0l-.42-1.43-1.43-.42a.75.75 0 0 1 0-1.44l1.43-.42.42-1.43a.75.75 0 0 1 .72-.55Z'
1080
+ ></path>
1081
+ </svg>
1082
+ <span>{uiM('markdown_rewrite')}</span>
1083
+ </button>
1084
+ {/if}
1085
+
1086
+ {#if onapplyblockformat}
1087
+ {#if onrewrite}
1088
+ <span class='hai-ai-doc-selection-divider'></span>
1089
+ {/if}
1090
+ <button
1091
+ type='button'
1092
+ class='hai-ai-doc-selection-btn'
1093
+ title={uiM('markdown_format_heading')}
1094
+ onclick={() => applyBlockFormat('heading')}
1095
+ >
1096
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
1097
+ <path
1098
+ d='M5 5.25a.75.75 0 0 1 .75.75V11h6.5V6a.75.75 0 0 1 1.5 0v12a.75.75 0 0 1-1.5 0v-5.5h-6.5V18a.75.75 0 0 1-1.5 0V6A.75.75 0 0 1 5 5.25Zm11.25 2.5a.75.75 0 0 1 0 1.5h3a.75.75 0 0 1 0 1.5H18.5v8.5a.75.75 0 0 1-1.5 0v-8.5h-.75a.75.75 0 0 1 0-1.5h3Z'
1099
+ ></path>
1100
+ </svg>
1101
+ </button>
1102
+ <button
1103
+ type='button'
1104
+ class='hai-ai-doc-selection-btn'
1105
+ title={uiM('markdown_format_bullet')}
1106
+ onclick={() => applyBlockFormat('bullet')}
1107
+ >
1108
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
1109
+ <path
1110
+ d='M6.25 7.25a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm14 0a.75.75 0 0 1-.75.75H8.75a.75.75 0 0 1 0-1.5H19.5a.75.75 0 0 1 .75.75Zm-14 5.5a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm14 0a.75.75 0 0 1-.75.75H8.75a.75.75 0 0 1 0-1.5H19.5a.75.75 0 0 1 .75.75Zm-14 5.5a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm14 0a.75.75 0 0 1-.75.75H8.75a.75.75 0 0 1 0-1.5H19.5a.75.75 0 0 1 .75.75Z'
1111
+ ></path>
1112
+ </svg>
1113
+ </button>
1114
+ {/if}
1115
+
1116
+ {#if onapplyinlineformat}
1117
+ {#if onapplyblockformat}
1118
+ <span class='hai-ai-doc-selection-divider'></span>
1119
+ {/if}
1120
+ <button
1121
+ type='button'
1122
+ class='hai-ai-doc-selection-btn'
1123
+ title={uiM('markdown_format_bold')}
1124
+ onclick={() => applyInlineFormat('bold')}
1125
+ >
1126
+ <strong>B</strong>
1127
+ </button>
1128
+ <button
1129
+ type='button'
1130
+ class='hai-ai-doc-selection-btn'
1131
+ title={uiM('markdown_format_strike')}
1132
+ onclick={() => applyInlineFormat('strike')}
1133
+ >
1134
+ S
1135
+ </button>
1136
+ <button
1137
+ type='button'
1138
+ class='hai-ai-doc-selection-btn'
1139
+ title={uiM('markdown_format_italic')}
1140
+ onclick={() => applyInlineFormat('italic')}
1141
+ >
1142
+ <em>I</em>
1143
+ </button>
1144
+ <button
1145
+ type='button'
1146
+ class='hai-ai-doc-selection-btn'
1147
+ title={uiM('markdown_format_underline')}
1148
+ onclick={() => applyInlineFormat('underline')}
1149
+ >
1150
+ U
1151
+ </button>
1152
+ <button
1153
+ type='button'
1154
+ class='hai-ai-doc-selection-btn'
1155
+ title={uiM('markdown_format_link')}
1156
+ onclick={() => applyInlineFormat('link')}
1157
+ >
1158
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
1159
+ <path
1160
+ d='M7.78 15.72a3.75 3.75 0 0 1 0-5.3l2.47-2.47a3.75 3.75 0 0 1 5.3 5.3l-.97.98a.75.75 0 0 1-1.06-1.06l.98-.97a2.25 2.25 0 0 0-3.19-3.19l-2.47 2.47a2.25 2.25 0 1 0 3.18 3.18l.49-.49a.75.75 0 0 1 1.06 1.06l-.49.49a3.75 3.75 0 0 1-5.3 0Zm8.44-7.44a.75.75 0 0 1 0 1.06l-8 8a.75.75 0 0 1-1.06-1.06l8-8a.75.75 0 0 1 1.06 0Z'
1161
+ ></path>
1162
+ </svg>
1163
+ </button>
1164
+ <button
1165
+ type='button'
1166
+ class='hai-ai-doc-selection-btn'
1167
+ title={uiM('markdown_format_code')}
1168
+ onclick={() => applyInlineFormat('code')}
1169
+ >
1170
+ &lt;/&gt;
1171
+ </button>
1172
+ <button
1173
+ type='button'
1174
+ class='hai-ai-doc-selection-btn'
1175
+ title={uiM('markdown_format_highlight')}
1176
+ onclick={() => applyInlineFormat('highlight')}
1177
+ >
1178
+ <span class='hai-ai-doc-selection-highlight-icon'>A</span>
1179
+ </button>
1180
+ {/if}
1181
+
1182
+ {#if oncopyselection || onannotation}
1183
+ {#if onapplyinlineformat}
1184
+ <span class='hai-ai-doc-selection-divider'></span>
1185
+ {/if}
1186
+ {/if}
1187
+
1188
+ {#if oncopyselection}
1189
+ <button
1190
+ type='button'
1191
+ class='hai-ai-doc-selection-btn hai-ai-doc-selection-btn-wide'
1192
+ title={uiM('markdown_copy_selection')}
1193
+ onclick={copySelection}
1194
+ >
1195
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
1196
+ <path
1197
+ d='M8.75 4.25A2.75 2.75 0 0 0 6 7v8.25A2.75 2.75 0 0 0 8.75 18h7.5A2.75 2.75 0 0 0 19 15.25V7a2.75 2.75 0 0 0-2.75-2.75h-7.5Zm-4 3A2.75 2.75 0 0 1 7.5 4.5a.75.75 0 0 0 0-1.5A4.25 4.25 0 0 0 3.25 7.25v8.5A4.25 4.25 0 0 0 7.5 20a.75.75 0 0 0 0-1.5 2.75 2.75 0 0 1-2.75-2.75v-8.5Z'
1198
+ ></path>
1199
+ </svg>
1200
+ </button>
1201
+ {/if}
1202
+
1203
+ {#if onannotation}
1204
+ <button
1205
+ type='button'
1206
+ class='hai-ai-doc-selection-btn hai-ai-doc-selection-btn-wide'
1207
+ title={uiM('markdown_annotation')}
1208
+ onclick={annotateSelection}
1209
+ >
1210
+ <svg viewBox='0 0 24 24' aria-hidden='true'>
1211
+ <path
1212
+ d='M6.75 4.25A2.75 2.75 0 0 0 4 7v7.75A2.75 2.75 0 0 0 6.75 17.5h1.72l2.3 2.01a1.75 1.75 0 0 0 2.3 0l2.3-2.01h1.88A2.75 2.75 0 0 0 20 14.75V7a2.75 2.75 0 0 0-2.75-2.75H6.75Zm1.5 4a.75.75 0 0 1 .75-.75h6.5a.75.75 0 0 1 0 1.5H9a.75.75 0 0 1-.75-.75Zm0 3.5A.75.75 0 0 1 9 11h4a.75.75 0 0 1 0 1.5H9a.75.75 0 0 1-.75-.75Z'
1213
+ ></path>
1214
+ </svg>
1215
+ </button>
1216
+ {/if}
1217
+ </div>
1218
+
1219
+ {#if rewriteMenuOpen}
1220
+ <div
1221
+ class='hai-ai-doc-rewrite-menu'
1222
+ role='menu'
1223
+ tabindex='-1'
1224
+ onmousedown={event => event.preventDefault()}
1225
+ >
1226
+ {#each resolvedRewriteActions as action}
1227
+ <button
1228
+ type='button'
1229
+ disabled={rewritePending}
1230
+ onclick={() => applyRewrite(action.id)}
1231
+ >
1232
+ {action.label}
1233
+ </button>
1234
+ {/each}
1235
+ </div>
1236
+ {/if}
1237
+ </div>
1238
+ {/if}
1239
+
1240
+ <article
1241
+ bind:this={previewHost}
1242
+ class={readerDocumentClass}
1243
+ contenteditable={editable}
1244
+ role='document'
1245
+ onmouseup={handlePreviewMouseUp}
1246
+ oninput={handlePreviewInput}
1247
+ onblur={handlePreviewBlur}
1248
+ >
1249
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -- Markdown HTML 渲染 -->
1250
+ {@html html}
1251
+ </article>
1252
+ </div>
1253
+ </section>
1254
+ </div>
1255
+ </div>
1256
+ </section>
1257
+
1258
+ <style>
1259
+ .hai-ai-doc-pane {
1260
+ --hai-ai-doc-selection-bg: color-mix(
1261
+ in srgb,
1262
+ oklch(var(--p, 0.62 0.22 264)) 28%,
1263
+ oklch(var(--b1, 1 0 0)) 72%
1264
+ );
1265
+ --hai-ai-doc-selection-fg: oklch(var(--bc, 0.22 0 0));
1266
+ display: flex;
1267
+ min-height: 0;
1268
+ height: 100%;
1269
+ flex-direction: column;
1270
+ border: 1px solid oklch(var(--bc) / 0.08);
1271
+ border-radius: 1.5rem;
1272
+ background: linear-gradient(
1273
+ 180deg,
1274
+ oklch(var(--b1)) 0%,
1275
+ oklch(var(--b1) / 0.98) 100%
1276
+ );
1277
+ overflow: hidden;
1278
+ box-shadow: 0 14px 40px oklch(var(--bc) / 0.06);
1279
+ }
1280
+
1281
+ .hai-ai-doc-shell {
1282
+ display: grid;
1283
+ grid-template-rows: auto auto 1fr;
1284
+ min-height: 0;
1285
+ height: 100%;
1286
+ }
1287
+
1288
+ .hai-ai-doc-topbar {
1289
+ padding: 1rem 1.5rem 0.5rem;
1290
+ }
1291
+
1292
+ .hai-ai-doc-meta-bar,
1293
+ .hai-ai-doc-heading-row {
1294
+ display: flex;
1295
+ align-items: center;
1296
+ justify-content: space-between;
1297
+ gap: 1rem;
1298
+ }
1299
+
1300
+ .hai-ai-doc-meta-bar {
1301
+ padding: 0.75rem 0;
1302
+ border-bottom: 1px solid oklch(var(--bc) / 0.08);
1303
+ justify-content: space-between;
1304
+ }
1305
+
1306
+ .hai-ai-doc-toolbar-heading {
1307
+ display: flex;
1308
+ align-items: center;
1309
+ gap: 0.85rem;
1310
+ min-width: 0;
1311
+ }
1312
+
1313
+ .hai-ai-doc-toolbar {
1314
+ display: flex;
1315
+ align-items: center;
1316
+ gap: 0.55rem;
1317
+ flex-wrap: wrap;
1318
+ justify-content: flex-end;
1319
+ }
1320
+
1321
+ .hai-ai-doc-toolbar-icon,
1322
+ :global(.hai-ai-doc-toolbar-icon),
1323
+ .hai-ai-doc-toolbar-close,
1324
+ .hai-ai-doc-toolbar-pill,
1325
+ :global(.hai-ai-doc-toolbar-pill),
1326
+ .hai-ai-doc-toolbar-action {
1327
+ color: oklch(var(--bc));
1328
+ cursor: pointer;
1329
+ flex-shrink: 0;
1330
+ }
1331
+
1332
+ .hai-ai-doc-toolbar-icon,
1333
+ :global(.hai-ai-doc-toolbar-icon),
1334
+ .hai-ai-doc-toolbar-close {
1335
+ width: 2.5rem;
1336
+ height: 2.5rem;
1337
+ display: inline-flex;
1338
+ align-items: center;
1339
+ justify-content: center;
1340
+ background: oklch(var(--b1));
1341
+ border: 1px solid oklch(var(--bc) / 0.1);
1342
+ transition:
1343
+ border-color 0.15s ease,
1344
+ background-color 0.15s ease,
1345
+ color 0.15s ease,
1346
+ box-shadow 0.15s ease,
1347
+ transform 0.15s ease;
1348
+ }
1349
+
1350
+ .hai-ai-doc-toolbar-icon,
1351
+ :global(.hai-ai-doc-toolbar-icon) {
1352
+ border-radius: 1rem;
1353
+ }
1354
+
1355
+ .hai-ai-doc-toolbar-pill,
1356
+ :global(.hai-ai-doc-toolbar-pill) {
1357
+ min-height: 2.5rem;
1358
+ display: inline-flex;
1359
+ align-items: center;
1360
+ gap: 0.58rem;
1361
+ padding: 0 0.98rem;
1362
+ border-radius: 9999px;
1363
+ border: 1px solid color-mix(in srgb, oklch(var(--bc)) 8%, white 92%);
1364
+ background: color-mix(in srgb, white 88%, oklch(var(--b1)) 12%);
1365
+ transition:
1366
+ border-color 0.15s ease,
1367
+ background-color 0.15s ease,
1368
+ color 0.15s ease,
1369
+ box-shadow 0.15s ease,
1370
+ transform 0.15s ease;
1371
+ white-space: nowrap;
1372
+ }
1373
+
1374
+ .hai-ai-doc-toolbar-pill span,
1375
+ :global(.hai-ai-doc-toolbar-pill span) {
1376
+ font-size: 0.95rem;
1377
+ font-weight: 600;
1378
+ letter-spacing: -0.01em;
1379
+ }
1380
+
1381
+ .hai-ai-doc-toolbar-close {
1382
+ border-radius: 1.05rem;
1383
+ }
1384
+
1385
+ .hai-ai-doc-toolbar-icon svg,
1386
+ :global(.hai-ai-doc-toolbar-icon svg),
1387
+ .hai-ai-doc-toolbar-close svg,
1388
+ .hai-ai-doc-toolbar-pill svg,
1389
+ :global(.hai-ai-doc-toolbar-pill svg) {
1390
+ width: 1.08rem;
1391
+ height: 1.08rem;
1392
+ fill: currentColor;
1393
+ }
1394
+
1395
+ .hai-ai-doc-toolbar-icon:not(:disabled):hover,
1396
+ :global(.hai-ai-doc-toolbar-icon:hover),
1397
+ .hai-ai-doc-toolbar-pill:not(:disabled):hover,
1398
+ :global(.hai-ai-doc-toolbar-pill:hover),
1399
+ .hai-ai-doc-toolbar-action:hover,
1400
+ .hai-ai-doc-toolbar-close:not(:disabled):hover,
1401
+ .hai-ai-doc-version-toggle:hover,
1402
+ .hai-ai-doc-outline-open:hover {
1403
+ border-color: oklch(var(--bc) / 0.16);
1404
+ background: color-mix(in srgb, white 70%, oklch(var(--b2)) 30%);
1405
+ color: oklch(var(--bc));
1406
+ }
1407
+
1408
+ .hai-ai-doc-toolbar-icon:not(:disabled):hover,
1409
+ :global(.hai-ai-doc-toolbar-icon:hover),
1410
+ .hai-ai-doc-toolbar-pill:not(:disabled):hover,
1411
+ :global(.hai-ai-doc-toolbar-pill:hover),
1412
+ .hai-ai-doc-toolbar-close:not(:disabled):hover {
1413
+ transform: translateY(-1px);
1414
+ box-shadow: 0 12px 28px -22px oklch(var(--bc) / 0.36);
1415
+ }
1416
+
1417
+ .hai-ai-doc-toolbar-icon:disabled,
1418
+ :global(.hai-ai-doc-toolbar-icon:disabled),
1419
+ .hai-ai-doc-toolbar-pill:disabled,
1420
+ :global(.hai-ai-doc-toolbar-pill:disabled),
1421
+ .hai-ai-doc-toolbar-close:disabled {
1422
+ opacity: 0.42;
1423
+ cursor: not-allowed;
1424
+ box-shadow: none;
1425
+ }
1426
+
1427
+ .hai-ai-doc-toolbar-action,
1428
+ .hai-ai-doc-version-toggle {
1429
+ border: 1px solid oklch(var(--bc) / 0.08);
1430
+ border-radius: 9999px;
1431
+ padding: 0.58rem 0.92rem;
1432
+ background: color-mix(in srgb, oklch(var(--b1)) 76%, white 24%);
1433
+ transition:
1434
+ border-color 0.15s ease,
1435
+ background-color 0.15s ease,
1436
+ color 0.15s ease;
1437
+ white-space: nowrap;
1438
+ }
1439
+
1440
+ .hai-ai-doc-toolbar-pill--success {
1441
+ color: oklch(var(--su, 0.7 0.15 160));
1442
+ border-color: oklch(var(--su, 0.7 0.15 160) / 0.18);
1443
+ background: oklch(var(--su, 0.7 0.15 160) / 0.08);
1444
+ }
1445
+
1446
+ .hai-ai-doc-toolbar-divider {
1447
+ width: 1px;
1448
+ height: 1.4rem;
1449
+ background: oklch(var(--bc) / 0.12);
1450
+ }
1451
+
1452
+ .hai-ai-doc-title-block {
1453
+ min-width: 0;
1454
+ }
1455
+
1456
+ .hai-ai-doc-title-block h2 {
1457
+ margin: 0;
1458
+ font-size: 1.4rem;
1459
+ line-height: 1.25;
1460
+ }
1461
+
1462
+ .hai-ai-doc-layout {
1463
+ min-height: 0;
1464
+ display: grid;
1465
+ grid-template-columns: 220px minmax(0, 1fr);
1466
+ position: relative;
1467
+ overflow: hidden;
1468
+ }
1469
+
1470
+ .hai-ai-doc-layout.hai-ai-doc-layout-collapsed {
1471
+ grid-template-columns: minmax(0, 1fr);
1472
+ }
1473
+
1474
+ .hai-ai-doc-outline {
1475
+ min-height: 0;
1476
+ display: grid;
1477
+ grid-template-rows: auto 1fr;
1478
+ position: relative;
1479
+ z-index: 0;
1480
+ padding: 0.5rem 0.625rem 1.25rem 1rem;
1481
+ border-right: 1px solid oklch(var(--bc) / 0.08);
1482
+ background: linear-gradient(
1483
+ 180deg,
1484
+ oklch(var(--b2) / 0.42),
1485
+ transparent 100%
1486
+ );
1487
+ }
1488
+
1489
+ .hai-ai-doc-outline-head {
1490
+ display: flex;
1491
+ justify-content: space-between;
1492
+ align-items: center;
1493
+ gap: 0.625rem;
1494
+ padding: 0.625rem 0.375rem 0.875rem 0;
1495
+ }
1496
+
1497
+ .hai-ai-doc-outline-head strong {
1498
+ font-size: 0.94rem;
1499
+ color: oklch(var(--bc) / 0.84);
1500
+ }
1501
+
1502
+ .hai-ai-doc-outline-toggle,
1503
+ .hai-ai-doc-outline-open {
1504
+ width: 2.5rem;
1505
+ height: 2.5rem;
1506
+ display: inline-flex;
1507
+ align-items: center;
1508
+ justify-content: center;
1509
+ border: 1px solid oklch(var(--bc) / 0.1);
1510
+ border-radius: 1rem;
1511
+ color: oklch(var(--bc) / 0.78);
1512
+ background: #fff;
1513
+ box-shadow: 0 12px 24px -22px oklch(var(--bc) / 0.28);
1514
+ transition:
1515
+ border-color 0.15s ease,
1516
+ background-color 0.15s ease,
1517
+ color 0.15s ease,
1518
+ box-shadow 0.15s ease,
1519
+ transform 0.15s ease;
1520
+ cursor: pointer;
1521
+ flex-shrink: 0;
1522
+ }
1523
+
1524
+ .hai-ai-doc-outline-toggle svg,
1525
+ .hai-ai-doc-outline-open svg {
1526
+ width: 1.1rem;
1527
+ height: 1.1rem;
1528
+ fill: currentColor;
1529
+ }
1530
+
1531
+ .hai-ai-doc-outline-list {
1532
+ min-height: 0;
1533
+ overflow: auto;
1534
+ display: flex;
1535
+ flex-direction: column;
1536
+ gap: 0.375rem;
1537
+ }
1538
+
1539
+ .hai-ai-doc-outline-item {
1540
+ width: 100%;
1541
+ text-align: left;
1542
+ color: oklch(var(--bc) / 0.68);
1543
+ line-height: 1.5;
1544
+ padding-top: 0.3rem;
1545
+ padding-right: 0.25rem;
1546
+ padding-bottom: 0.3rem;
1547
+ border: none;
1548
+ border-radius: 0.75rem;
1549
+ background: transparent;
1550
+ transition: all 0.15s ease;
1551
+ cursor: pointer;
1552
+ }
1553
+
1554
+ .hai-ai-doc-outline-item:hover {
1555
+ background: oklch(var(--bc) / 0.05);
1556
+ color: inherit;
1557
+ }
1558
+
1559
+ .hai-ai-doc-outline-item.active {
1560
+ color: oklch(var(--p));
1561
+ font-weight: 700;
1562
+ background: oklch(var(--p) / 0.12);
1563
+ }
1564
+
1565
+ .hai-ai-doc-outline-empty {
1566
+ margin: 0;
1567
+ padding: 0.25rem 0.25rem 0;
1568
+ color: oklch(var(--bc) / 0.58);
1569
+ font-size: 0.875rem;
1570
+ }
1571
+
1572
+ .hai-ai-doc-reader {
1573
+ min-width: 0;
1574
+ min-height: 0;
1575
+ position: relative;
1576
+ z-index: 1;
1577
+ }
1578
+
1579
+ .hai-ai-doc-scroll {
1580
+ position: relative;
1581
+ height: 100%;
1582
+ overflow: auto;
1583
+ padding: 0.5rem 0 1.75rem;
1584
+ }
1585
+
1586
+ .hai-ai-doc-selection-layer {
1587
+ position: absolute;
1588
+ z-index: 30;
1589
+ display: flex;
1590
+ flex-direction: column;
1591
+ gap: 0.45rem;
1592
+ width: min(max-content, calc(100% - 2rem));
1593
+ transform: translateX(-50%);
1594
+ pointer-events: none;
1595
+ }
1596
+
1597
+ .hai-ai-doc-selection-layer[data-placement='top'] {
1598
+ flex-direction: column-reverse;
1599
+ }
1600
+
1601
+ .hai-ai-doc-selection-layer[data-alignment='left'] {
1602
+ transform: none;
1603
+ }
1604
+
1605
+ .hai-ai-doc-selection-layer[data-alignment='right'] {
1606
+ transform: translateX(-100%);
1607
+ }
1608
+
1609
+ .hai-ai-doc-selection-toolbar {
1610
+ display: flex;
1611
+ align-items: center;
1612
+ gap: 0.25rem;
1613
+ flex-wrap: wrap;
1614
+ max-width: min(calc(100vw - 2rem), 34rem);
1615
+ padding: 0.38rem;
1616
+ border-radius: 1.2rem;
1617
+ background: #fff;
1618
+ border: 1px solid color-mix(in srgb, oklch(var(--bc)) 9%, white 91%);
1619
+ box-shadow:
1620
+ 0 30px 60px -34px rgb(15 23 42 / 0.28),
1621
+ 0 14px 26px -18px rgb(15 23 42 / 0.16),
1622
+ 0 0 0 1px rgb(255 255 255 / 0.94) inset;
1623
+ pointer-events: auto;
1624
+ isolation: isolate;
1625
+ }
1626
+
1627
+ .hai-ai-doc-selection-chip,
1628
+ .hai-ai-doc-selection-btn {
1629
+ min-height: 2.25rem;
1630
+ border-radius: 0.9rem;
1631
+ transition:
1632
+ background-color 0.14s ease,
1633
+ border-color 0.14s ease,
1634
+ color 0.14s ease,
1635
+ transform 0.14s ease;
1636
+ }
1637
+
1638
+ .hai-ai-doc-selection-chip {
1639
+ display: inline-flex;
1640
+ align-items: center;
1641
+ gap: 0.48rem;
1642
+ padding: 0 0.88rem;
1643
+ border: 1px solid oklch(var(--p) / 0.16);
1644
+ background: oklch(var(--p) / 0.1);
1645
+ color: oklch(var(--bc) / 0.92);
1646
+ font-weight: 700;
1647
+ cursor: pointer;
1648
+ }
1649
+
1650
+ .hai-ai-doc-selection-chip svg {
1651
+ width: 0.95rem;
1652
+ height: 0.95rem;
1653
+ fill: currentColor;
1654
+ }
1655
+
1656
+ .hai-ai-doc-selection-chip:not(:disabled):hover,
1657
+ .hai-ai-doc-selection-btn:hover,
1658
+ .hai-ai-doc-rewrite-menu button:hover {
1659
+ transform: translateY(-1px);
1660
+ }
1661
+
1662
+ .hai-ai-doc-selection-chip:disabled {
1663
+ opacity: 0.55;
1664
+ cursor: default;
1665
+ }
1666
+
1667
+ .hai-ai-doc-selection-btn {
1668
+ width: 2.25rem;
1669
+ min-width: 2.25rem;
1670
+ display: inline-flex;
1671
+ align-items: center;
1672
+ justify-content: center;
1673
+ padding: 0;
1674
+ border: 1px solid transparent;
1675
+ background: transparent;
1676
+ color: oklch(var(--bc) / 0.72);
1677
+ cursor: pointer;
1678
+ }
1679
+
1680
+ .hai-ai-doc-selection-btn-wide {
1681
+ min-width: 2.25rem;
1682
+ }
1683
+
1684
+ .hai-ai-doc-selection-btn:hover {
1685
+ border-color: oklch(var(--bc) / 0.1);
1686
+ background: oklch(var(--bc) / 0.045);
1687
+ color: oklch(var(--bc));
1688
+ }
1689
+
1690
+ .hai-ai-doc-selection-btn svg {
1691
+ width: 1rem;
1692
+ height: 1rem;
1693
+ fill: currentColor;
1694
+ }
1695
+
1696
+ .hai-ai-doc-selection-divider {
1697
+ width: 1px;
1698
+ height: 1.45rem;
1699
+ margin: 0 0.2rem;
1700
+ background: oklch(var(--bc) / 0.12);
1701
+ flex-shrink: 0;
1702
+ }
1703
+
1704
+ .hai-ai-doc-selection-highlight-icon {
1705
+ position: relative;
1706
+ font-weight: 700;
1707
+ }
1708
+
1709
+ .hai-ai-doc-selection-highlight-icon::after {
1710
+ content: '';
1711
+ position: absolute;
1712
+ left: -0.08rem;
1713
+ right: -0.08rem;
1714
+ bottom: -0.02rem;
1715
+ height: 0.36rem;
1716
+ border-radius: 9999px;
1717
+ background: oklch(var(--wa, 0.9 0.14 90) / 0.45);
1718
+ z-index: -1;
1719
+ }
1720
+
1721
+ .hai-ai-doc-rewrite-menu {
1722
+ width: 13.75rem;
1723
+ display: grid;
1724
+ gap: 0.375rem;
1725
+ padding: 0.5rem;
1726
+ border-radius: 1.125rem;
1727
+ background: #fff;
1728
+ border: 1px solid color-mix(in srgb, oklch(var(--bc)) 9%, white 91%);
1729
+ box-shadow:
1730
+ 0 30px 60px -34px rgb(15 23 42 / 0.28),
1731
+ 0 14px 26px -18px rgb(15 23 42 / 0.16),
1732
+ 0 0 0 1px rgb(255 255 255 / 0.94) inset;
1733
+ pointer-events: auto;
1734
+ isolation: isolate;
1735
+ }
1736
+
1737
+ .hai-ai-doc-rewrite-menu button {
1738
+ text-align: left;
1739
+ border: 1px solid transparent;
1740
+ border-radius: 0.875rem;
1741
+ padding: 0.7rem 0.8rem;
1742
+ color: oklch(var(--bc) / 0.9);
1743
+ background: transparent;
1744
+ cursor: pointer;
1745
+ }
1746
+
1747
+ .hai-ai-doc-rewrite-menu button:disabled {
1748
+ opacity: 0.55;
1749
+ cursor: default;
1750
+ }
1751
+
1752
+ .hai-markdown {
1753
+ line-height: 1.9;
1754
+ color: inherit;
1755
+ word-wrap: break-word;
1756
+ overflow-wrap: break-word;
1757
+ }
1758
+
1759
+ .hai-markdown-document {
1760
+ max-width: 52.5rem;
1761
+ margin: 0 auto;
1762
+ padding: 1.25rem 2rem 6rem;
1763
+ outline: none;
1764
+ }
1765
+
1766
+ :global(.hai-markdown-document ::selection) {
1767
+ background: var(--hai-ai-doc-selection-bg, rgb(59 130 246 / 0.22));
1768
+ color: var(--hai-ai-doc-selection-fg, inherit);
1769
+ }
1770
+
1771
+ :global(.hai-markdown-document ::-moz-selection) {
1772
+ background: var(--hai-ai-doc-selection-bg, rgb(59 130 246 / 0.22));
1773
+ color: var(--hai-ai-doc-selection-fg, inherit);
1774
+ }
1775
+
1776
+ .hai-markdown-document.hai-markdown-editable {
1777
+ min-height: 100%;
1778
+ }
1779
+
1780
+ .hai-markdown :global(h1),
1781
+ .hai-markdown :global(h2),
1782
+ .hai-markdown :global(h3),
1783
+ .hai-markdown :global(h4),
1784
+ .hai-markdown :global(h5),
1785
+ .hai-markdown :global(h6) {
1786
+ font-weight: 700;
1787
+ line-height: 1.25;
1788
+ margin-top: 1.6em;
1789
+ margin-bottom: 0.75em;
1790
+ letter-spacing: -0.02em;
1791
+ color: oklch(var(--bc));
1792
+ scroll-margin-top: 1rem;
1793
+ }
1794
+
1795
+ .hai-markdown :global(h1) {
1796
+ font-size: 2.2rem;
1797
+ padding-bottom: 0.45em;
1798
+ border-bottom: 1px solid oklch(var(--bc) / 0.12);
1799
+ }
1800
+
1801
+ .hai-markdown :global(h2) {
1802
+ font-size: 1.48rem;
1803
+ }
1804
+
1805
+ .hai-markdown :global(h3) {
1806
+ font-size: 1.18rem;
1807
+ }
1808
+
1809
+ .hai-markdown :global(h4) {
1810
+ font-size: 1.02rem;
1811
+ }
1812
+
1813
+ .hai-markdown :global(h5) {
1814
+ font-size: 0.95rem;
1815
+ }
1816
+
1817
+ .hai-markdown :global(h6) {
1818
+ font-size: 0.88rem;
1819
+ color: oklch(var(--bc) / 0.7);
1820
+ }
1821
+
1822
+ .hai-markdown :global(:first-child) {
1823
+ margin-top: 0;
1824
+ }
1825
+
1826
+ .hai-markdown :global(p) {
1827
+ margin-top: 0;
1828
+ margin-bottom: 1em;
1829
+ }
1830
+
1831
+ .hai-markdown :global(a) {
1832
+ color: oklch(var(--p));
1833
+ text-decoration: none;
1834
+ font-weight: 500;
1835
+ }
1836
+
1837
+ .hai-markdown :global(a:hover) {
1838
+ text-decoration: underline;
1839
+ }
1840
+
1841
+ .hai-markdown :global(strong) {
1842
+ font-weight: 700;
1843
+ }
1844
+
1845
+ .hai-markdown :global(em) {
1846
+ font-style: italic;
1847
+ }
1848
+
1849
+ .hai-markdown :global(del) {
1850
+ text-decoration: line-through;
1851
+ color: oklch(var(--bc) / 0.55);
1852
+ }
1853
+
1854
+ .hai-markdown :global(:not(pre) > code) {
1855
+ padding: 0.2em 0.4em;
1856
+ margin: 0 0.1em;
1857
+ font-size: 0.875em;
1858
+ font-family:
1859
+ ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
1860
+ 'Liberation Mono', monospace;
1861
+ background: oklch(var(--bc) / 0.08);
1862
+ border-radius: 0.375rem;
1863
+ word-break: break-word;
1864
+ }
1865
+
1866
+ .hai-markdown :global(.hai-md-code-block) {
1867
+ position: relative;
1868
+ margin: 1em 0;
1869
+ border-radius: 0.95rem;
1870
+ overflow: hidden;
1871
+ background: oklch(var(--n));
1872
+ color: oklch(var(--nc));
1873
+ box-shadow: 0 10px 26px oklch(var(--bc) / 0.08);
1874
+ }
1875
+
1876
+ .hai-markdown :global(.hai-md-code-header) {
1877
+ display: flex;
1878
+ align-items: center;
1879
+ justify-content: space-between;
1880
+ gap: 0.75rem;
1881
+ padding: 0.65rem 1rem;
1882
+ min-height: 2.5rem;
1883
+ background: oklch(var(--n) / 0.94);
1884
+ border-bottom: 1px solid oklch(var(--nc) / 0.1);
1885
+ }
1886
+
1887
+ .hai-markdown :global(.hai-md-code-header-main) {
1888
+ min-width: 0;
1889
+ display: flex;
1890
+ align-items: center;
1891
+ }
1892
+
1893
+ .hai-markdown :global(.hai-md-code-actions) {
1894
+ display: flex;
1895
+ align-items: center;
1896
+ gap: 0.35rem;
1897
+ }
1898
+
1899
+ .hai-markdown :global(.hai-md-code-lang) {
1900
+ font-size: 0.75rem;
1901
+ font-weight: 600;
1902
+ text-transform: uppercase;
1903
+ letter-spacing: 0.05em;
1904
+ color: oklch(var(--nc) / 0.6);
1905
+ user-select: none;
1906
+ }
1907
+
1908
+ .hai-markdown :global(.hai-md-code-lang-empty) {
1909
+ opacity: 0.7;
1910
+ }
1911
+
1912
+ .hai-markdown :global(.hai-md-code-action) {
1913
+ display: inline-flex;
1914
+ align-items: center;
1915
+ justify-content: center;
1916
+ width: 2rem;
1917
+ height: 2rem;
1918
+ border: none;
1919
+ border-radius: 0.45rem;
1920
+ background: transparent;
1921
+ color: oklch(var(--nc) / 0.55);
1922
+ cursor: pointer;
1923
+ transition: all 0.15s ease;
1924
+ }
1925
+
1926
+ .hai-markdown :global(.hai-md-code-action:hover) {
1927
+ background: oklch(var(--nc) / 0.1);
1928
+ color: oklch(var(--nc) / 0.92);
1929
+ }
1930
+
1931
+ .hai-markdown :global(.hai-md-copy-btn.hai-md-copied) {
1932
+ color: oklch(var(--su, 0.7 0.15 160));
1933
+ }
1934
+
1935
+ .hai-markdown :global(.hai-md-code-block pre) {
1936
+ margin: 0;
1937
+ padding: 1rem;
1938
+ overflow-x: auto;
1939
+ background: transparent;
1940
+ }
1941
+
1942
+ .hai-markdown :global(.hai-md-code-block code) {
1943
+ font-family:
1944
+ ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
1945
+ 'Liberation Mono', monospace;
1946
+ font-size: 0.875rem;
1947
+ line-height: 1.6;
1948
+ tab-size: 2;
1949
+ }
1950
+
1951
+ .hai-markdown :global(.hai-md-code-preview-slot) {
1952
+ border-top: 1px solid oklch(var(--nc) / 0.08);
1953
+ background: oklch(var(--b1));
1954
+ }
1955
+
1956
+ .hai-markdown :global(blockquote) {
1957
+ margin: 1em 0;
1958
+ padding: 0.65em 1.1em;
1959
+ border-left: 4px solid oklch(var(--p) / 0.4);
1960
+ background: oklch(var(--bc) / 0.03);
1961
+ border-radius: 0 0.5rem 0.5rem 0;
1962
+ color: oklch(var(--bc) / 0.85);
1963
+ }
1964
+
1965
+ .hai-markdown :global(blockquote p:last-child) {
1966
+ margin-bottom: 0;
1967
+ }
1968
+
1969
+ .hai-markdown :global(ul),
1970
+ .hai-markdown :global(ol) {
1971
+ margin: 0.5em 0 1em;
1972
+ padding-left: 2em;
1973
+ }
1974
+
1975
+ .hai-markdown :global(ul) {
1976
+ list-style-type: disc;
1977
+ }
1978
+
1979
+ .hai-markdown :global(ol) {
1980
+ list-style-type: decimal;
1981
+ }
1982
+
1983
+ .hai-markdown :global(li) {
1984
+ margin: 0.25em 0;
1985
+ line-height: 1.7;
1986
+ }
1987
+
1988
+ .hai-markdown :global(li > ul),
1989
+ .hai-markdown :global(li > ol) {
1990
+ margin: 0.25em 0;
1991
+ }
1992
+
1993
+ .hai-markdown :global(ul ul) {
1994
+ list-style-type: circle;
1995
+ }
1996
+
1997
+ .hai-markdown :global(ul ul ul) {
1998
+ list-style-type: square;
1999
+ }
2000
+
2001
+ .hai-markdown :global(ul:has(> li > input[type='checkbox'])) {
2002
+ list-style: none;
2003
+ padding-left: 0;
2004
+ }
2005
+
2006
+ .hai-markdown :global(li > input[type='checkbox']) {
2007
+ margin-right: 0.5em;
2008
+ vertical-align: middle;
2009
+ accent-color: oklch(var(--p));
2010
+ }
2011
+
2012
+ .hai-markdown :global(.hai-md-table-wrap) {
2013
+ margin: 1em 0;
2014
+ overflow-x: auto;
2015
+ border-radius: 0.5rem;
2016
+ border: 1px solid oklch(var(--bc) / 0.1);
2017
+ }
2018
+
2019
+ .hai-markdown :global(table) {
2020
+ width: 100%;
2021
+ border-collapse: collapse;
2022
+ font-size: 0.9em;
2023
+ }
2024
+
2025
+ .hai-markdown :global(thead) {
2026
+ background: oklch(var(--bc) / 0.04);
2027
+ }
2028
+
2029
+ .hai-markdown :global(th) {
2030
+ font-weight: 600;
2031
+ text-align: left;
2032
+ padding: 0.75rem 1rem;
2033
+ border-bottom: 2px solid oklch(var(--bc) / 0.12);
2034
+ }
2035
+
2036
+ .hai-markdown :global(td) {
2037
+ padding: 0.625rem 1rem;
2038
+ border-bottom: 1px solid oklch(var(--bc) / 0.06);
2039
+ }
2040
+
2041
+ .hai-markdown :global(tr:last-child td) {
2042
+ border-bottom: none;
2043
+ }
2044
+
2045
+ .hai-markdown :global(tbody tr:hover) {
2046
+ background: oklch(var(--bc) / 0.02);
2047
+ }
2048
+
2049
+ .hai-markdown :global(hr) {
2050
+ height: 0;
2051
+ margin: 2em 0;
2052
+ border: 0;
2053
+ border-top: 1px solid oklch(var(--bc) / 0.12);
2054
+ }
2055
+
2056
+ .hai-markdown :global(.hai-md-img) {
2057
+ max-width: 100%;
2058
+ height: auto;
2059
+ border-radius: 0.5rem;
2060
+ margin: 1em 0;
2061
+ }
2062
+
2063
+ .hai-markdown :global(kbd) {
2064
+ padding: 0.15em 0.4em;
2065
+ font-size: 0.85em;
2066
+ font-family: ui-monospace, SFMono-Regular, monospace;
2067
+ background: oklch(var(--bc) / 0.06);
2068
+ border: 1px solid oklch(var(--bc) / 0.15);
2069
+ border-radius: 0.25rem;
2070
+ box-shadow: inset 0 -1px 0 oklch(var(--bc) / 0.1);
2071
+ }
2072
+
2073
+ .hai-md-preview-card {
2074
+ margin: 0;
2075
+ padding: 1rem;
2076
+ background: oklch(var(--b1));
2077
+ color: oklch(var(--bc));
2078
+ }
2079
+
2080
+ .hai-md-preview-head {
2081
+ font-size: 0.8rem;
2082
+ font-weight: 700;
2083
+ letter-spacing: 0.04em;
2084
+ text-transform: uppercase;
2085
+ color: oklch(var(--bc) / 0.7);
2086
+ }
2087
+
2088
+ .hai-md-preview-desc {
2089
+ margin: 0.5rem 0 0;
2090
+ font-size: 0.875rem;
2091
+ color: oklch(var(--bc) / 0.7);
2092
+ }
2093
+
2094
+ :global(.hai-md-preview-card pre) {
2095
+ margin: 0.85rem 0 0;
2096
+ padding: 0.85rem 1rem;
2097
+ overflow-x: auto;
2098
+ border-radius: 0.85rem;
2099
+ background: oklch(var(--b2));
2100
+ font-size: 0.85rem;
2101
+ }
2102
+
2103
+ :global(.hai-md-preview-card.hai-md-preview-error pre) {
2104
+ color: oklch(var(--er));
2105
+ }
2106
+
2107
+ .hai-md-preview-frame {
2108
+ display: block;
2109
+ width: 100%;
2110
+ min-height: 280px;
2111
+ margin-top: 0.85rem;
2112
+ border: 1px solid oklch(var(--bc) / 0.1);
2113
+ border-radius: 1rem;
2114
+ background: white;
2115
+ }
2116
+
2117
+ .hai-md-preview-loading {
2118
+ width: 100%;
2119
+ height: 4px;
2120
+ margin-top: 0.85rem;
2121
+ border-radius: 9999px;
2122
+ background: linear-gradient(
2123
+ 90deg,
2124
+ oklch(var(--p) / 0.15) 0%,
2125
+ oklch(var(--p) / 0.5) 50%,
2126
+ oklch(var(--p) / 0.15) 100%
2127
+ );
2128
+ background-size: 220px 100%;
2129
+ animation: hai-md-loading 1.2s linear infinite;
2130
+ }
2131
+
2132
+ .hai-md-preview-rendered :global(:first-child) {
2133
+ margin-top: 0.85rem;
2134
+ }
2135
+
2136
+ .hai-markdown :global(.hljs-keyword),
2137
+ .hai-markdown :global(.hljs-selector-tag),
2138
+ .hai-markdown :global(.hljs-literal),
2139
+ .hai-markdown :global(.hljs-section),
2140
+ .hai-markdown :global(.hljs-link) {
2141
+ color: oklch(0.7 0.15 280);
2142
+ }
2143
+
2144
+ .hai-markdown :global(.hljs-string),
2145
+ .hai-markdown :global(.hljs-addition) {
2146
+ color: oklch(0.75 0.12 150);
2147
+ }
2148
+
2149
+ .hai-markdown :global(.hljs-number),
2150
+ .hai-markdown :global(.hljs-type) {
2151
+ color: oklch(0.78 0.12 70);
2152
+ }
2153
+
2154
+ .hai-markdown :global(.hljs-comment),
2155
+ .hai-markdown :global(.hljs-quote),
2156
+ .hai-markdown :global(.hljs-meta) {
2157
+ color: oklch(var(--nc) / 0.45);
2158
+ font-style: italic;
2159
+ }
2160
+
2161
+ .hai-markdown :global(.hljs-title),
2162
+ .hai-markdown :global(.hljs-name) {
2163
+ color: oklch(0.75 0.12 210);
2164
+ }
2165
+
2166
+ .hai-markdown :global(.hljs-variable),
2167
+ .hai-markdown :global(.hljs-template-variable) {
2168
+ color: oklch(0.8 0.1 30);
2169
+ }
2170
+
2171
+ .hai-markdown :global(.hljs-built_in),
2172
+ .hai-markdown :global(.hljs-builtin-name) {
2173
+ color: oklch(0.75 0.15 200);
2174
+ }
2175
+
2176
+ .hai-markdown :global(.hljs-attr),
2177
+ .hai-markdown :global(.hljs-attribute) {
2178
+ color: oklch(0.78 0.1 80);
2179
+ }
2180
+
2181
+ .hai-markdown :global(.hljs-symbol),
2182
+ .hai-markdown :global(.hljs-bullet) {
2183
+ color: oklch(0.75 0.12 320);
2184
+ }
2185
+
2186
+ .hai-markdown :global(.hljs-deletion) {
2187
+ color: oklch(0.7 0.15 25);
2188
+ }
2189
+
2190
+ .hai-markdown :global(.hljs-regexp),
2191
+ .hai-markdown :global(.hljs-selector-id),
2192
+ .hai-markdown :global(.hljs-selector-class) {
2193
+ color: oklch(0.7 0.15 25);
2194
+ }
2195
+
2196
+ .hai-markdown :global(.hljs-emphasis) {
2197
+ font-style: italic;
2198
+ }
2199
+
2200
+ .hai-markdown :global(.hljs-strong) {
2201
+ font-weight: 700;
2202
+ }
2203
+
2204
+ @keyframes hai-md-loading {
2205
+ from {
2206
+ background-position: 220px 0;
2207
+ }
2208
+
2209
+ to {
2210
+ background-position: -220px 0;
2211
+ }
2212
+ }
2213
+
2214
+ @media (max-width: 960px) {
2215
+ .hai-ai-doc-layout,
2216
+ .hai-ai-doc-layout.hai-ai-doc-layout-collapsed {
2217
+ grid-template-columns: minmax(0, 1fr);
2218
+ }
2219
+
2220
+ .hai-ai-doc-outline {
2221
+ display: none;
2222
+ }
2223
+
2224
+ .hai-ai-doc-topbar {
2225
+ padding-left: 1rem;
2226
+ padding-right: 1rem;
2227
+ }
2228
+ }
2229
+
2230
+ @media (max-width: 640px) {
2231
+ .hai-ai-doc-meta-bar {
2232
+ align-items: flex-start;
2233
+ flex-direction: column;
2234
+ }
2235
+
2236
+ .hai-ai-doc-toolbar,
2237
+ .hai-ai-doc-toolbar-heading {
2238
+ width: 100%;
2239
+ }
2240
+
2241
+ .hai-ai-doc-toolbar {
2242
+ justify-content: flex-start;
2243
+ }
2244
+
2245
+ .hai-ai-doc-scroll {
2246
+ padding-bottom: 1rem;
2247
+ }
2248
+
2249
+ .hai-markdown-document {
2250
+ padding-left: 1.125rem;
2251
+ padding-right: 1.125rem;
2252
+ }
2253
+
2254
+ .hai-ai-doc-selection-toolbar {
2255
+ max-width: calc(100vw - 2rem);
2256
+ overflow: auto;
2257
+ }
2258
+ }
2259
+ </style>