for-humanity 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +3 -3
- package/dist/cli.css +47 -11
- package/dist/cli.js +40 -15
- package/package.json +1 -1
- package/src/component/widget/prose/_function/remark-code-labels.ts +47 -0
- package/src/component/widget/prose/wg-prose.css +41 -10
- package/src/entry/cli/_function/create-processor/create-processor.ts +3 -1
- package/src/entry/cli/cli.test.ts +24 -0
- package/src/style/token.css +18 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.9.1 — 2026-10-13
|
|
6
|
+
|
|
7
|
+
- Draw tables with an outer border and cell borders, box flowcharts with a border, and separate sections with a solid rule and 64px of space.
|
|
8
|
+
- Tint the whole code block of a `bad` or `good` label instead of relying on the caption, so a label can carry its reason as a code comment.
|
|
9
|
+
|
|
10
|
+
## 0.9.0 — 2026-10-13
|
|
11
|
+
|
|
12
|
+
- Add code labels: a fence meta of `bad` or `good` with an optional title renders the code block with a colored left stripe and a caption, keeping Shiki highlighting.
|
|
13
|
+
- Give table header rows a soft background and a solid rule, widen the space above sections to 44px and subsections to 28px, and set section headings to 15px so long reference pages read in blocks.
|
|
14
|
+
|
|
5
15
|
## 0.8.1 — 2026-10-13
|
|
6
16
|
|
|
7
17
|
- Render tab-indented code blocks at four columns per tab instead of the browser default of eight.
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Write Markdown. Keep the context. Share a static site.
|
|
|
33
33
|
Node.js 22 or later and pnpm. Install the CLI in your document project:
|
|
34
34
|
|
|
35
35
|
```sh
|
|
36
|
-
pnpm add -D --save-exact for-humanity@0.
|
|
36
|
+
pnpm add -D --save-exact for-humanity@0.9.1
|
|
37
37
|
mkdir docs
|
|
38
38
|
```
|
|
39
39
|
|
|
@@ -52,7 +52,7 @@ pnpm exec for-humanity preview docs
|
|
|
52
52
|
|
|
53
53
|
If your Markdown files are in the project root, use `.` instead of `docs`. The root `README.md` becomes the home page; root `AGENTS.md` and `CLAUDE.md` stay out of the site.
|
|
54
54
|
|
|
55
|
-
Version `0.
|
|
55
|
+
Version `0.9.1` supports `dev`, `build` and `preview`. `init` is planned; see the [Roadmap](https://github.com/minu-ha/for-humanity/blob/main/templates/document/roadmap.md) and [Changelog](https://github.com/minu-ha/for-humanity/blob/main/CHANGELOG.md).
|
|
56
56
|
Cloudflare Pages can host the generated site. See [Deployment](https://github.com/minu-ha/for-humanity/blob/main/templates/document/deployment.md) for the build settings.
|
|
57
57
|
Set an optional public root `url` in the site configuration to generate `robots.txt` and `sitemap.xml`; projects without a URL emit neither file. See [Crawling](https://github.com/minu-ha/for-humanity/blob/main/templates/document/crawling.md).
|
|
58
58
|
|
|
@@ -75,7 +75,7 @@ pnpm preview # Preview the static site
|
|
|
75
75
|
pnpm build:pages # Add Cloudflare Pages headers and a 404 page
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Changes pushed to `main` update the documentation site. A stable version tag such as `v0.
|
|
78
|
+
Changes pushed to `main` update the documentation site. A stable version tag such as `v0.9.1` runs checks, publishes the package to npm, then creates a GitHub Release with the same package file.
|
|
79
79
|
The npm README updates with each package release. See [Maintenance](https://github.com/minu-ha/for-humanity/blob/main/templates/document/maintenance.md#packaging-and-release) for the release steps.
|
|
80
80
|
|
|
81
81
|
## Make it yours
|
package/dist/cli.css
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
}
|
|
24
24
|
& h2 {
|
|
25
25
|
margin: var(--app-space-section) 0 var(--app-space-stack);
|
|
26
|
-
padding-top: var(--app-space-
|
|
27
|
-
border-top: 1px
|
|
26
|
+
padding-top: var(--app-space-group);
|
|
27
|
+
border-top: 1px solid var(--app-color-border);
|
|
28
28
|
color: var(--app-color-text-strong);
|
|
29
29
|
font-size: var(--app-font-size-section);
|
|
30
30
|
font-weight: var(--app-font-weight-strong);
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
border-top: 0;
|
|
45
45
|
}
|
|
46
46
|
& h3 {
|
|
47
|
-
margin: var(--app-space-
|
|
47
|
+
margin: var(--app-space-subsection) 0 var(--app-space-inline);
|
|
48
48
|
color: var(--app-color-text-strong);
|
|
49
49
|
font-size: var(--app-font-size-lead);
|
|
50
50
|
font-weight: var(--app-font-weight-strong);
|
|
@@ -138,13 +138,16 @@
|
|
|
138
138
|
}
|
|
139
139
|
& table {
|
|
140
140
|
width: 100%;
|
|
141
|
+
border: 1px solid var(--app-color-border-soft);
|
|
141
142
|
border-collapse: collapse;
|
|
142
143
|
font-size: var(--app-font-size-dense);
|
|
143
144
|
font-variant-numeric: tabular-nums;
|
|
144
145
|
}
|
|
145
146
|
& th {
|
|
146
147
|
padding: var(--app-space-inline) var(--app-space-stack);
|
|
147
|
-
border
|
|
148
|
+
border: 1px solid var(--app-color-border-soft);
|
|
149
|
+
border-bottom-color: var(--app-color-border);
|
|
150
|
+
background: var(--app-color-code);
|
|
148
151
|
color: var(--app-color-text-strong);
|
|
149
152
|
font-weight: var(--app-font-weight-strong);
|
|
150
153
|
text-align: left;
|
|
@@ -153,13 +156,10 @@
|
|
|
153
156
|
}
|
|
154
157
|
& td {
|
|
155
158
|
padding: var(--app-space-inline) var(--app-space-stack);
|
|
156
|
-
border
|
|
159
|
+
border: 1px solid var(--app-color-border-soft);
|
|
157
160
|
text-align: left;
|
|
158
161
|
vertical-align: top;
|
|
159
162
|
}
|
|
160
|
-
& tbody tr:last-child td {
|
|
161
|
-
border-bottom: 0;
|
|
162
|
-
}
|
|
163
163
|
& tbody tr:hover td {
|
|
164
164
|
background: var(--app-color-accent-soft);
|
|
165
165
|
}
|
|
@@ -168,6 +168,29 @@
|
|
|
168
168
|
border: 0;
|
|
169
169
|
border-top: 1px dotted var(--app-color-border);
|
|
170
170
|
}
|
|
171
|
+
& .wg_prose__codeLabel {
|
|
172
|
+
margin: var(--app-space-block) 0;
|
|
173
|
+
border-left: 3px solid var(--app-color-bad);
|
|
174
|
+
}
|
|
175
|
+
& .wg_prose__codeLabel--good {
|
|
176
|
+
border-left-color: var(--app-color-good);
|
|
177
|
+
}
|
|
178
|
+
& .wg_prose__codeLabelTitle {
|
|
179
|
+
padding: var(--app-space-inline) var(--app-space-block);
|
|
180
|
+
background: var(--app-color-bad-soft);
|
|
181
|
+
color: var(--app-color-bad);
|
|
182
|
+
font-size: var(--app-font-size-dense);
|
|
183
|
+
font-weight: var(--app-font-weight-strong);
|
|
184
|
+
}
|
|
185
|
+
& .wg_prose__codeLabel--good .wg_prose__codeLabelTitle {
|
|
186
|
+
background: var(--app-color-good-soft);
|
|
187
|
+
color: var(--app-color-good);
|
|
188
|
+
}
|
|
189
|
+
& .wg_prose__codeLabel pre {
|
|
190
|
+
margin: 0;
|
|
191
|
+
border-left: 0;
|
|
192
|
+
border-radius: 0 var(--app-radius-box) var(--app-radius-box) 0;
|
|
193
|
+
}
|
|
171
194
|
}
|
|
172
195
|
.wg_prose__head {
|
|
173
196
|
margin-bottom: var(--app-space-inline);
|
|
@@ -248,7 +271,9 @@
|
|
|
248
271
|
.wg_prose__flow {
|
|
249
272
|
overflow-x: auto;
|
|
250
273
|
margin: var(--app-space-block) 0;
|
|
251
|
-
padding: var(--app-space-block)
|
|
274
|
+
padding: var(--app-space-block);
|
|
275
|
+
border: 1px solid var(--app-color-border-soft);
|
|
276
|
+
border-radius: var(--app-radius-box);
|
|
252
277
|
letter-spacing: 0;
|
|
253
278
|
& svg {
|
|
254
279
|
display: block;
|
|
@@ -737,6 +762,10 @@
|
|
|
737
762
|
--app-color-border-layout: light-dark(#eee, #2b2b2b);
|
|
738
763
|
--app-color-accent: light-dark(#555, #b5b5b5);
|
|
739
764
|
--app-color-accent-soft: light-dark(#f5f5f2, #222);
|
|
765
|
+
--app-color-bad: light-dark(#b2412e, #ff9b8a);
|
|
766
|
+
--app-color-bad-soft: light-dark(#fbeeec, #3a2522);
|
|
767
|
+
--app-color-good: light-dark(#2f7a47, #8bd3a3);
|
|
768
|
+
--app-color-good-soft: light-dark(#eaf5ee, #1f3327);
|
|
740
769
|
--app-color-link: light-dark(#11a, #9db8ff);
|
|
741
770
|
--app-color-link-visited: light-dark(#51a, #c5a5ed);
|
|
742
771
|
--app-color-link-hover: light-dark(#44d, #c1d1ff);
|
|
@@ -756,7 +785,7 @@
|
|
|
756
785
|
--app-font-size-body: 12px;
|
|
757
786
|
--app-font-size-lead: 12px;
|
|
758
787
|
--app-font-size-part: 13px;
|
|
759
|
-
--app-font-size-section:
|
|
788
|
+
--app-font-size-section: 15px;
|
|
760
789
|
--app-font-size-title: 16px;
|
|
761
790
|
--app-font-weight-mark: 400;
|
|
762
791
|
--app-font-weight-active: 600;
|
|
@@ -775,7 +804,8 @@
|
|
|
775
804
|
--app-space-stack: 10px;
|
|
776
805
|
--app-space-block: 14px;
|
|
777
806
|
--app-space-group: 20px;
|
|
778
|
-
--app-space-
|
|
807
|
+
--app-space-subsection: 28px;
|
|
808
|
+
--app-space-section: 64px;
|
|
779
809
|
--app-space-tree-row: 0px;
|
|
780
810
|
--app-space-tree-group: 16px;
|
|
781
811
|
--app-space-tree-label: 2px;
|
|
@@ -827,6 +857,12 @@
|
|
|
827
857
|
--app-space-gutter: 16px;
|
|
828
858
|
}
|
|
829
859
|
}
|
|
860
|
+
.wg_prose__codeLabel--bad {
|
|
861
|
+
--app-code-background: var(--app-color-bad-soft);
|
|
862
|
+
}
|
|
863
|
+
.wg_prose__codeLabel--good {
|
|
864
|
+
--app-code-background: var(--app-color-good-soft);
|
|
865
|
+
}
|
|
830
866
|
|
|
831
867
|
/* src/component/widget/shell/wg-shell.css */
|
|
832
868
|
.wg_shell__root {
|
package/dist/cli.js
CHANGED
|
@@ -1231,8 +1231,33 @@ var remarkBlocks = () => (tree, file) => {
|
|
|
1231
1231
|
});
|
|
1232
1232
|
};
|
|
1233
1233
|
|
|
1234
|
-
// src/component/widget/prose/_function/remark-
|
|
1234
|
+
// src/component/widget/prose/_function/remark-code-labels.ts
|
|
1235
1235
|
import { visit as visit3 } from "unist-util-visit";
|
|
1236
|
+
var code_label_pattern = /^(bad|good)(?:\s+(.+?))?\s*$/;
|
|
1237
|
+
var inserted_node_count = 3;
|
|
1238
|
+
var escapeHtml = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1239
|
+
var remarkCodeLabels = () => (tree) => {
|
|
1240
|
+
visit3(tree, "code", (node, index, parent) => {
|
|
1241
|
+
if (typeof node.meta !== "string" || parent === void 0 || index === void 0) {
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
const match = code_label_pattern.exec(node.meta);
|
|
1245
|
+
if (match === null) {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const [, tone, title] = match;
|
|
1249
|
+
const caption = title === void 0 ? "" : `<figcaption class="wg_prose__codeLabelTitle">${escapeHtml(title)}</figcaption>`;
|
|
1250
|
+
node.meta = null;
|
|
1251
|
+
parent.children.splice(index, 1, { type: "html", value: `<figure class="wg_prose__codeLabel wg_prose__codeLabel--${tone}">${caption}` }, node, {
|
|
1252
|
+
type: "html",
|
|
1253
|
+
value: "</figure>"
|
|
1254
|
+
});
|
|
1255
|
+
return index + inserted_node_count;
|
|
1256
|
+
});
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
// src/component/widget/prose/_function/remark-flow.ts
|
|
1260
|
+
import { visit as visit4 } from "unist-util-visit";
|
|
1236
1261
|
|
|
1237
1262
|
// src/util/mermaid/render-flow.js
|
|
1238
1263
|
import { renderMermaidASCII } from "beautiful-mermaid";
|
|
@@ -1546,7 +1571,7 @@ var renderFlow = (source) => gridToSvg(renderMermaidASCII(widenCjk(source), ASCI
|
|
|
1546
1571
|
|
|
1547
1572
|
// src/component/widget/prose/_function/remark-flow.ts
|
|
1548
1573
|
var remarkFlow = () => (tree, file) => {
|
|
1549
|
-
|
|
1574
|
+
visit4(tree, "code", (node, index, parent) => {
|
|
1550
1575
|
if (node.lang !== "mermaid" || parent === void 0 || index === void 0) {
|
|
1551
1576
|
return;
|
|
1552
1577
|
}
|
|
@@ -1564,10 +1589,10 @@ var remarkFlow = () => (tree, file) => {
|
|
|
1564
1589
|
// src/component/widget/prose/_function/remark-links.ts
|
|
1565
1590
|
import { existsSync } from "node:fs";
|
|
1566
1591
|
import { dirname, posix, relative, resolve } from "node:path";
|
|
1567
|
-
import { visit as
|
|
1592
|
+
import { visit as visit5 } from "unist-util-visit";
|
|
1568
1593
|
var remarkLinks = (options) => (tree, file) => {
|
|
1569
1594
|
const here = dirname(relative(options.root, file.path));
|
|
1570
|
-
|
|
1595
|
+
visit5(tree, (node) => {
|
|
1571
1596
|
if (node.type !== "link" && node.type !== "definition") {
|
|
1572
1597
|
return;
|
|
1573
1598
|
}
|
|
@@ -1601,7 +1626,7 @@ var remarkParts = () => (tree) => {
|
|
|
1601
1626
|
};
|
|
1602
1627
|
|
|
1603
1628
|
// src/component/widget/prose/_function/remark-status.ts
|
|
1604
|
-
import { escape as
|
|
1629
|
+
import { escape as escapeHtml2, escapeRegExp } from "es-toolkit/string";
|
|
1605
1630
|
import { findAndReplace } from "mdast-util-find-and-replace";
|
|
1606
1631
|
|
|
1607
1632
|
// src/component/widget/prose/_constant/status.ts
|
|
@@ -1627,7 +1652,7 @@ var remarkStatus = (options) => {
|
|
|
1627
1652
|
}
|
|
1628
1653
|
return {
|
|
1629
1654
|
type: "html",
|
|
1630
|
-
value: `<span class="wg_prose__pill wg_prose__pill--${item.kind}">${
|
|
1655
|
+
value: `<span class="wg_prose__pill wg_prose__pill--${item.kind}">${escapeHtml2(text)}</span>`
|
|
1631
1656
|
};
|
|
1632
1657
|
}
|
|
1633
1658
|
],
|
|
@@ -1651,9 +1676,9 @@ var remarkStatus = (options) => {
|
|
|
1651
1676
|
};
|
|
1652
1677
|
|
|
1653
1678
|
// src/component/widget/prose/_function/remark-swatch.ts
|
|
1654
|
-
import { visit as
|
|
1679
|
+
import { visit as visit6 } from "unist-util-visit";
|
|
1655
1680
|
var remarkSwatch = () => (tree) => {
|
|
1656
|
-
|
|
1681
|
+
visit6(tree, "inlineCode", (node) => {
|
|
1657
1682
|
if (/^#[0-9a-f]{3,8}$/i.test(node.value.trim())) {
|
|
1658
1683
|
node.data = {
|
|
1659
1684
|
hProperties: { className: ["wg_prose__color"], style: `--wg-prose-chip-color: ${node.value.trim()}` }
|
|
@@ -1663,9 +1688,9 @@ var remarkSwatch = () => (tree) => {
|
|
|
1663
1688
|
};
|
|
1664
1689
|
|
|
1665
1690
|
// src/component/widget/prose/_function/remark-unknown-directives.ts
|
|
1666
|
-
import { SKIP as SKIP2, visit as
|
|
1691
|
+
import { SKIP as SKIP2, visit as visit7 } from "unist-util-visit";
|
|
1667
1692
|
var remarkUnknownDirectives = () => (tree, file) => {
|
|
1668
|
-
|
|
1693
|
+
visit7(tree, (node, index, parent) => {
|
|
1669
1694
|
if (node.type === "textDirective" && parent !== void 0 && index !== void 0) {
|
|
1670
1695
|
parent.children[index] = {
|
|
1671
1696
|
type: "text",
|
|
@@ -1682,7 +1707,7 @@ var remarkUnknownDirectives = () => (tree, file) => {
|
|
|
1682
1707
|
// src/entry/cli/_function/create-processor/_rehype-assets.ts
|
|
1683
1708
|
import { existsSync as existsSync2, realpathSync, statSync } from "node:fs";
|
|
1684
1709
|
import { dirname as dirname2, extname as extname2, isAbsolute, relative as relative2, resolve as resolve2, sep } from "node:path";
|
|
1685
|
-
import { visit as
|
|
1710
|
+
import { visit as visit8 } from "unist-util-visit";
|
|
1686
1711
|
|
|
1687
1712
|
// src/util/file/to-asset-path.ts
|
|
1688
1713
|
import { createHash } from "node:crypto";
|
|
@@ -1695,7 +1720,7 @@ var toAssetPath = (options) => {
|
|
|
1695
1720
|
|
|
1696
1721
|
// src/entry/cli/_function/create-processor/_rehype-assets.ts
|
|
1697
1722
|
var rehypeAssets = (options) => (tree, file) => {
|
|
1698
|
-
|
|
1723
|
+
visit8(tree, "element", (node) => {
|
|
1699
1724
|
const property = node.tagName === "img" ? "src" : "href";
|
|
1700
1725
|
const url = node.properties[property];
|
|
1701
1726
|
if (node.tagName !== "img" && node.tagName !== "a" || typeof url !== "string" || /^(?:[a-z][a-z\d+.-]*:|\/|#)/i.test(url)) {
|
|
@@ -1735,11 +1760,11 @@ var rehypeAssets = (options) => (tree, file) => {
|
|
|
1735
1760
|
// src/entry/cli/_function/create-processor/_rehype-heading-ids.ts
|
|
1736
1761
|
import Slugger from "github-slugger";
|
|
1737
1762
|
import { toString as toPlainText3 } from "hast-util-to-string";
|
|
1738
|
-
import { visit as
|
|
1763
|
+
import { visit as visit9 } from "unist-util-visit";
|
|
1739
1764
|
var rehypeHeadingIds = () => (tree, file) => {
|
|
1740
1765
|
const slugger = new Slugger();
|
|
1741
1766
|
const headings = [];
|
|
1742
|
-
|
|
1767
|
+
visit9(tree, "element", (node) => {
|
|
1743
1768
|
const depth = /^h([1-6])$/.exec(node.tagName)?.[1];
|
|
1744
1769
|
if (depth === void 0) {
|
|
1745
1770
|
return;
|
|
@@ -1765,7 +1790,7 @@ var remarkReport = (options) => (_tree, file) => {
|
|
|
1765
1790
|
|
|
1766
1791
|
// src/entry/cli/_function/create-processor/create-processor.ts
|
|
1767
1792
|
var createProcessor = (options) => {
|
|
1768
|
-
return unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkSmartypants).use(remarkDirective).use(remarkBlocks).use(remarkParts).use(remarkFlow).use(remarkStatus, { status: options.site.status }).use(remarkSwatch).use(remarkLinks, { root: options.root }).use(remarkUnknownDirectives).use(remarkReport, { root: options.root }).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeShiki, { theme: createCssVariablesTheme({ name: "for-humanity", variablePrefix: "--app-code-" }) }).use(rehypeHead).use(rehypeHeadingIds).use(rehypeSections).use(rehypeTables).use(rehypeRaw).use(rehypeAssets, { root: options.root, files: options.files ?? /* @__PURE__ */ new Map() }).use(rehypeStringify, { allowDangerousHtml: true });
|
|
1793
|
+
return unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkSmartypants).use(remarkDirective).use(remarkBlocks).use(remarkParts).use(remarkFlow).use(remarkCodeLabels).use(remarkStatus, { status: options.site.status }).use(remarkSwatch).use(remarkLinks, { root: options.root }).use(remarkUnknownDirectives).use(remarkReport, { root: options.root }).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeShiki, { theme: createCssVariablesTheme({ name: "for-humanity", variablePrefix: "--app-code-" }) }).use(rehypeHead).use(rehypeHeadingIds).use(rehypeSections).use(rehypeTables).use(rehypeRaw).use(rehypeAssets, { root: options.root, files: options.files ?? /* @__PURE__ */ new Map() }).use(rehypeStringify, { allowDangerousHtml: true });
|
|
1769
1794
|
};
|
|
1770
1795
|
|
|
1771
1796
|
// src/entry/cli/_function/read-docs.ts
|
package/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {Root} from "mdast";
|
|
2
|
+
import {visit} from "unist-util-visit";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 펜스 메타의 라벨 · `bad 제목`, `good 제목` · 제목은 선택
|
|
6
|
+
*/
|
|
7
|
+
const code_label_pattern = /^(bad|good)(?:\s+(.+?))?\s*$/;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* figure 여는 조각, 코드, 닫는 조각
|
|
11
|
+
*/
|
|
12
|
+
const inserted_node_count = 3;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 제목을 HTML 문자열에 넣기 위한 escape
|
|
16
|
+
*/
|
|
17
|
+
const escapeHtml = (text: string) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 코드 펜스 메타 `bad`·`good` → 색 띠와 제목이 붙은 figure · 다른 메타는 그대로 둠
|
|
21
|
+
* 코드 노드는 유지해 Shiki 강조를 받고, figure 조각은 마지막 rehype-raw에서 합쳐짐
|
|
22
|
+
*/
|
|
23
|
+
export const remarkCodeLabels = () => (tree: Root) => {
|
|
24
|
+
visit(tree, "code", (node, index, parent) => {
|
|
25
|
+
if (typeof node.meta !== "string" || parent === undefined || index === undefined) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const match = code_label_pattern.exec(node.meta);
|
|
30
|
+
|
|
31
|
+
if (match === null) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const [, tone, title] = match;
|
|
36
|
+
const caption = title === undefined ? "" : `<figcaption class="wg_prose__codeLabelTitle">${escapeHtml(title)}</figcaption>`;
|
|
37
|
+
|
|
38
|
+
node.meta = null;
|
|
39
|
+
parent.children.splice(index, 1, {type: "html", value: `<figure class="wg_prose__codeLabel wg_prose__codeLabel--${tone}">${caption}`}, node, {
|
|
40
|
+
type: "html",
|
|
41
|
+
value: "</figure>",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 끼워 넣은 세 노드 다음부터 계속
|
|
45
|
+
return index + inserted_node_count;
|
|
46
|
+
});
|
|
47
|
+
};
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
|
|
35
35
|
& h2 {
|
|
36
36
|
margin: var(--app-space-section) 0 var(--app-space-stack);
|
|
37
|
-
padding-top: var(--app-space-
|
|
38
|
-
border-top: 1px
|
|
37
|
+
padding-top: var(--app-space-group);
|
|
38
|
+
border-top: 1px solid var(--app-color-border);
|
|
39
39
|
color: var(--app-color-text-strong);
|
|
40
40
|
font-size: var(--app-font-size-section);
|
|
41
41
|
font-weight: var(--app-font-weight-strong);
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
& h3 {
|
|
63
|
-
margin: var(--app-space-
|
|
63
|
+
margin: var(--app-space-subsection) 0 var(--app-space-inline);
|
|
64
64
|
color: var(--app-color-text-strong);
|
|
65
65
|
font-size: var(--app-font-size-lead);
|
|
66
66
|
font-weight: var(--app-font-weight-strong);
|
|
@@ -176,8 +176,10 @@
|
|
|
176
176
|
margin-bottom: 0;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
/* 표 · 코드 상자처럼 바깥 선과 칸 선 · 머리 행은 바탕과 진한 밑선 */
|
|
179
180
|
& table {
|
|
180
181
|
width: 100%;
|
|
182
|
+
border: 1px solid var(--app-color-border-soft);
|
|
181
183
|
border-collapse: collapse;
|
|
182
184
|
font-size: var(--app-font-size-dense);
|
|
183
185
|
font-variant-numeric: tabular-nums;
|
|
@@ -185,7 +187,9 @@
|
|
|
185
187
|
|
|
186
188
|
& th {
|
|
187
189
|
padding: var(--app-space-inline) var(--app-space-stack);
|
|
188
|
-
border
|
|
190
|
+
border: 1px solid var(--app-color-border-soft);
|
|
191
|
+
border-bottom-color: var(--app-color-border);
|
|
192
|
+
background: var(--app-color-code);
|
|
189
193
|
color: var(--app-color-text-strong);
|
|
190
194
|
font-weight: var(--app-font-weight-strong);
|
|
191
195
|
text-align: left;
|
|
@@ -195,15 +199,11 @@
|
|
|
195
199
|
|
|
196
200
|
& td {
|
|
197
201
|
padding: var(--app-space-inline) var(--app-space-stack);
|
|
198
|
-
border
|
|
202
|
+
border: 1px solid var(--app-color-border-soft);
|
|
199
203
|
text-align: left;
|
|
200
204
|
vertical-align: top;
|
|
201
205
|
}
|
|
202
206
|
|
|
203
|
-
& tbody tr:last-child td {
|
|
204
|
-
border-bottom: 0;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
207
|
/* 표의 읽는 행 표시 */
|
|
208
208
|
& tbody tr:hover td {
|
|
209
209
|
background: var(--app-color-accent-soft);
|
|
@@ -214,6 +214,35 @@
|
|
|
214
214
|
border: 0;
|
|
215
215
|
border-top: 1px dotted var(--app-color-border);
|
|
216
216
|
}
|
|
217
|
+
|
|
218
|
+
/* 라벨 코드 · 왼쪽 띠와 제목 · 코드 상자의 여백은 figure 가 맡음 · 토큰은 token.css 의 bad·good */
|
|
219
|
+
& .wg_prose__codeLabel {
|
|
220
|
+
margin: var(--app-space-block) 0;
|
|
221
|
+
border-left: 3px solid var(--app-color-bad);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
& .wg_prose__codeLabel--good {
|
|
225
|
+
border-left-color: var(--app-color-good);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
& .wg_prose__codeLabelTitle {
|
|
229
|
+
padding: var(--app-space-inline) var(--app-space-block);
|
|
230
|
+
background: var(--app-color-bad-soft);
|
|
231
|
+
color: var(--app-color-bad);
|
|
232
|
+
font-size: var(--app-font-size-dense);
|
|
233
|
+
font-weight: var(--app-font-weight-strong);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
& .wg_prose__codeLabel--good .wg_prose__codeLabelTitle {
|
|
237
|
+
background: var(--app-color-good-soft);
|
|
238
|
+
color: var(--app-color-good);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
& .wg_prose__codeLabel pre {
|
|
242
|
+
margin: 0;
|
|
243
|
+
border-left: 0;
|
|
244
|
+
border-radius: 0 var(--app-radius-box) var(--app-radius-box) 0;
|
|
245
|
+
}
|
|
217
246
|
/* stylelint-enable selector-disallowed-list */
|
|
218
247
|
}
|
|
219
248
|
|
|
@@ -324,7 +353,9 @@
|
|
|
324
353
|
.wg_prose__flow {
|
|
325
354
|
overflow-x: auto;
|
|
326
355
|
margin: var(--app-space-block) 0;
|
|
327
|
-
padding: var(--app-space-block)
|
|
356
|
+
padding: var(--app-space-block);
|
|
357
|
+
border: 1px solid var(--app-color-border-soft);
|
|
358
|
+
border-radius: var(--app-radius-box);
|
|
328
359
|
|
|
329
360
|
/* 라벨 위치는 자간 0의 격자로 계산 */
|
|
330
361
|
letter-spacing: 0;
|
|
@@ -13,6 +13,7 @@ import {rehypeHead} from "@/component/widget/prose/_function/rehype-head";
|
|
|
13
13
|
import {rehypeSections} from "@/component/widget/prose/_function/rehype-sections/rehype-sections";
|
|
14
14
|
import {rehypeTables} from "@/component/widget/prose/_function/rehype-tables";
|
|
15
15
|
import {remarkBlocks} from "@/component/widget/prose/_function/remark-blocks";
|
|
16
|
+
import {remarkCodeLabels} from "@/component/widget/prose/_function/remark-code-labels";
|
|
16
17
|
import {remarkFlow} from "@/component/widget/prose/_function/remark-flow";
|
|
17
18
|
import {remarkLinks} from "@/component/widget/prose/_function/remark-links";
|
|
18
19
|
import {remarkParts} from "@/component/widget/prose/_function/remark-parts";
|
|
@@ -25,7 +26,7 @@ import {remarkReport} from "@/entry/cli/_function/create-processor/_remark-repor
|
|
|
25
26
|
import type {SiteConfig} from "@/type/site-config";
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
|
-
* Markdown 처리 순서: remark → 코드 강조 → 머리 → 제목 id → 가름 수집 → 표
|
|
29
|
+
* Markdown 처리 순서: remark(부품·흐름도·코드 라벨) → 코드 강조 → 머리 → 제목 id → 가름 수집 → 표
|
|
29
30
|
* 플러그인 HTML 조각은 마지막 rehype-raw에서 해석
|
|
30
31
|
* Shiki inline style: token.css의 --app-code-* 참조
|
|
31
32
|
*/
|
|
@@ -39,6 +40,7 @@ export const createProcessor = (options: {site: SiteConfig; root: string; files?
|
|
|
39
40
|
.use(remarkBlocks)
|
|
40
41
|
.use(remarkParts)
|
|
41
42
|
.use(remarkFlow)
|
|
43
|
+
.use(remarkCodeLabels)
|
|
42
44
|
.use(remarkStatus, {status: options.site.status})
|
|
43
45
|
.use(remarkSwatch)
|
|
44
46
|
.use(remarkLinks, {root: options.root})
|
|
@@ -73,6 +73,30 @@ test("site port defaults to 4321 and accepts only integers in the TCP range", ()
|
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
+
test("code fences labelled bad or good render a captioned figure and keep highlighting", async () => {
|
|
77
|
+
const root = await mkdtemp(join(tmpdir(), "for-humanity-code-labels-"));
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
await writeFile(
|
|
81
|
+
join(root, "guide.md"),
|
|
82
|
+
"---\nname: Guide\nlabel: Guide\ngroup: Guide\n---\n\n## Read\n\n```ts bad 리터럴 <폴백>\nconst a = 1;\n```\n\n```ts good\nconst b = 2;\n```\n\n```ts other meta\nconst c = 3;\n```\n",
|
|
83
|
+
);
|
|
84
|
+
const site = siteConfigSchema.parse({});
|
|
85
|
+
const [doc] = await readDocs({root, processor: createProcessor({site, root})});
|
|
86
|
+
|
|
87
|
+
assert.ok(doc);
|
|
88
|
+
assert.match(
|
|
89
|
+
doc.html,
|
|
90
|
+
/<figure class="wg_prose__codeLabel wg_prose__codeLabel--bad"><figcaption class="wg_prose__codeLabelTitle">리터럴 <폴백><\/figcaption>\s*<pre class="shiki/,
|
|
91
|
+
);
|
|
92
|
+
assert.match(doc.html, /<figure class="wg_prose__codeLabel wg_prose__codeLabel--good">\s*<pre class="shiki/);
|
|
93
|
+
assert.equal(doc.html.split("<figure").length - 1, 2, "other meta stays a plain code block");
|
|
94
|
+
assert.equal(doc.html.split("</figure>").length - 1, 2);
|
|
95
|
+
} finally {
|
|
96
|
+
await rm(root, {recursive: true, force: true});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
76
100
|
test("cursor face is off unless the site turns it on", () => {
|
|
77
101
|
assert.equal(siteConfigSchema.parse({}).cursorFace, false);
|
|
78
102
|
assert.equal(siteConfigSchema.parse({cursorFace: true}).cursorFace, true);
|
package/src/style/token.css
CHANGED
|
@@ -35,6 +35,12 @@
|
|
|
35
35
|
--app-color-accent: light-dark(#555, #b5b5b5);
|
|
36
36
|
--app-color-accent-soft: light-dark(#f5f5f2, #222);
|
|
37
37
|
|
|
38
|
+
/* 라벨 코드 · bad·good 띠와 제목 · soft 제목 바탕 */
|
|
39
|
+
--app-color-bad: light-dark(#b2412e, #ff9b8a);
|
|
40
|
+
--app-color-bad-soft: light-dark(#fbeeec, #3a2522);
|
|
41
|
+
--app-color-good: light-dark(#2f7a47, #8bd3a3);
|
|
42
|
+
--app-color-good-soft: light-dark(#eaf5ee, #1f3327);
|
|
43
|
+
|
|
38
44
|
/* 링크 · 기본·방문·hover */
|
|
39
45
|
--app-color-link: light-dark(#11a, #9db8ff);
|
|
40
46
|
--app-color-link-visited: light-dark(#51a, #c5a5ed);
|
|
@@ -63,7 +69,7 @@
|
|
|
63
69
|
--app-font-size-body: 12px;
|
|
64
70
|
--app-font-size-lead: 12px;
|
|
65
71
|
--app-font-size-part: 13px;
|
|
66
|
-
--app-font-size-section:
|
|
72
|
+
--app-font-size-section: 15px;
|
|
67
73
|
--app-font-size-title: 16px;
|
|
68
74
|
|
|
69
75
|
/* 굵기 · 기본 400 · 현재 위치·제목·강조 600 */
|
|
@@ -90,7 +96,8 @@
|
|
|
90
96
|
--app-space-stack: 10px;
|
|
91
97
|
--app-space-block: 14px;
|
|
92
98
|
--app-space-group: 20px;
|
|
93
|
-
--app-space-
|
|
99
|
+
--app-space-subsection: 28px;
|
|
100
|
+
--app-space-section: 64px;
|
|
94
101
|
|
|
95
102
|
/* 탐색 트리 · 항목 위아래 여백·묶음 사이·묶음 라벨 아래·중첩 들여쓰기 */
|
|
96
103
|
--app-space-tree-row: 0px;
|
|
@@ -164,3 +171,12 @@
|
|
|
164
171
|
--app-space-gutter: 16px;
|
|
165
172
|
}
|
|
166
173
|
}
|
|
174
|
+
|
|
175
|
+
/* 라벨 코드 · Shiki 가 pre 에 인라인으로 쓰는 코드 바탕을 figure 색으로 바꿈 */
|
|
176
|
+
.wg_prose__codeLabel--bad {
|
|
177
|
+
--app-code-background: var(--app-color-bad-soft);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.wg_prose__codeLabel--good {
|
|
181
|
+
--app-code-background: var(--app-color-good-soft);
|
|
182
|
+
}
|