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
|
|
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
|
+
}
|
package/app/lib/remark-a2.mjs
CHANGED
|
@@ -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
|
-
|
|
149
|
-
|
|
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,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
|
|
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
|
|
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,
|
|
83
|
+
return { title: line.slice(0, start), separator: line.slice(start, end), text: line.slice(end) };
|
|
80
84
|
}
|
|
81
85
|
return null;
|
|
82
86
|
}
|