hd-garden 0.5.5 → 0.5.6

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.
@@ -404,6 +404,13 @@ export function TableOfContents({
404
404
  <List size={17} weight="bold" aria-hidden="true" />
405
405
  <span>目录</span>
406
406
  </button>
407
+ {/* Without JavaScript the button above does nothing. This link opens the
408
+ drawer through :target instead; CSS shows it only when scripting is
409
+ off, so with JavaScript nothing here is visible. */}
410
+ <a className="toc-nojs-trigger" id={`${panelId}-open`} href={`#${panelId}`} aria-label={`打开${label}`}>
411
+ <List size={17} weight="bold" aria-hidden="true" />
412
+ <span>目录</span>
413
+ </a>
407
414
  <button
408
415
  className="toc-mobile-backdrop"
409
416
  type="button"
@@ -429,6 +436,9 @@ export function TableOfContents({
429
436
  >
430
437
  <X size={18} aria-hidden="true" />
431
438
  </button>
439
+ <a className="toc-nojs-close" href={`#${panelId}-open`} aria-label={`关闭${label}`}>
440
+ <X size={18} aria-hidden="true" />
441
+ </a>
432
442
  </div>
433
443
  <ol id={listId} ref={listRef} className="toc-list">
434
444
  {tree.map((node, index) => renderNode(node, [index]))}
package/app/globals.css CHANGED
@@ -1987,7 +1987,9 @@ button {
1987
1987
 
1988
1988
  .toc-mobile-trigger,
1989
1989
  .toc-mobile-backdrop,
1990
- .toc-mobile-close {
1990
+ .toc-mobile-close,
1991
+ .toc-nojs-trigger,
1992
+ .toc-nojs-close {
1991
1993
  display: none;
1992
1994
  }
1993
1995
 
@@ -3731,7 +3733,8 @@ html[data-theme="dark"] .article-references {
3731
3733
  overflow: visible;
3732
3734
  }
3733
3735
 
3734
- .toc-mobile-trigger {
3736
+ .toc-mobile-trigger,
3737
+ .toc-nojs-trigger {
3735
3738
  position: fixed;
3736
3739
  right: calc(16px + env(safe-area-inset-right));
3737
3740
  bottom: calc(16px + env(safe-area-inset-bottom));
@@ -3754,19 +3757,25 @@ html[data-theme="dark"] .article-references {
3754
3757
  }
3755
3758
 
3756
3759
  :root[data-theme="dark"] .toc-mobile-trigger,
3757
- html[data-theme="dark"] .toc-mobile-trigger {
3760
+ html[data-theme="dark"] .toc-mobile-trigger,
3761
+ :root[data-theme="dark"] .toc-nojs-trigger,
3762
+ html[data-theme="dark"] .toc-nojs-trigger {
3758
3763
  background: rgb(30 29 26 / 94%);
3759
3764
  border-color: var(--rule-strong);
3760
3765
  color: var(--ink);
3761
3766
  }
3762
3767
 
3763
3768
  .toc-mobile-trigger:hover,
3764
- .toc-mobile-trigger:focus-visible {
3769
+ .toc-mobile-trigger:focus-visible,
3770
+ .toc-nojs-trigger:hover,
3771
+ .toc-nojs-trigger:focus-visible {
3765
3772
  transform: translateY(-2px);
3766
3773
  }
3767
3774
 
3768
3775
  .toc-mobile-trigger:focus-visible,
3769
- .toc-mobile-close:focus-visible {
3776
+ .toc-mobile-close:focus-visible,
3777
+ .toc-nojs-trigger:focus-visible,
3778
+ .toc-nojs-close:focus-visible {
3770
3779
  outline: 2px solid var(--accent);
3771
3780
  outline-offset: 3px;
3772
3781
  }
@@ -3812,7 +3821,7 @@ html[data-theme="dark"] .article-references {
3812
3821
  max-height: min(72vh, 560px);
3813
3822
  max-height: min(72dvh, 560px);
3814
3823
  margin: 0;
3815
- padding: 0 24px calc(18px + env(safe-area-inset-bottom));
3824
+ padding: 0 0 calc(18px + env(safe-area-inset-bottom));
3816
3825
  display: flex;
3817
3826
  border: 0;
3818
3827
  border-top: 1px solid var(--line);
@@ -3849,7 +3858,8 @@ html[data-theme="dark"] .article-references {
3849
3858
  letter-spacing: 0.04em;
3850
3859
  }
3851
3860
 
3852
- .toc-mobile-close {
3861
+ .toc-mobile-close,
3862
+ .toc-nojs-close {
3853
3863
  width: 34px;
3854
3864
  height: 34px;
3855
3865
  padding: 0;
@@ -3862,14 +3872,31 @@ html[data-theme="dark"] .article-references {
3862
3872
  touch-action: manipulation;
3863
3873
  }
3864
3874
 
3865
- .toc-mobile-close:hover {
3875
+ .toc-mobile-close:hover,
3876
+ .toc-nojs-close:hover {
3866
3877
  color: var(--ink);
3867
3878
  }
3868
3879
 
3880
+ /* The no-JS links share the button styles above but stay hidden unless
3881
+ scripting is off (see the scripting: none block below). */
3882
+ .toc-nojs-trigger,
3883
+ .toc-nojs-close {
3884
+ display: none;
3885
+ }
3886
+
3869
3887
  .toc .toc-list {
3870
3888
  padding: 7px 4px 4px 0;
3871
3889
  }
3872
3890
 
3891
+ /* Keep the drawer's title and rows off the screen edge, in line with the
3892
+ body text. The inset lives on the header and the list rather than the
3893
+ panel, because the post layout's desktop rule overrides the panel's
3894
+ padding on post pages but not on column pages. */
3895
+ .toc .toc-header,
3896
+ .toc .toc-list {
3897
+ padding-inline: 20px;
3898
+ }
3899
+
3873
3900
  .toc li {
3874
3901
  margin: 10px 0;
3875
3902
  font-size: 13px;
@@ -5468,3 +5495,52 @@ html[data-theme="dark"] .article-references {
5468
5495
  justify-content: flex-start;
5469
5496
  }
5470
5497
  }
5498
+
5499
+ /* No JavaScript: the drawer button cannot open anything, so swap in plain
5500
+ links that open the drawer through :target. A TOC link moves the target to
5501
+ the heading, which closes the drawer and jumps there natively. */
5502
+ @media (max-width: 900px) and (scripting: none) {
5503
+ .toc-mobile-trigger,
5504
+ .toc-mobile-close {
5505
+ display: none;
5506
+ }
5507
+
5508
+ .toc-nojs-trigger {
5509
+ display: inline-flex;
5510
+ color: var(--ink);
5511
+ text-decoration: none;
5512
+ }
5513
+
5514
+ .toc-nojs-close {
5515
+ display: grid;
5516
+ text-decoration: none;
5517
+ }
5518
+
5519
+ /* Sub-lists are all open without JS, so the collapse toggles have no job. */
5520
+ .toc-section-toggle {
5521
+ display: none;
5522
+ }
5523
+
5524
+ /* Same dim as the JS drawer; taps pass through to the page behind. */
5525
+ body:has(.toc:target) .toc-mobile-backdrop {
5526
+ opacity: 1;
5527
+ visibility: visible;
5528
+ transition: opacity 180ms ease, visibility 0s;
5529
+ }
5530
+
5531
+ body:has(.toc:target) .toc-nojs-trigger {
5532
+ opacity: 0;
5533
+ visibility: hidden;
5534
+ pointer-events: none;
5535
+ }
5536
+
5537
+ .reading-page > .toc:target,
5538
+ .post-reading-page .article-reading-layout .toc:target,
5539
+ .column-reading-page > .toc:target,
5540
+ .garden-page-toc .toc:target {
5541
+ visibility: visible;
5542
+ pointer-events: auto;
5543
+ transform: translateY(0);
5544
+ transition: transform 220ms ease, visibility 0s;
5545
+ }
5546
+ }
@@ -145,10 +145,13 @@ function rewriteAlert(node) {
145
145
  let label = gfm ? gfm.label : "";
146
146
  let rest = gfm?.body ? [gfm.body, ...children.slice(1)] : children.slice(1);
147
147
  if (!label) {
148
- const custom = strongOnlyText(rest.find((child) => !isBlankParagraph(child)));
149
- if (custom) {
148
+ // A leading bold-only line is the title only when body text follows it.
149
+ // On its own it stays the body, under the default label.
150
+ const index = rest.findIndex((child) => !isBlankParagraph(child));
151
+ const custom = strongOnlyText(rest[index]);
152
+ const hasBody = rest.slice(index + 1).some((child) => !isBlankParagraph(child));
153
+ if (custom && hasBody) {
150
154
  label = custom;
151
- const index = rest.findIndex((child) => !isBlankParagraph(child));
152
155
  rest = rest.filter((_, childIndex) => childIndex !== index);
153
156
  }
154
157
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hd-garden",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "private": false,
5
5
  "description": "用 Markdown 写作、用 GitHub Pages 发布的个人博客引擎。",
6
6
  "license": "MIT",
@@ -1,7 +1,7 @@
1
1
  // A compare cell written as one line, 「X:Y」 or "X: Y". X is the title and Y
2
2
  // the text. The split is at the first full-width colon, or a colon followed by
3
3
  // a space, that is not inside inline code, link text or a link URL. The
4
- // separator and any space after it are returned as written, so
4
+ // separator, with the spaces on either side of it, is returned as written, so
5
5
  // title + separator + text always equals the original line.
6
6
 
7
7
  // End of a backtick code span starting at `start`, or -1 when it never closes.
@@ -72,11 +72,15 @@ export function splitCompareLine(line) {
72
72
  if (end > 0 && /^<[a-z][a-z0-9+.-]*:[^\s<>]*>$/iu.test(line.slice(at, end + 1))) { at = end; continue; }
73
73
  }
74
74
  const isSeparator = char === ":" || (char === ":" && line[at + 1] === " ");
75
- if (!isSeparator || at === 0) continue;
75
+ if (!isSeparator) continue;
76
+ // Spaces on either side of the colon belong to the separator.
77
+ let start = at;
78
+ while (start > 0 && /\s/u.test(line[start - 1])) start -= 1;
79
+ if (start === 0) continue;
76
80
  let end = at + 1;
77
81
  while (end < line.length && /\s/u.test(line[end])) end += 1;
78
82
  if (end >= line.length) return null;
79
- return { title: line.slice(0, at), separator: line.slice(at, end), text: line.slice(end) };
83
+ return { title: line.slice(0, start), separator: line.slice(start, end), text: line.slice(end) };
80
84
  }
81
85
  return null;
82
86
  }