for-humanity 0.11.0 → 0.12.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 +4 -0
- package/README.md +3 -3
- package/dist/cli.css +29 -2
- package/dist/cli.js +29 -1
- package/package.json +1 -1
- package/src/component/widget/prose/wg-prose.css +30 -0
- package/src/entry/cli/_function/create-processor/_shiki-transformers.ts +37 -1
- package/src/entry/cli/cli.test.ts +22 -0
- package/src/style/token.css +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.12.0 — 2026-10-13
|
|
6
|
+
|
|
7
|
+
- Add a `[file name]` fence meta that draws a title bar with the file name and language above the code block, sharing the `bad`/`good` tone.
|
|
8
|
+
|
|
5
9
|
## 0.11.0 — 2026-10-13
|
|
6
10
|
|
|
7
11
|
- Add `<<< ./file.ext#region meta` code snippet imports: a file inside the document root becomes a code block, with optional `#region` extraction, dedent, fence meta passthrough and dev reloads when the file changes.
|
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.12.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.12.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.12.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
|
@@ -203,6 +203,31 @@
|
|
|
203
203
|
border-radius: var(--app-radius-inline);
|
|
204
204
|
background: var(--app-color-accent-soft);
|
|
205
205
|
}
|
|
206
|
+
& .wg_prose__codeFile {
|
|
207
|
+
margin: var(--app-space-block) 0;
|
|
208
|
+
}
|
|
209
|
+
& .wg_prose__codeFile pre {
|
|
210
|
+
margin: 0;
|
|
211
|
+
border-top-left-radius: 0;
|
|
212
|
+
border-top-right-radius: 0;
|
|
213
|
+
}
|
|
214
|
+
& .wg_prose__codeFileTitle {
|
|
215
|
+
display: flex;
|
|
216
|
+
gap: var(--app-space-block);
|
|
217
|
+
justify-content: space-between;
|
|
218
|
+
padding: var(--app-space-inline) var(--app-space-block);
|
|
219
|
+
border: 1px solid var(--app-color-border);
|
|
220
|
+
border-bottom: 0;
|
|
221
|
+
border-radius: var(--app-radius-box) var(--app-radius-box) 0 0;
|
|
222
|
+
background: var(--app-code-background);
|
|
223
|
+
color: var(--app-color-text-muted);
|
|
224
|
+
font-family: var(--app-font-mono);
|
|
225
|
+
font-size: var(--app-font-size-dense);
|
|
226
|
+
letter-spacing: 0;
|
|
227
|
+
}
|
|
228
|
+
& .wg_prose__codeFileLang {
|
|
229
|
+
text-transform: uppercase;
|
|
230
|
+
}
|
|
206
231
|
& pre.wg_prose__lineNumbers code {
|
|
207
232
|
counter-reset: line;
|
|
208
233
|
}
|
|
@@ -886,10 +911,12 @@
|
|
|
886
911
|
--app-space-gutter: 16px;
|
|
887
912
|
}
|
|
888
913
|
}
|
|
889
|
-
.wg_prose__code--bad
|
|
914
|
+
.wg_prose__code--bad,
|
|
915
|
+
.wg_prose__codeFile--bad {
|
|
890
916
|
--app-code-background: var(--app-color-bad-soft);
|
|
891
917
|
}
|
|
892
|
-
.wg_prose__code--good
|
|
918
|
+
.wg_prose__code--good,
|
|
919
|
+
.wg_prose__codeFile--good {
|
|
893
920
|
--app-code-background: var(--app-color-good-soft);
|
|
894
921
|
}
|
|
895
922
|
|
package/dist/cli.js
CHANGED
|
@@ -1880,6 +1880,33 @@ var tone = {
|
|
|
1880
1880
|
}
|
|
1881
1881
|
}
|
|
1882
1882
|
};
|
|
1883
|
+
var fileTitle = {
|
|
1884
|
+
name: "for-humanity:code-title",
|
|
1885
|
+
root(root) {
|
|
1886
|
+
const meta = this.options.meta?.__raw;
|
|
1887
|
+
const title = typeof meta === "string" ? /\[([^\]]+)\]/.exec(meta)?.[1] : void 0;
|
|
1888
|
+
if (title === void 0) {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
const tones = typeof meta === "string" ? ["bad", "good"].filter((word) => new RegExp(`\\b${word}\\b`).test(meta)) : [];
|
|
1892
|
+
const caption = {
|
|
1893
|
+
type: "element",
|
|
1894
|
+
tagName: "figcaption",
|
|
1895
|
+
properties: { className: ["wg_prose__codeFileTitle"] },
|
|
1896
|
+
children: [
|
|
1897
|
+
{ type: "text", value: title },
|
|
1898
|
+
{ type: "element", tagName: "span", properties: { className: ["wg_prose__codeFileLang"] }, children: [{ type: "text", value: this.options.lang }] }
|
|
1899
|
+
]
|
|
1900
|
+
};
|
|
1901
|
+
const figure = {
|
|
1902
|
+
type: "element",
|
|
1903
|
+
tagName: "figure",
|
|
1904
|
+
properties: { className: ["wg_prose__codeFile", ...tones.map((tone2) => `wg_prose__codeFile--${tone2}`)] },
|
|
1905
|
+
children: [caption, ...root.children.filter((child) => child.type !== "doctype")]
|
|
1906
|
+
};
|
|
1907
|
+
root.children = [figure];
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1883
1910
|
var shikiTransformers = [
|
|
1884
1911
|
transformerNotationHighlight({ matchAlgorithm: "v3" }),
|
|
1885
1912
|
transformerNotationErrorLevel({ matchAlgorithm: "v3" }),
|
|
@@ -1887,7 +1914,8 @@ var shikiTransformers = [
|
|
|
1887
1914
|
transformerNotationWordHighlight({ matchAlgorithm: "v3" }),
|
|
1888
1915
|
transformerMetaHighlight(),
|
|
1889
1916
|
lineNumbers,
|
|
1890
|
-
tone
|
|
1917
|
+
tone,
|
|
1918
|
+
fileTitle
|
|
1891
1919
|
];
|
|
1892
1920
|
|
|
1893
1921
|
// src/entry/cli/_function/create-processor/create-processor.ts
|
package/package.json
CHANGED
|
@@ -263,6 +263,36 @@
|
|
|
263
263
|
background: var(--app-color-accent-soft);
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
/* 파일 이름 띠 · 펜스 메타 [파일 이름] · 코드 상자와 한 몸, 여백은 figure 가 맡음 */
|
|
267
|
+
& .wg_prose__codeFile {
|
|
268
|
+
margin: var(--app-space-block) 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
& .wg_prose__codeFile pre {
|
|
272
|
+
margin: 0;
|
|
273
|
+
border-top-left-radius: 0;
|
|
274
|
+
border-top-right-radius: 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
& .wg_prose__codeFileTitle {
|
|
278
|
+
display: flex;
|
|
279
|
+
gap: var(--app-space-block);
|
|
280
|
+
justify-content: space-between;
|
|
281
|
+
padding: var(--app-space-inline) var(--app-space-block);
|
|
282
|
+
border: 1px solid var(--app-color-border);
|
|
283
|
+
border-bottom: 0;
|
|
284
|
+
border-radius: var(--app-radius-box) var(--app-radius-box) 0 0;
|
|
285
|
+
background: var(--app-code-background);
|
|
286
|
+
color: var(--app-color-text-muted);
|
|
287
|
+
font-family: var(--app-font-mono);
|
|
288
|
+
font-size: var(--app-font-size-dense);
|
|
289
|
+
letter-spacing: 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
& .wg_prose__codeFileLang {
|
|
293
|
+
text-transform: uppercase;
|
|
294
|
+
}
|
|
295
|
+
|
|
266
296
|
/* 줄 번호 · 펜스 메타 line-numbers · 복사에 섞이지 않게 content 로 */
|
|
267
297
|
& pre.wg_prose__lineNumbers code {
|
|
268
298
|
counter-reset: line;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
transformerNotationHighlight,
|
|
6
6
|
transformerNotationWordHighlight,
|
|
7
7
|
} from "@shikijs/transformers";
|
|
8
|
+
import type {Element, ElementContent} from "hast";
|
|
8
9
|
import type {ShikiTransformer} from "shiki";
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -42,7 +43,41 @@ const tone: ShikiTransformer = {
|
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
|
-
*
|
|
46
|
+
* 펜스 메타 `[파일 이름]` → figure 로 감싸고 제목 띠에 파일 이름과 언어 · 블록 색 클래스는 figure 에도 둠
|
|
47
|
+
*/
|
|
48
|
+
const fileTitle: ShikiTransformer = {
|
|
49
|
+
name: "for-humanity:code-title",
|
|
50
|
+
root(root) {
|
|
51
|
+
const meta = this.options.meta?.__raw;
|
|
52
|
+
const title = typeof meta === "string" ? /\[([^\]]+)\]/.exec(meta)?.[1] : undefined;
|
|
53
|
+
|
|
54
|
+
if (title === undefined) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const tones = typeof meta === "string" ? ["bad", "good"].filter((word) => new RegExp(`\\b${word}\\b`).test(meta)) : [];
|
|
59
|
+
const caption: Element = {
|
|
60
|
+
type: "element",
|
|
61
|
+
tagName: "figcaption",
|
|
62
|
+
properties: {className: ["wg_prose__codeFileTitle"]},
|
|
63
|
+
children: [
|
|
64
|
+
{type: "text", value: title},
|
|
65
|
+
{type: "element", tagName: "span", properties: {className: ["wg_prose__codeFileLang"]}, children: [{type: "text", value: this.options.lang}]},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
const figure: Element = {
|
|
69
|
+
type: "element",
|
|
70
|
+
tagName: "figure",
|
|
71
|
+
properties: {className: ["wg_prose__codeFile", ...tones.map((tone) => `wg_prose__codeFile--${tone}`)]},
|
|
72
|
+
children: [caption, ...root.children.filter((child): child is ElementContent => child.type !== "doctype")],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
root.children = [figure];
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 코드 줄 표기 · `// [!code highlight|error|warning|++|--]`, `// [!code word:x]`, 메타 `{2,4-6}`, `line-numbers`, 블록 색 `bad`·`good`, 제목 `[파일 이름]`
|
|
46
81
|
*/
|
|
47
82
|
export const shikiTransformers: ShikiTransformer[] = [
|
|
48
83
|
transformerNotationHighlight({matchAlgorithm: "v3"}),
|
|
@@ -52,4 +87,5 @@ export const shikiTransformers: ShikiTransformer[] = [
|
|
|
52
87
|
transformerMetaHighlight(),
|
|
53
88
|
lineNumbers,
|
|
54
89
|
tone,
|
|
90
|
+
fileTitle,
|
|
55
91
|
];
|
|
@@ -96,6 +96,28 @@ test("code notation marks lines, meta highlights lines, and line-numbers tags th
|
|
|
96
96
|
}
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
test("a bracketed fence meta renders a file title bar that carries the block tone", async () => {
|
|
100
|
+
const root = await mkdtemp(join(tmpdir(), "for-humanity-code-title-"));
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
await writeFile(
|
|
104
|
+
join(root, "guide.md"),
|
|
105
|
+
"---\nname: Guide\nlabel: Guide\ngroup: Guide\n---\n\n## Read\n\n```ts bad [page/products.ts]\nconst a = 1;\n```\n\n```ts {1}\nconst b = 2;\n```\n",
|
|
106
|
+
);
|
|
107
|
+
const site = siteConfigSchema.parse({});
|
|
108
|
+
const [doc] = await readDocs({root, processor: createProcessor({site, root})});
|
|
109
|
+
|
|
110
|
+
assert.ok(doc);
|
|
111
|
+
assert.match(
|
|
112
|
+
doc.html,
|
|
113
|
+
/<figure class="wg_prose__codeFile wg_prose__codeFile--bad"><figcaption class="wg_prose__codeFileTitle">page\/products\.ts<span class="wg_prose__codeFileLang">ts<\/span><\/figcaption><pre class="[^"]*wg_prose__code--bad/,
|
|
114
|
+
);
|
|
115
|
+
assert.equal(doc.html.split("<figure").length - 1, 1, "braces meta is not a title");
|
|
116
|
+
} finally {
|
|
117
|
+
await rm(root, {recursive: true, force: true});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
99
121
|
test("code snippets import a file or a region from inside the document root and pass fence meta", async () => {
|
|
100
122
|
const root = await mkdtemp(join(tmpdir(), "for-humanity-snippets-"));
|
|
101
123
|
|
package/src/style/token.css
CHANGED
|
@@ -176,11 +176,13 @@
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
/* 블록 색 · 펜스 메타 bad·good · Shiki 가 pre 에 인라인으로 쓰는 코드 바탕을 바꿈 */
|
|
180
|
-
.wg_prose__code--bad
|
|
179
|
+
/* 블록 색 · 펜스 메타 bad·good · Shiki 가 pre 에 인라인으로 쓰는 코드 바탕과 제목 띠의 바탕을 바꿈 */
|
|
180
|
+
.wg_prose__code--bad,
|
|
181
|
+
.wg_prose__codeFile--bad {
|
|
181
182
|
--app-code-background: var(--app-color-bad-soft);
|
|
182
183
|
}
|
|
183
184
|
|
|
184
|
-
.wg_prose__code--good
|
|
185
|
+
.wg_prose__code--good,
|
|
186
|
+
.wg_prose__codeFile--good {
|
|
185
187
|
--app-code-background: var(--app-color-good-soft);
|
|
186
188
|
}
|