@h-ai/ui 0.1.0-alpha.11 → 0.1.0-alpha.12

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.
@@ -176,7 +176,6 @@
176
176
  // 复制前后的图标以内联 SVG 缓存,避免每次点击都重新拼接按钮内容。
177
177
  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>`
178
178
  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>`
179
- const HTML_SNIPPET_REGEX = /<!doctype html>|<html[\s>]|<body[\s>]|<div[\s>]|<main[\s>]/i
180
179
 
181
180
  let {
182
181
  // 暴露给外层的滚动容器引用,用于同步滚动或定位选区工具条。
@@ -197,6 +196,10 @@
197
196
  showCopyButton = true,
198
197
  // 是否显示代码块运行按钮与预览占位。
199
198
  showRunButton = false,
199
+ // 是否在代码块头部显示“代码/预览”切换。
200
+ showCodePreviewToggle = false,
201
+ // 代码预览切换模式下展示在语言标签旁的提示文案。
202
+ codePreviewHint = '',
200
203
  // 是否启用语法高亮。
201
204
  enableHighlight = true,
202
205
  // 是否把换行渲染为 <br>。
@@ -319,11 +322,19 @@
319
322
  resolveDocumentMarkdownContent(content, sourceKind, codeLanguage),
320
323
  )
321
324
  // 渲染结果同时提供 HTML、目录和代码块元数据,供顶部目录与代码预览共用。
325
+ const codePreviewToggleEnabled = $derived(
326
+ showCodePreviewToggle && sourceKind === 'code',
327
+ )
328
+ const normalizedCodePreviewHint = $derived(codePreviewHint.trim())
322
329
  const renderResult = $derived(
323
330
  renderMarkdownDocument(documentContent, {
324
331
  enableHighlight,
325
332
  showCopyButton,
326
333
  showRunButton,
334
+ showCodePreviewToggle: codePreviewToggleEnabled,
335
+ codeViewCodeLabel: uiM('markdown_format_code'),
336
+ codeViewPreviewLabel: uiM('file_list_preview'),
337
+ codePreviewHint: normalizedCodePreviewHint,
327
338
  breaks,
328
339
  }),
329
340
  )
@@ -337,6 +348,9 @@
337
348
  const resolvedRewriteActions = $derived(
338
349
  resolveRewriteActions(rewriteActions, onrewrite),
339
350
  )
351
+ const directRewriteAction = $derived(
352
+ resolvedRewriteActions.length === 1 ? resolvedRewriteActions[0] : null,
353
+ )
340
354
  // 新版工具条支持 paragraph / heading1-4,优先走细粒度回调,避免把旧接口误当成新接口调用。
341
355
  const richBlockFormattingEnabled = $derived(Boolean(onapplyblockstyle))
342
356
  // 旧版 heading / bullet 回调仍然保留,用于兼容外部还没迁移的调用方。
@@ -371,6 +385,7 @@
371
385
  cn(
372
386
  'hai-markdown',
373
387
  'hai-markdown-document',
388
+ sourceKind === 'code' ? 'hai-markdown-code-only' : '',
374
389
  editable ? 'hai-markdown-editable' : '',
375
390
  ),
376
391
  )
@@ -416,6 +431,7 @@
416
431
 
417
432
  $effect(() => {
418
433
  void documentContent
434
+ codePreviews = {}
419
435
  closeSelectionToolbar()
420
436
  })
421
437
 
@@ -483,6 +499,8 @@
483
499
  ]
484
500
  }
485
501
 
502
+ const HTML_DOCUMENT_PATTERN = /<!doctype html>|<html[\s>]|<body[\s>]|<div[\s>]|<main[\s>]/i
503
+
486
504
  async function copyRawContent(): Promise<boolean> {
487
505
  try {
488
506
  await navigator.clipboard.writeText(content)
@@ -549,9 +567,7 @@
549
567
  }
550
568
 
551
569
  function looksLikeHtml(code: string): boolean {
552
- return HTML_SNIPPET_REGEX.test(
553
- code,
554
- )
570
+ return HTML_DOCUMENT_PATTERN.test(code)
555
571
  }
556
572
 
557
573
  function buildJavaScriptPreview(code: string): string {
@@ -668,6 +684,38 @@ ${safeCode}
668
684
  return undefined
669
685
  }
670
686
 
687
+ function formatPreviewLanguageLabel(language: string | undefined): string | null {
688
+ const normalized = language?.trim().toLocaleLowerCase()
689
+ if (!normalized) {
690
+ return null
691
+ }
692
+
693
+ if (
694
+ normalized === 'javascript'
695
+ || normalized === 'js'
696
+ || normalized === 'mjs'
697
+ ) {
698
+ return 'JavaScript'
699
+ }
700
+
701
+ if (normalized === 'markdown' || normalized === 'md') {
702
+ return 'Markdown'
703
+ }
704
+
705
+ return normalized.toUpperCase()
706
+ }
707
+
708
+ function resolvePreviewUnavailableMessage(language: string | undefined): string {
709
+ const languageLabel = formatPreviewLanguageLabel(language)
710
+ if (!languageLabel) {
711
+ return uiM('markdown_run_unavailable')
712
+ }
713
+
714
+ return uiM('markdown_run_unavailable_language', {
715
+ language: languageLabel,
716
+ })
717
+ }
718
+
671
719
  async function runCodeBlock(blockId: string): Promise<void> {
672
720
  const codeBlock = lookupCodeBlock(blockId)
673
721
  if (!codeBlock) {
@@ -698,7 +746,7 @@ ${safeCode}
698
746
  ...codePreviews,
699
747
  [blockId]: {
700
748
  status: 'error',
701
- error: uiM('markdown_run_unavailable'),
749
+ error: resolvePreviewUnavailableMessage(request.language),
702
750
  },
703
751
  }
704
752
  return
@@ -724,6 +772,20 @@ ${safeCode}
724
772
  }
725
773
  }
726
774
 
775
+ function setCodeBlockView(
776
+ codeBlock: HTMLElement,
777
+ view: 'code' | 'preview',
778
+ ): void {
779
+ codeBlock.dataset.codeView = view
780
+ const toggleButtons = codeBlock.querySelectorAll<HTMLButtonElement>(
781
+ '[data-code-view-toggle]',
782
+ )
783
+ for (const button of toggleButtons) {
784
+ const isActive = button.dataset.codeView === view
785
+ button.setAttribute('aria-pressed', isActive ? 'true' : 'false')
786
+ }
787
+ }
788
+
727
789
  async function handleClick(event: MouseEvent): Promise<void> {
728
790
  const target = event.target as HTMLElement
729
791
  const copyButton = target.closest(
@@ -734,6 +796,26 @@ ${safeCode}
734
796
  return
735
797
  }
736
798
 
799
+ const viewButton = target.closest(
800
+ '[data-code-view-toggle]',
801
+ ) as HTMLButtonElement | null
802
+ const viewCodeBlock = viewButton?.closest<HTMLElement>('.hai-md-code-block')
803
+ const viewCodeBlockId = viewButton?.dataset.codeBlockId
804
+ const nextView = viewButton?.dataset.codeView === 'preview'
805
+ ? 'preview'
806
+ : 'code'
807
+ if (viewButton && viewCodeBlock) {
808
+ setCodeBlockView(viewCodeBlock, nextView)
809
+ if (
810
+ nextView === 'preview'
811
+ && viewCodeBlockId
812
+ && codePreviews[viewCodeBlockId]?.status !== 'ready'
813
+ ) {
814
+ await runCodeBlock(viewCodeBlockId)
815
+ }
816
+ return
817
+ }
818
+
737
819
  const runButton = target.closest(
738
820
  '[data-run-code]',
739
821
  ) as HTMLButtonElement | null
@@ -775,12 +857,12 @@ ${safeCode}
775
857
  }
776
858
 
777
859
  if (preview.status === 'running') {
778
- 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>`
860
+ host.innerHTML = `<div class="hai-md-preview-card"><div class="hai-md-preview-empty"><div class="hai-md-preview-empty-title">${escapePreviewText(uiM('markdown_running'))}</div><div class="hai-md-preview-loading"></div></div></div>`
779
861
  return
780
862
  }
781
863
 
782
864
  if (preview.status === 'error') {
783
- 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>`
865
+ host.innerHTML = `<div class="hai-md-preview-card hai-md-preview-error"><div class="hai-md-preview-empty"><div class="hai-md-preview-empty-title">${escapePreviewText(uiM('markdown_run_failed'))}</div><p class="hai-md-preview-empty-text">${escapePreviewText(preview.error ?? uiM('markdown_run_failed'))}</p></div></div>`
784
866
  return
785
867
  }
786
868
 
@@ -1539,7 +1621,7 @@ ${safeCode}
1539
1621
  {/if}
1540
1622
 
1541
1623
  <div
1542
- class:hai-ai-doc-layout-collapsed={outlineCollapsed}
1624
+ class:hai-ai-doc-layout-collapsed={!showOutline || outlineCollapsed}
1543
1625
  class='hai-ai-doc-layout'
1544
1626
  >
1545
1627
  {#if showOutline && !outlineCollapsed}
@@ -1613,12 +1695,19 @@ ${safeCode}
1613
1695
  type='button'
1614
1696
  class={cn(
1615
1697
  'hai-ai-doc-selection-chip',
1616
- activeSelectionMenu === 'rewrite'
1698
+ (!directRewriteAction && activeSelectionMenu === 'rewrite')
1617
1699
  ? 'hai-ai-doc-selection-chip--active'
1618
1700
  : '',
1619
1701
  )}
1620
1702
  disabled={rewritePending}
1621
- onclick={event => toggleSelectionMenu('rewrite', event)}
1703
+ onclick={(event) => {
1704
+ if (directRewriteAction) {
1705
+ void applyRewrite(directRewriteAction.id)
1706
+ return
1707
+ }
1708
+
1709
+ toggleSelectionMenu('rewrite', event)
1710
+ }}
1622
1711
  >
1623
1712
  <svg viewBox='0 0 24 24' aria-hidden='true'>
1624
1713
  <path
@@ -1905,7 +1994,7 @@ ${safeCode}
1905
1994
  {/if}
1906
1995
  </div>
1907
1996
 
1908
- {#if activeSelectionMenu === 'rewrite' && resolvedRewriteActions.length > 0}
1997
+ {#if activeSelectionMenu === 'rewrite' && resolvedRewriteActions.length > 1}
1909
1998
  <div
1910
1999
  class='hai-ai-doc-rewrite-menu'
1911
2000
  data-menu-alignment={selectionMenuAlignment}
@@ -2126,8 +2215,12 @@ ${safeCode}
2126
2215
  );
2127
2216
  --hai-ai-doc-selection-fg: oklch(var(--bc, 0.22 0 0));
2128
2217
  display: flex;
2218
+ width: 100%;
2219
+ min-width: 0;
2129
2220
  min-height: 0;
2130
2221
  height: 100%;
2222
+ container-type: inline-size;
2223
+ container-name: ai-doc-editor;
2131
2224
  flex-direction: column;
2132
2225
  border: 1px solid oklch(var(--bc) / 0.08);
2133
2226
  border-radius: 1.5rem;
@@ -2141,8 +2234,10 @@ ${safeCode}
2141
2234
  }
2142
2235
 
2143
2236
  .hai-ai-doc-shell {
2144
- display: grid;
2145
- grid-template-rows: auto auto 1fr;
2237
+ display: flex;
2238
+ flex-direction: column;
2239
+ width: 100%;
2240
+ min-width: 0;
2146
2241
  min-height: 0;
2147
2242
  height: 100%;
2148
2243
  }
@@ -2336,18 +2431,23 @@ ${safeCode}
2336
2431
  }
2337
2432
 
2338
2433
  .hai-ai-doc-layout {
2434
+ width: 100%;
2339
2435
  min-height: 0;
2340
- display: grid;
2341
- grid-template-columns: 220px minmax(0, 1fr);
2436
+ min-width: 0;
2437
+ flex: 1 1 auto;
2438
+ display: flex;
2439
+ align-items: stretch;
2342
2440
  position: relative;
2343
2441
  overflow: hidden;
2344
2442
  }
2345
2443
 
2346
2444
  .hai-ai-doc-layout.hai-ai-doc-layout-collapsed {
2347
- grid-template-columns: minmax(0, 1fr);
2445
+ display: flex;
2348
2446
  }
2349
2447
 
2350
2448
  .hai-ai-doc-outline {
2449
+ width: 220px;
2450
+ flex: 0 0 220px;
2351
2451
  min-height: 0;
2352
2452
  display: grid;
2353
2453
  grid-template-rows: auto 1fr;
@@ -2446,18 +2546,28 @@ ${safeCode}
2446
2546
  }
2447
2547
 
2448
2548
  .hai-ai-doc-reader {
2549
+ width: 100%;
2550
+ flex: 1 1 auto;
2449
2551
  min-width: 0;
2450
2552
  min-height: 0;
2451
2553
  position: relative;
2452
2554
  z-index: 2;
2453
2555
  isolation: isolate;
2556
+ display: flex;
2557
+ flex-direction: column;
2454
2558
  }
2455
2559
 
2456
2560
  .hai-ai-doc-scroll {
2457
2561
  position: relative;
2562
+ width: 100%;
2563
+ min-width: 0;
2458
2564
  height: 100%;
2459
2565
  overflow: auto;
2460
2566
  padding: 0.5rem 0 1.75rem;
2567
+ display: flex;
2568
+ flex-direction: column;
2569
+ flex: 1 1 auto;
2570
+ min-height: 0;
2461
2571
  }
2462
2572
 
2463
2573
  .hai-ai-doc-selection-layer {
@@ -2875,12 +2985,28 @@ ${safeCode}
2875
2985
  }
2876
2986
 
2877
2987
  .hai-markdown-document {
2988
+ width: 100%;
2989
+ min-width: 0;
2878
2990
  max-width: 52.5rem;
2879
2991
  margin: 0 auto;
2880
2992
  padding: 1.25rem 2rem 6rem;
2881
2993
  outline: none;
2882
2994
  }
2883
2995
 
2996
+ .hai-markdown-document.hai-markdown-code-only {
2997
+ max-width: none !important;
2998
+ width: 100% !important;
2999
+ min-width: 0;
3000
+ margin: 0 !important;
3001
+ height: 100%;
3002
+ min-height: 100%;
3003
+ padding: 0.5rem 0.75rem 0.75rem;
3004
+ display: flex;
3005
+ flex-direction: column;
3006
+ flex: 1 1 auto;
3007
+ box-sizing: border-box;
3008
+ }
3009
+
2884
3010
  :global(.hai-markdown-document ::selection) {
2885
3011
  background: var(--hai-ai-doc-selection-bg, rgb(59 130 246 / 0.22));
2886
3012
  color: var(--hai-ai-doc-selection-fg, inherit);
@@ -3019,6 +3145,7 @@ ${safeCode}
3019
3145
  gap: 0.75rem;
3020
3146
  padding: 0.65rem 1rem;
3021
3147
  min-height: 2.5rem;
3148
+ line-height: 1.2;
3022
3149
  background: oklch(var(--n) / 0.94);
3023
3150
  border-bottom: 1px solid oklch(var(--nc) / 0.1);
3024
3151
  }
@@ -3027,6 +3154,7 @@ ${safeCode}
3027
3154
  min-width: 0;
3028
3155
  display: flex;
3029
3156
  align-items: center;
3157
+ gap: 0.55rem;
3030
3158
  }
3031
3159
 
3032
3160
  .hai-markdown :global(.hai-md-code-actions) {
@@ -3038,12 +3166,54 @@ ${safeCode}
3038
3166
  .hai-markdown :global(.hai-md-code-lang) {
3039
3167
  font-size: 0.75rem;
3040
3168
  font-weight: 600;
3169
+ line-height: 1;
3041
3170
  text-transform: uppercase;
3042
3171
  letter-spacing: 0.05em;
3043
3172
  color: oklch(var(--nc) / 0.6);
3044
3173
  user-select: none;
3045
3174
  }
3046
3175
 
3176
+ .hai-markdown :global(.hai-md-code-view-switch) {
3177
+ display: inline-flex;
3178
+ align-items: center;
3179
+ gap: 0.16rem;
3180
+ padding: 0.15rem;
3181
+ border-radius: 9999px;
3182
+ border: 1px solid oklch(var(--nc) / 0.16);
3183
+ background: oklch(var(--nc) / 0.08);
3184
+ flex-shrink: 0;
3185
+ }
3186
+
3187
+ .hai-markdown :global(.hai-md-code-view-btn) {
3188
+ min-height: 1.7rem;
3189
+ padding: 0 0.72rem;
3190
+ border: none;
3191
+ border-radius: 9999px;
3192
+ background: transparent;
3193
+ color: oklch(var(--nc) / 0.72);
3194
+ font-size: 0.86rem;
3195
+ font-weight: 600;
3196
+ line-height: 1;
3197
+ cursor: pointer;
3198
+ transition:
3199
+ background-color 0.15s ease,
3200
+ color 0.15s ease;
3201
+ }
3202
+
3203
+ .hai-markdown :global(.hai-md-code-view-btn[aria-pressed='true']) {
3204
+ background: oklch(var(--nc) / 0.2);
3205
+ color: oklch(var(--nc));
3206
+ }
3207
+
3208
+ .hai-markdown :global(.hai-md-code-preview-hint) {
3209
+ font-size: 0.75rem;
3210
+ line-height: 1;
3211
+ color: oklch(var(--nc) / 0.48);
3212
+ white-space: nowrap;
3213
+ overflow: hidden;
3214
+ text-overflow: ellipsis;
3215
+ }
3216
+
3047
3217
  .hai-markdown :global(.hai-md-code-lang-empty) {
3048
3218
  opacity: 0.7;
3049
3219
  }
@@ -3083,8 +3253,21 @@ ${safeCode}
3083
3253
  ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
3084
3254
  'Liberation Mono', monospace;
3085
3255
  font-size: 0.875rem;
3086
- line-height: 1.6;
3256
+ line-height: 1.45;
3087
3257
  tab-size: 2;
3258
+
3259
+ /* Shiki CSS Variables 主题(由 highlight.ts 的 createCssVariablesTheme 引用) */
3260
+ --hai-hl-foreground: oklch(var(--nc));
3261
+ --hai-hl-background: transparent;
3262
+ --hai-hl-token-keyword: oklch(0.7 0.15 280);
3263
+ --hai-hl-token-string: oklch(0.75 0.12 150);
3264
+ --hai-hl-token-string-expression: oklch(0.75 0.12 150);
3265
+ --hai-hl-token-constant: oklch(0.78 0.12 70);
3266
+ --hai-hl-token-comment: oklch(var(--nc) / 0.45);
3267
+ --hai-hl-token-function: oklch(0.72 0.14 220);
3268
+ --hai-hl-token-parameter: oklch(0.72 0.12 35);
3269
+ --hai-hl-token-punctuation: oklch(var(--nc) / 0.7);
3270
+ --hai-hl-token-link: oklch(0.7 0.15 280);
3088
3271
  }
3089
3272
 
3090
3273
  .hai-markdown :global(.hai-md-code-preview-slot) {
@@ -3092,6 +3275,76 @@ ${safeCode}
3092
3275
  background: oklch(var(--b1));
3093
3276
  }
3094
3277
 
3278
+ .hai-markdown :global(.hai-md-code-block[data-code-view='code'] .hai-md-code-preview-slot) {
3279
+ display: none;
3280
+ }
3281
+
3282
+ .hai-markdown :global(.hai-md-code-block[data-code-view='preview'] pre) {
3283
+ display: none;
3284
+ }
3285
+
3286
+ .hai-markdown :global(.hai-md-code-block[data-code-view='preview'] .hai-md-code-preview-slot) {
3287
+ display: block;
3288
+ }
3289
+
3290
+ .hai-markdown-code-only :global(.hai-md-code-block) {
3291
+ width: 100% !important;
3292
+ max-width: none !important;
3293
+ min-width: 0;
3294
+ margin: 0;
3295
+ height: auto;
3296
+ min-height: 0;
3297
+ flex: 1 1 auto;
3298
+ display: flex !important;
3299
+ align-self: stretch;
3300
+ align-items: stretch;
3301
+ flex-direction: column;
3302
+ overflow: hidden;
3303
+ }
3304
+
3305
+ .hai-markdown-code-only :global(.hai-md-code-header),
3306
+ .hai-markdown-code-only :global(.hai-md-code-block pre),
3307
+ .hai-markdown-code-only :global(.hai-md-code-preview-slot) {
3308
+ width: 100%;
3309
+ min-width: 0;
3310
+ min-height: 0;
3311
+ box-sizing: border-box;
3312
+ }
3313
+
3314
+ /* 代码/预览切换时统一固定 header 高度,避免视觉抖动。 */
3315
+ .hai-markdown-code-only :global(.hai-md-code-header) {
3316
+ flex: 0 0 3.1rem;
3317
+ height: 3.1rem;
3318
+ min-height: 3.1rem;
3319
+ padding: 0 1rem;
3320
+ align-items: center;
3321
+ }
3322
+
3323
+ .hai-markdown-code-only :global(.hai-md-code-block pre),
3324
+ .hai-markdown-code-only :global(.hai-md-code-preview-slot) {
3325
+ flex: 1 1 auto;
3326
+ height: auto;
3327
+ min-height: 0;
3328
+ }
3329
+
3330
+ .hai-markdown-code-only :global(.hai-md-code-block pre) {
3331
+ overflow: auto;
3332
+ }
3333
+
3334
+ .hai-markdown-code-only :global(.hai-md-code-preview-slot) {
3335
+ background: oklch(var(--n));
3336
+ color: oklch(var(--nc));
3337
+ }
3338
+
3339
+ .hai-markdown-code-only :global(.hai-md-code-block[data-code-view='preview']) {
3340
+ flex: 1 1 auto;
3341
+ }
3342
+
3343
+ .hai-markdown-code-only :global(.hai-md-code-block[data-code-view='preview'] .hai-md-code-preview-slot) {
3344
+ flex: 1 1 auto;
3345
+ display: flex;
3346
+ }
3347
+
3095
3348
  .hai-markdown :global(blockquote) {
3096
3349
  margin: 1em 0;
3097
3350
  padding: 0.65em 1.1em;
@@ -3209,14 +3462,29 @@ ${safeCode}
3209
3462
  box-shadow: inset 0 -1px 0 oklch(var(--bc) / 0.1);
3210
3463
  }
3211
3464
 
3212
- .hai-md-preview-card {
3465
+ :global(.hai-md-preview-card) {
3213
3466
  margin: 0;
3214
3467
  padding: 1rem;
3215
3468
  background: oklch(var(--b1));
3216
3469
  color: oklch(var(--bc));
3217
3470
  }
3218
3471
 
3219
- .hai-md-preview-head {
3472
+ .hai-markdown-code-only :global(.hai-md-code-block[data-code-view='preview'] .hai-md-preview-card) {
3473
+ display: flex;
3474
+ flex-direction: column;
3475
+ flex: 1 1 auto;
3476
+ min-height: 0;
3477
+ padding: 0.95rem 1rem;
3478
+ background: oklch(var(--n));
3479
+ color: oklch(var(--nc));
3480
+ }
3481
+
3482
+ .hai-markdown-code-only :global(.hai-md-code-block[data-code-view='preview'] .hai-md-preview-head),
3483
+ .hai-markdown-code-only :global(.hai-md-code-block[data-code-view='preview'] .hai-md-preview-desc) {
3484
+ display: none;
3485
+ }
3486
+
3487
+ :global(.hai-md-preview-head) {
3220
3488
  font-size: 0.8rem;
3221
3489
  font-weight: 700;
3222
3490
  letter-spacing: 0.04em;
@@ -3224,12 +3492,43 @@ ${safeCode}
3224
3492
  color: oklch(var(--bc) / 0.7);
3225
3493
  }
3226
3494
 
3227
- .hai-md-preview-desc {
3495
+ :global(.hai-md-preview-desc) {
3228
3496
  margin: 0.5rem 0 0;
3229
3497
  font-size: 0.875rem;
3230
3498
  color: oklch(var(--bc) / 0.7);
3231
3499
  }
3232
3500
 
3501
+ :global(.hai-md-preview-empty) {
3502
+ min-height: 100%;
3503
+ display: flex;
3504
+ flex-direction: column;
3505
+ gap: 0.62rem;
3506
+ }
3507
+
3508
+ :global(.hai-md-preview-empty-title) {
3509
+ margin: 0;
3510
+ font-size: 0.9rem;
3511
+ font-weight: 700;
3512
+ letter-spacing: 0.01em;
3513
+ color: oklch(var(--bc) / 0.85);
3514
+ }
3515
+
3516
+ :global(.hai-md-preview-empty-text) {
3517
+ margin: 0;
3518
+ font-size: 0.84rem;
3519
+ line-height: 1.58;
3520
+ white-space: pre-wrap;
3521
+ color: oklch(var(--bc) / 0.74);
3522
+ }
3523
+
3524
+ .hai-markdown-code-only :global(.hai-md-preview-empty-title) {
3525
+ color: oklch(var(--nc) / 0.88);
3526
+ }
3527
+
3528
+ .hai-markdown-code-only :global(.hai-md-preview-empty-text) {
3529
+ color: oklch(var(--nc) / 0.72);
3530
+ }
3531
+
3233
3532
  :global(.hai-md-preview-card pre) {
3234
3533
  margin: 0.85rem 0 0;
3235
3534
  padding: 0.85rem 1rem;
@@ -3243,7 +3542,7 @@ ${safeCode}
3243
3542
  color: oklch(var(--er));
3244
3543
  }
3245
3544
 
3246
- .hai-md-preview-frame {
3545
+ :global(.hai-md-preview-frame) {
3247
3546
  display: block;
3248
3547
  width: 100%;
3249
3548
  min-height: 280px;
@@ -3253,10 +3552,19 @@ ${safeCode}
3253
3552
  background: white;
3254
3553
  }
3255
3554
 
3256
- .hai-md-preview-loading {
3555
+ .hai-markdown-code-only :global(.hai-md-code-block[data-code-view='preview'] .hai-md-preview-frame) {
3556
+ flex: 1 1 auto;
3557
+ height: 100%;
3558
+ min-height: 0;
3559
+ margin-top: 0;
3560
+ border: none;
3561
+ border-radius: 0;
3562
+ }
3563
+
3564
+ :global(.hai-md-preview-loading) {
3257
3565
  width: 100%;
3258
3566
  height: 4px;
3259
- margin-top: 0.85rem;
3567
+ margin-top: 0.5rem;
3260
3568
  border-radius: 9999px;
3261
3569
  background: linear-gradient(
3262
3570
  90deg,
@@ -3268,27 +3576,10 @@ ${safeCode}
3268
3576
  animation: hai-md-loading 1.2s linear infinite;
3269
3577
  }
3270
3578
 
3271
- .hai-md-preview-rendered :global(:first-child) {
3579
+ :global(.hai-md-preview-rendered > :first-child) {
3272
3580
  margin-top: 0.85rem;
3273
3581
  }
3274
3582
 
3275
- /* Shiki CSS Variables 主题 — 代码高亮 token 颜色 */
3276
- .hai-markdown :global(.hai-md-code-block code) {
3277
- --hai-hl-fg: oklch(var(--nc));
3278
- --hai-hl-bg: transparent;
3279
-
3280
- /* token 颜色 */
3281
- --hai-hl-token-keyword: oklch(0.7 0.15 280);
3282
- --hai-hl-token-string: oklch(0.75 0.12 150);
3283
- --hai-hl-token-string-expression: oklch(0.75 0.12 150);
3284
- --hai-hl-token-constant: oklch(0.78 0.12 70);
3285
- --hai-hl-token-comment: oklch(var(--nc) / 0.45);
3286
- --hai-hl-token-function: oklch(0.72 0.14 220);
3287
- --hai-hl-token-parameter: oklch(0.72 0.12 35);
3288
- --hai-hl-token-punctuation: oklch(var(--nc) / 0.7);
3289
- --hai-hl-token-link: oklch(0.7 0.15 280);
3290
- }
3291
-
3292
3583
  @keyframes hai-md-loading {
3293
3584
  from {
3294
3585
  background-position: 220px 0;
@@ -3299,16 +3590,31 @@ ${safeCode}
3299
3590
  }
3300
3591
  }
3301
3592
 
3302
- @media (max-width: 960px) {
3303
- .hai-ai-doc-layout,
3304
- .hai-ai-doc-layout.hai-ai-doc-layout-collapsed {
3305
- grid-template-columns: minmax(0, 1fr);
3593
+ @container ai-doc-editor (max-width: 800px) {
3594
+ .hai-ai-doc-layout {
3595
+ display: flex;
3596
+ min-height: 0;
3306
3597
  }
3307
3598
 
3308
3599
  .hai-ai-doc-outline {
3309
- display: none;
3600
+ position: absolute;
3601
+ top: 0.65rem;
3602
+ left: 0.65rem;
3603
+ bottom: 0.85rem;
3604
+ width: min(220px, calc(100% - 1.3rem));
3605
+ max-width: 220px;
3606
+ z-index: 14;
3607
+ padding: 0.5rem 0.625rem 0.9rem 0.75rem;
3608
+ border: 1px solid oklch(var(--bc) / 0.12);
3609
+ border-radius: 0.95rem;
3610
+ background:
3611
+ linear-gradient(180deg, oklch(var(--b2) / 0.84), oklch(var(--b1) / 0.92));
3612
+ box-shadow: 0 20px 40px -28px oklch(var(--bc) / 0.35);
3613
+ backdrop-filter: blur(8px);
3310
3614
  }
3615
+ }
3311
3616
 
3617
+ @media (max-width: 960px) {
3312
3618
  .hai-ai-doc-topbar {
3313
3619
  padding-left: 1rem;
3314
3620
  padding-right: 1rem;
@@ -395,8 +395,8 @@
395
395
 
396
396
  .hai-markdown :global(.hai-md-code-block code) {
397
397
  /* ─── 基础色 ─── */
398
- --hai-hl-fg: oklch(var(--nc));
399
- --hai-hl-bg: transparent;
398
+ --hai-hl-foreground: oklch(var(--nc));
399
+ --hai-hl-background: transparent;
400
400
 
401
401
  /* ─── Token 颜色(DaisyUI oklch 自适应) ─── */
402
402
  --hai-hl-token-keyword: oklch(0.7 0.15 280);