for-humanity 0.8.0 → 0.9.0
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 +9 -0
- package/README.md +3 -3
- package/dist/cli.css +34 -4
- 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 +35 -2
- 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 +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.9.0 — 2026-10-13
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
- 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.
|
|
9
|
+
|
|
10
|
+
## 0.8.1 — 2026-10-13
|
|
11
|
+
|
|
12
|
+
- Render tab-indented code blocks at four columns per tab instead of the browser default of eight.
|
|
13
|
+
|
|
5
14
|
## 0.8.0 — 2026-10-13
|
|
6
15
|
|
|
7
16
|
- Add a `cursorFace` site setting for the pixel face that follows the mouse on desktop. It is off by default; this documentation site turns it on.
|
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.0
|
|
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.0` 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.0` 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
|
@@ -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);
|
|
@@ -120,6 +120,7 @@
|
|
|
120
120
|
font-size: var(--app-font-size-detail);
|
|
121
121
|
line-height: var(--app-line-height-code);
|
|
122
122
|
letter-spacing: 0;
|
|
123
|
+
tab-size: 4;
|
|
123
124
|
}
|
|
124
125
|
& pre code {
|
|
125
126
|
padding: 0;
|
|
@@ -143,7 +144,8 @@
|
|
|
143
144
|
}
|
|
144
145
|
& th {
|
|
145
146
|
padding: var(--app-space-inline) var(--app-space-stack);
|
|
146
|
-
border-bottom: 1px
|
|
147
|
+
border-bottom: 1px solid var(--app-color-border);
|
|
148
|
+
background: var(--app-color-code);
|
|
147
149
|
color: var(--app-color-text-strong);
|
|
148
150
|
font-weight: var(--app-font-weight-strong);
|
|
149
151
|
text-align: left;
|
|
@@ -167,6 +169,29 @@
|
|
|
167
169
|
border: 0;
|
|
168
170
|
border-top: 1px dotted var(--app-color-border);
|
|
169
171
|
}
|
|
172
|
+
& .wg_prose__codeLabel {
|
|
173
|
+
margin: var(--app-space-block) 0;
|
|
174
|
+
border-left: 3px solid var(--app-color-bad);
|
|
175
|
+
}
|
|
176
|
+
& .wg_prose__codeLabel--good {
|
|
177
|
+
border-left-color: var(--app-color-good);
|
|
178
|
+
}
|
|
179
|
+
& .wg_prose__codeLabelTitle {
|
|
180
|
+
padding: var(--app-space-inline) var(--app-space-block);
|
|
181
|
+
background: var(--app-color-bad-soft);
|
|
182
|
+
color: var(--app-color-bad);
|
|
183
|
+
font-size: var(--app-font-size-dense);
|
|
184
|
+
font-weight: var(--app-font-weight-strong);
|
|
185
|
+
}
|
|
186
|
+
& .wg_prose__codeLabel--good .wg_prose__codeLabelTitle {
|
|
187
|
+
background: var(--app-color-good-soft);
|
|
188
|
+
color: var(--app-color-good);
|
|
189
|
+
}
|
|
190
|
+
& .wg_prose__codeLabel pre {
|
|
191
|
+
margin: 0;
|
|
192
|
+
border-left: 0;
|
|
193
|
+
border-radius: 0 var(--app-radius-box) var(--app-radius-box) 0;
|
|
194
|
+
}
|
|
170
195
|
}
|
|
171
196
|
.wg_prose__head {
|
|
172
197
|
margin-bottom: var(--app-space-inline);
|
|
@@ -736,6 +761,10 @@
|
|
|
736
761
|
--app-color-border-layout: light-dark(#eee, #2b2b2b);
|
|
737
762
|
--app-color-accent: light-dark(#555, #b5b5b5);
|
|
738
763
|
--app-color-accent-soft: light-dark(#f5f5f2, #222);
|
|
764
|
+
--app-color-bad: light-dark(#b2412e, #ff9b8a);
|
|
765
|
+
--app-color-bad-soft: light-dark(#fbeeec, #3a2522);
|
|
766
|
+
--app-color-good: light-dark(#2f7a47, #8bd3a3);
|
|
767
|
+
--app-color-good-soft: light-dark(#eaf5ee, #1f3327);
|
|
739
768
|
--app-color-link: light-dark(#11a, #9db8ff);
|
|
740
769
|
--app-color-link-visited: light-dark(#51a, #c5a5ed);
|
|
741
770
|
--app-color-link-hover: light-dark(#44d, #c1d1ff);
|
|
@@ -755,7 +784,7 @@
|
|
|
755
784
|
--app-font-size-body: 12px;
|
|
756
785
|
--app-font-size-lead: 12px;
|
|
757
786
|
--app-font-size-part: 13px;
|
|
758
|
-
--app-font-size-section:
|
|
787
|
+
--app-font-size-section: 15px;
|
|
759
788
|
--app-font-size-title: 16px;
|
|
760
789
|
--app-font-weight-mark: 400;
|
|
761
790
|
--app-font-weight-active: 600;
|
|
@@ -774,7 +803,8 @@
|
|
|
774
803
|
--app-space-stack: 10px;
|
|
775
804
|
--app-space-block: 14px;
|
|
776
805
|
--app-space-group: 20px;
|
|
777
|
-
--app-space-
|
|
806
|
+
--app-space-subsection: 28px;
|
|
807
|
+
--app-space-section: 44px;
|
|
778
808
|
--app-space-tree-row: 0px;
|
|
779
809
|
--app-space-tree-group: 16px;
|
|
780
810
|
--app-space-tree-label: 2px;
|
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
|
+
};
|
|
@@ -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);
|
|
@@ -153,6 +153,9 @@
|
|
|
153
153
|
font-size: var(--app-font-size-detail);
|
|
154
154
|
line-height: var(--app-line-height-code);
|
|
155
155
|
letter-spacing: 0;
|
|
156
|
+
|
|
157
|
+
/* 탭 들여쓰기 코드 · 브라우저 기본 8칸은 좁은 본문에서 너무 넓음 */
|
|
158
|
+
tab-size: 4;
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
& pre code {
|
|
@@ -182,7 +185,8 @@
|
|
|
182
185
|
|
|
183
186
|
& th {
|
|
184
187
|
padding: var(--app-space-inline) var(--app-space-stack);
|
|
185
|
-
border-bottom: 1px
|
|
188
|
+
border-bottom: 1px solid var(--app-color-border);
|
|
189
|
+
background: var(--app-color-code);
|
|
186
190
|
color: var(--app-color-text-strong);
|
|
187
191
|
font-weight: var(--app-font-weight-strong);
|
|
188
192
|
text-align: left;
|
|
@@ -211,6 +215,35 @@
|
|
|
211
215
|
border: 0;
|
|
212
216
|
border-top: 1px dotted var(--app-color-border);
|
|
213
217
|
}
|
|
218
|
+
|
|
219
|
+
/* 라벨 코드 · 왼쪽 띠와 제목 · 코드 상자의 여백은 figure 가 맡음 · 토큰은 token.css 의 bad·good */
|
|
220
|
+
& .wg_prose__codeLabel {
|
|
221
|
+
margin: var(--app-space-block) 0;
|
|
222
|
+
border-left: 3px solid var(--app-color-bad);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
& .wg_prose__codeLabel--good {
|
|
226
|
+
border-left-color: var(--app-color-good);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
& .wg_prose__codeLabelTitle {
|
|
230
|
+
padding: var(--app-space-inline) var(--app-space-block);
|
|
231
|
+
background: var(--app-color-bad-soft);
|
|
232
|
+
color: var(--app-color-bad);
|
|
233
|
+
font-size: var(--app-font-size-dense);
|
|
234
|
+
font-weight: var(--app-font-weight-strong);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
& .wg_prose__codeLabel--good .wg_prose__codeLabelTitle {
|
|
238
|
+
background: var(--app-color-good-soft);
|
|
239
|
+
color: var(--app-color-good);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
& .wg_prose__codeLabel pre {
|
|
243
|
+
margin: 0;
|
|
244
|
+
border-left: 0;
|
|
245
|
+
border-radius: 0 var(--app-radius-box) var(--app-radius-box) 0;
|
|
246
|
+
}
|
|
214
247
|
/* stylelint-enable selector-disallowed-list */
|
|
215
248
|
}
|
|
216
249
|
|
|
@@ -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: 44px;
|
|
94
101
|
|
|
95
102
|
/* 탐색 트리 · 항목 위아래 여백·묶음 사이·묶음 라벨 아래·중첩 들여쓰기 */
|
|
96
103
|
--app-space-tree-row: 0px;
|