@kensio/colophon 0.2.0 → 2.1.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/README.md +133 -12
- package/dist/config.d.ts +50 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +62 -8
- package/dist/config.js.map +1 -1
- package/dist/content/index.d.ts +13 -0
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +23 -6
- package/dist/content/index.js.map +1 -1
- package/dist/generate.d.ts +8 -8
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +11 -20
- package/dist/generate.js.map +1 -1
- package/dist/highlight.d.ts +65 -0
- package/dist/highlight.d.ts.map +1 -0
- package/dist/highlight.js +114 -0
- package/dist/highlight.js.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +5 -2
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +10 -6
- package/dist/render.js.map +1 -1
- package/dist/templates/banner.d.ts.map +1 -1
- package/dist/templates/banner.js +2 -12
- package/dist/templates/banner.js.map +1 -1
- package/dist/templates/card.d.ts.map +1 -1
- package/dist/templates/card.js +2 -12
- package/dist/templates/card.js.map +1 -1
- package/dist/templates/code.d.ts +12 -0
- package/dist/templates/code.d.ts.map +1 -0
- package/dist/templates/code.js +203 -0
- package/dist/templates/code.js.map +1 -0
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +4 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/props.d.ts +7 -0
- package/dist/templates/props.d.ts.map +1 -0
- package/dist/templates/props.js +17 -0
- package/dist/templates/props.js.map +1 -0
- package/dist/types.d.ts +57 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ finished work.
|
|
|
10
10
|
|
|
11
11
|
- **Frontmatter-driven** — general props read from a post, not a fixed schema.
|
|
12
12
|
- **Templates** — a small registry of layouts; frontmatter picks one.
|
|
13
|
+
- **Syntax-highlighted code images** — the `code` template renders a snippet
|
|
14
|
+
from frontmatter with real VS Code theme colours.
|
|
13
15
|
- **Configurable branding** — colours, gradient, fonts, footer and badge come
|
|
14
16
|
from config, not from any one site's stylesheet.
|
|
15
17
|
- **Multiple sizes from one input** — a 1:1 square plus a 1.91:1 landscape by
|
|
@@ -23,7 +25,8 @@ finished work.
|
|
|
23
25
|
pnpm add @kensio/colophon
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
`sharp` is a dependency and does the SVG → PNG rasterisation
|
|
28
|
+
`sharp` is a dependency and does the SVG → PNG rasterisation; `shiki` provides
|
|
29
|
+
the grammars and themes for the `code` template.
|
|
27
30
|
|
|
28
31
|
## Quick start (CLI)
|
|
29
32
|
|
|
@@ -59,9 +62,10 @@ Run it over a content tree:
|
|
|
59
62
|
colophon content --config colophon.config.ts
|
|
60
63
|
```
|
|
61
64
|
|
|
62
|
-
For every file that declares `meta_img_props`, Colophon writes
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
For every file that declares `meta_img_props`, Colophon writes one PNG per
|
|
66
|
+
output size next to it, named `<slug>-<size>.png`
|
|
67
|
+
(`post/index.md` → `post/post-og.png` and `post/post-square.png`). Existing
|
|
68
|
+
files are skipped unless you pass `--overwrite`.
|
|
65
69
|
|
|
66
70
|
```
|
|
67
71
|
colophon [contentDir] [options]
|
|
@@ -99,8 +103,8 @@ const images = await renderMetaImages(
|
|
|
99
103
|
);
|
|
100
104
|
|
|
101
105
|
for (const image of images) {
|
|
102
|
-
|
|
103
|
-
await writeFile(`
|
|
106
|
+
// image.name is the output-size name ("og", "square", …).
|
|
107
|
+
await writeFile(`social-${image.name}.png`, image.png);
|
|
104
108
|
}
|
|
105
109
|
```
|
|
106
110
|
|
|
@@ -130,10 +134,74 @@ only want frontmatter discovery.
|
|
|
130
134
|
| -------- | ---------------------------------------------------------------------------- |
|
|
131
135
|
| `banner` | Left-aligned title with optional version, subtitle, corner badge and footer. |
|
|
132
136
|
| `card` | Minimal centred title with an optional subtitle. |
|
|
137
|
+
| `code` | Syntax-highlighted snippet on a rounded panel over the background. |
|
|
133
138
|
|
|
134
139
|
Register your own by passing `templates` in config — a template is `{ name,
|
|
135
|
-
render(context) }` returning SVG foreground content
|
|
136
|
-
over (and can override) the built-ins.
|
|
140
|
+
render(context) }` returning SVG foreground content, either directly or as a
|
|
141
|
+
promise. Anything you add merges over (and can override) the built-ins.
|
|
142
|
+
|
|
143
|
+
### The `code` template
|
|
144
|
+
|
|
145
|
+
Put the snippet in frontmatter and name its language:
|
|
146
|
+
|
|
147
|
+
```yaml
|
|
148
|
+
---
|
|
149
|
+
title: eslint changed TypeScript files only
|
|
150
|
+
slug: eslint-changed-ts-files-only
|
|
151
|
+
meta_img_props:
|
|
152
|
+
template: code
|
|
153
|
+
language: bash
|
|
154
|
+
code: |
|
|
155
|
+
mapfile -t CHANGED_TS < <(
|
|
156
|
+
git diff origin/main --name-only \
|
|
157
|
+
| grep '\.ts'
|
|
158
|
+
)
|
|
159
|
+
---
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
| Prop | Notes |
|
|
163
|
+
| ---------- | -------------------------------------------------------------------- |
|
|
164
|
+
| `code` | The snippet. Surrounding blank lines are trimmed; tabs are expanded. |
|
|
165
|
+
| `language` | Any [Shiki language]; unknown names fall back to plain text. |
|
|
166
|
+
| `title` | Optional heading above the panel. Omit for a bare code image. |
|
|
167
|
+
| `theme` | Optional per-post override of `config.code.theme`. |
|
|
168
|
+
|
|
169
|
+
[Shiki language]: https://shiki.style/languages
|
|
170
|
+
|
|
171
|
+
Pygments-style names carried over from an older pipeline (`text`, `console`,
|
|
172
|
+
`html+handlebars`, …) are mapped onto their Shiki equivalents, so existing
|
|
173
|
+
frontmatter usually needs no changes.
|
|
174
|
+
|
|
175
|
+
The font size is fitted to the snippet: Colophon measures the longest line and
|
|
176
|
+
the line count against a monospace grid and picks the largest size that fits on
|
|
177
|
+
both axes, within `minFontScale`/`maxFontScale`. Code too long to fit legibly is
|
|
178
|
+
truncated with an ellipsis rather than shrunk into unreadability. The panel then
|
|
179
|
+
shrinks vertically onto the result so short snippets aren't left floating.
|
|
180
|
+
|
|
181
|
+
Styling comes from `config.code`:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
export default defineConfig({
|
|
185
|
+
colors: { brand: "#2563eb" },
|
|
186
|
+
footer: "example.com",
|
|
187
|
+
code: {
|
|
188
|
+
theme: "night-owl", // any bundled Shiki theme
|
|
189
|
+
fontFamily: '"JetBrains Mono", monospace',
|
|
190
|
+
charWidthRatio: 0.6, // glyph advance ÷ font size, for your monospace face
|
|
191
|
+
lineHeight: 1.55,
|
|
192
|
+
tabSize: 2,
|
|
193
|
+
cornerScale: 0.025,
|
|
194
|
+
maxFontScale: 0.075,
|
|
195
|
+
minFontScale: 0.018,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`charWidthRatio` is how the layout knows where each token sits, so it must match
|
|
201
|
+
the font actually used — `0.6` suits most monospace faces (Source Code Pro,
|
|
202
|
+
Menlo, DejaVu Sans Mono); Consolas wants about `0.55`. Fonts must be installed
|
|
203
|
+
where `sharp` can see them; the default stack ends in the generic `monospace`
|
|
204
|
+
family so it always resolves to something.
|
|
137
205
|
|
|
138
206
|
## Configuration
|
|
139
207
|
|
|
@@ -146,14 +214,45 @@ All fields are optional; sensible defaults apply.
|
|
|
146
214
|
| `fontFamily` | `Arial, Helvetica, sans-serif` | Uses fonts available to `sharp`/librsvg. |
|
|
147
215
|
| `footer` | none | Footer text; omit the field for none. |
|
|
148
216
|
| `badge` | none | Corner badge for `banner`; omit the field for none. |
|
|
149
|
-
| `
|
|
150
|
-
| `
|
|
217
|
+
| `code` | `github-dark`, monospace stack | Styling for the `code` template (see above). |
|
|
218
|
+
| `sizes` | `og` + `square` | Named output sizes (see below). |
|
|
219
|
+
| `templates` | `banner`, `card`, `code` | Merged over the built-ins. |
|
|
220
|
+
|
|
221
|
+
### Output sizes and filenames
|
|
222
|
+
|
|
223
|
+
Each output size is a named `{ name, width, height }`. The `name` becomes the
|
|
224
|
+
filename suffix, so every image is distinct: `my-post-og.png`,
|
|
225
|
+
`my-post-square.png`. The default set is one 1.91:1 Open Graph landscape and one
|
|
226
|
+
1:1 square, which between them satisfy `og:image` and both `twitter:image` card
|
|
227
|
+
types (`summary_large_image` reuses the landscape; `summary` uses the square).
|
|
228
|
+
|
|
229
|
+
`SIZE_PRESETS` ships the common standards — compose your own set:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import { defineConfig, SIZE_PRESETS } from "@kensio/colophon";
|
|
233
|
+
|
|
234
|
+
export default defineConfig({
|
|
235
|
+
sizes: [
|
|
236
|
+
SIZE_PRESETS.og, // 1200×630 — og:image (Facebook, LinkedIn, Slack, …)
|
|
237
|
+
SIZE_PRESETS.square, // 1200×1200 — Twitter summary card, universal
|
|
238
|
+
SIZE_PRESETS.twitter, // 1200×600 (2:1) — Twitter summary_large_image
|
|
239
|
+
SIZE_PRESETS.pinterest, // 1000×1500 (2:3) — Pinterest
|
|
240
|
+
{ name: "hero", width: 1600, height: 900 }, // or anything custom
|
|
241
|
+
],
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The base filename is the **post slug**: Colophon reads a top-level `slug` from
|
|
246
|
+
frontmatter (SEO-friendly, keyword-rich), falling back to the file name — or the
|
|
247
|
+
parent directory for `index.*` files. Point `slugField` at a different key, or
|
|
248
|
+
override naming entirely with `generate`'s `outputPath` callback.
|
|
151
249
|
|
|
152
250
|
### Frontmatter shape
|
|
153
251
|
|
|
154
252
|
By default Colophon reads a `meta_img_props` object and a `template` field
|
|
155
|
-
within it
|
|
156
|
-
`defaultTemplate`, `extensions`) so
|
|
253
|
+
within it, plus a top-level `slug`. All are configurable via walk options
|
|
254
|
+
(`propsKey`, `templateField`, `defaultTemplate`, `slugField`, `extensions`) so
|
|
255
|
+
you can match an existing convention.
|
|
157
256
|
|
|
158
257
|
## Sample output
|
|
159
258
|
|
|
@@ -182,8 +281,30 @@ updated PNGs so this gallery stays in sync.
|
|
|
182
281
|
<sub><code>card</code> · 1200×630 · solid background, title only</sub>
|
|
183
282
|
</td>
|
|
184
283
|
</tr>
|
|
284
|
+
<tr>
|
|
285
|
+
<td>
|
|
286
|
+
<img src="docs/samples/code-square.png" alt="code template, square" width="100%" /><br />
|
|
287
|
+
<sub><code>code</code> · 1200×1200 · bash, <code>github-dark</code></sub>
|
|
288
|
+
</td>
|
|
289
|
+
<td>
|
|
290
|
+
<img src="docs/samples/code-wide.png" alt="code template, landscape" width="100%" /><br />
|
|
291
|
+
<sub><code>code</code> · 1200×630 · TypeScript, <code>night-owl</code></sub>
|
|
292
|
+
</td>
|
|
293
|
+
</tr>
|
|
185
294
|
</table>
|
|
186
295
|
|
|
296
|
+
## Upgrading from 1.x
|
|
297
|
+
|
|
298
|
+
Adding the `code` template made two small breaking changes:
|
|
299
|
+
|
|
300
|
+
- `Template.render` may now return `string | Promise<string>`, and `buildSvg`
|
|
301
|
+
is `async`. Custom templates that return a string still work unchanged; call
|
|
302
|
+
sites of `buildSvg` need an `await`. `renderMetaImages` and `generate` were
|
|
303
|
+
already async and are unaffected.
|
|
304
|
+
- `MetaImageProps.title` is optional, and `walkContent`/`extractProps` no
|
|
305
|
+
longer skip a file that declares props without a title — a `code` post
|
|
306
|
+
describes its image entirely through `code` and `language`.
|
|
307
|
+
|
|
187
308
|
## Development
|
|
188
309
|
|
|
189
310
|
- `pnpm build` — compile to `dist/`.
|
package/dist/config.d.ts
CHANGED
|
@@ -1,10 +1,57 @@
|
|
|
1
|
-
import type { BrandColors, ColophonConfig,
|
|
2
|
-
/**
|
|
3
|
-
|
|
1
|
+
import type { BrandColors, CodeStyle, ColophonConfig, OutputSize, ResolvedConfig } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Named output-size presets covering the common social-image standards. Each
|
|
4
|
+
* `name` becomes the filename suffix (e.g. `my-post-og.png`).
|
|
5
|
+
*
|
|
6
|
+
* - `og` — 1.91:1, the Open Graph standard (`og:image`): Facebook, LinkedIn,
|
|
7
|
+
* Slack, Discord, WhatsApp, Mastodon, and the usual `twitter:image` reuse.
|
|
8
|
+
* - `square` — 1:1, X/Twitter `summary` card and a universal fallback.
|
|
9
|
+
* - `twitter` — 2:1, X/Twitter `summary_large_image` card.
|
|
10
|
+
* - `pinterest` — 2:3 tall pin.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SIZE_PRESETS: {
|
|
13
|
+
readonly og: {
|
|
14
|
+
readonly name: "og";
|
|
15
|
+
readonly width: 1200;
|
|
16
|
+
readonly height: 630;
|
|
17
|
+
};
|
|
18
|
+
readonly square: {
|
|
19
|
+
readonly name: "square";
|
|
20
|
+
readonly width: 1200;
|
|
21
|
+
readonly height: 1200;
|
|
22
|
+
};
|
|
23
|
+
readonly twitter: {
|
|
24
|
+
readonly name: "twitter";
|
|
25
|
+
readonly width: 1200;
|
|
26
|
+
readonly height: 600;
|
|
27
|
+
};
|
|
28
|
+
readonly pinterest: {
|
|
29
|
+
readonly name: "pinterest";
|
|
30
|
+
readonly width: 1000;
|
|
31
|
+
readonly height: 1500;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Default output sizes: the Open Graph landscape plus a square. Between them
|
|
36
|
+
* they satisfy `og:image` and both `twitter:image` card types.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DEFAULT_SIZES: readonly OutputSize[];
|
|
4
39
|
/** Default font stack. Override via `config.fontFamily` for branded fonts. */
|
|
5
40
|
export declare const DEFAULT_FONT_FAMILY = "Arial, Helvetica, sans-serif";
|
|
6
41
|
/** Neutral default palette, used when no `colors.brand` is supplied. */
|
|
7
42
|
export declare const DEFAULT_COLORS: Required<BrandColors>;
|
|
43
|
+
/**
|
|
44
|
+
* Default monospace stack for the `code` template. It ends in the generic
|
|
45
|
+
* `monospace` family so it always resolves to something, whatever fonts the
|
|
46
|
+
* machine running the build happens to have.
|
|
47
|
+
*/
|
|
48
|
+
export declare const DEFAULT_CODE_FONT_FAMILY = "\"JetBrains Mono\", \"Source Code Pro\", \"DejaVu Sans Mono\", Menlo, Consolas, monospace";
|
|
49
|
+
/**
|
|
50
|
+
* Default `code` template styling. `charWidthRatio` and `lineHeight` drive the
|
|
51
|
+
* monospace grid the snippet is laid out on; the font-size bounds are
|
|
52
|
+
* fractions of the image height so they hold at every output size.
|
|
53
|
+
*/
|
|
54
|
+
export declare const DEFAULT_CODE_STYLE: Required<CodeStyle>;
|
|
8
55
|
/**
|
|
9
56
|
* Identity helper that returns its argument typed as {@link ColophonConfig}.
|
|
10
57
|
* Use it in a config module to get editor completion and type-checking.
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,WAAW,EACX,cAAc,EACd,UAAU,EACV,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,WAAW,EACX,SAAS,EACT,cAAc,EACd,UAAU,EACV,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY;;iBACjB,IAAI,EAAE,IAAI;iBAAE,KAAK,EAAE,IAAI;iBAAE,MAAM,EAAE,GAAG;;;iBAChC,IAAI,EAAE,QAAQ;iBAAE,KAAK,EAAE,IAAI;iBAAE,MAAM,EAAE,IAAI;;;iBACxC,IAAI,EAAE,SAAS;iBAAE,KAAK,EAAE,IAAI;iBAAE,MAAM,EAAE,GAAG;;;iBACvC,IAAI,EAAE,WAAW;iBAAE,KAAK,EAAE,IAAI;iBAAE,MAAM,EAAE,IAAI;;CACZ,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,UAAU,EAG9C,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,iCAAiC,CAAC;AAElE,wEAAwE;AACxE,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,WAAW,CAKhD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,8FACkD,CAAC;AAExF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,SAAS,CASlD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAEnE;AAoDD;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,GAAE,cAAmB,GAAG,cAAc,CAazE"}
|
package/dist/config.js
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
import { builtinTemplates } from "./templates/index.js";
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Named output-size presets covering the common social-image standards. Each
|
|
4
|
+
* `name` becomes the filename suffix (e.g. `my-post-og.png`).
|
|
5
|
+
*
|
|
6
|
+
* - `og` — 1.91:1, the Open Graph standard (`og:image`): Facebook, LinkedIn,
|
|
7
|
+
* Slack, Discord, WhatsApp, Mastodon, and the usual `twitter:image` reuse.
|
|
8
|
+
* - `square` — 1:1, X/Twitter `summary` card and a universal fallback.
|
|
9
|
+
* - `twitter` — 2:1, X/Twitter `summary_large_image` card.
|
|
10
|
+
* - `pinterest` — 2:3 tall pin.
|
|
11
|
+
*/
|
|
12
|
+
export const SIZE_PRESETS = {
|
|
13
|
+
og: { name: "og", width: 1200, height: 630 },
|
|
14
|
+
square: { name: "square", width: 1200, height: 1200 },
|
|
15
|
+
twitter: { name: "twitter", width: 1200, height: 600 },
|
|
16
|
+
pinterest: { name: "pinterest", width: 1000, height: 1500 },
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Default output sizes: the Open Graph landscape plus a square. Between them
|
|
20
|
+
* they satisfy `og:image` and both `twitter:image` card types.
|
|
21
|
+
*/
|
|
22
|
+
export const DEFAULT_SIZES = [
|
|
23
|
+
SIZE_PRESETS.og,
|
|
24
|
+
SIZE_PRESETS.square,
|
|
6
25
|
];
|
|
7
26
|
/** Default font stack. Override via `config.fontFamily` for branded fonts. */
|
|
8
27
|
export const DEFAULT_FONT_FAMILY = "Arial, Helvetica, sans-serif";
|
|
@@ -13,6 +32,27 @@ export const DEFAULT_COLORS = {
|
|
|
13
32
|
brandWarm: "#db2777",
|
|
14
33
|
foreground: "#ffffff",
|
|
15
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* Default monospace stack for the `code` template. It ends in the generic
|
|
37
|
+
* `monospace` family so it always resolves to something, whatever fonts the
|
|
38
|
+
* machine running the build happens to have.
|
|
39
|
+
*/
|
|
40
|
+
export const DEFAULT_CODE_FONT_FAMILY = '"JetBrains Mono", "Source Code Pro", "DejaVu Sans Mono", Menlo, Consolas, monospace';
|
|
41
|
+
/**
|
|
42
|
+
* Default `code` template styling. `charWidthRatio` and `lineHeight` drive the
|
|
43
|
+
* monospace grid the snippet is laid out on; the font-size bounds are
|
|
44
|
+
* fractions of the image height so they hold at every output size.
|
|
45
|
+
*/
|
|
46
|
+
export const DEFAULT_CODE_STYLE = {
|
|
47
|
+
theme: "github-dark",
|
|
48
|
+
fontFamily: DEFAULT_CODE_FONT_FAMILY,
|
|
49
|
+
charWidthRatio: 0.6,
|
|
50
|
+
lineHeight: 1.55,
|
|
51
|
+
tabSize: 2,
|
|
52
|
+
cornerScale: 0.025,
|
|
53
|
+
maxFontScale: 0.075,
|
|
54
|
+
minFontScale: 0.018,
|
|
55
|
+
};
|
|
16
56
|
/**
|
|
17
57
|
* Identity helper that returns its argument typed as {@link ColophonConfig}.
|
|
18
58
|
* Use it in a config module to get editor completion and type-checking.
|
|
@@ -43,21 +83,35 @@ function defaultBackground(colors) {
|
|
|
43
83
|
],
|
|
44
84
|
};
|
|
45
85
|
}
|
|
86
|
+
function resolveCode(code) {
|
|
87
|
+
return { ...DEFAULT_CODE_STYLE, ...code };
|
|
88
|
+
}
|
|
89
|
+
function resolveSizes(sizes) {
|
|
90
|
+
if (sizes === undefined || sizes.length === 0) {
|
|
91
|
+
return DEFAULT_SIZES;
|
|
92
|
+
}
|
|
93
|
+
const seen = new Set();
|
|
94
|
+
for (const size of sizes) {
|
|
95
|
+
if (seen.has(size.name)) {
|
|
96
|
+
throw new Error(`Duplicate output size name "${size.name}"; names must be unique so each image gets a distinct filename.`);
|
|
97
|
+
}
|
|
98
|
+
seen.add(size.name);
|
|
99
|
+
}
|
|
100
|
+
return sizes;
|
|
101
|
+
}
|
|
46
102
|
/**
|
|
47
103
|
* Apply defaults to a user config. Safe to call with no argument.
|
|
48
104
|
*/
|
|
49
105
|
export function resolveConfig(config = {}) {
|
|
50
106
|
const colors = resolveColors(config.colors);
|
|
51
|
-
const dimensions = config.dimensions && config.dimensions.length > 0
|
|
52
|
-
? config.dimensions
|
|
53
|
-
: DEFAULT_DIMENSIONS;
|
|
54
107
|
return {
|
|
55
108
|
colors,
|
|
56
109
|
background: config.background ?? defaultBackground(colors),
|
|
57
110
|
fontFamily: config.fontFamily ?? DEFAULT_FONT_FAMILY,
|
|
58
111
|
footer: config.footer,
|
|
59
112
|
badge: config.badge,
|
|
60
|
-
|
|
113
|
+
code: resolveCode(config.code),
|
|
114
|
+
sizes: resolveSizes(config.sizes),
|
|
61
115
|
templates: { ...builtinTemplates, ...config.templates },
|
|
62
116
|
};
|
|
63
117
|
}
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/content/index.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export interface WalkOptions {
|
|
|
11
11
|
readonly templateField?: string;
|
|
12
12
|
/** Template to use when the template field is absent. */
|
|
13
13
|
readonly defaultTemplate?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Top-level frontmatter field to read the post slug from (used as the base
|
|
16
|
+
* filename). Default `slug`; falls back to the file/directory name.
|
|
17
|
+
*/
|
|
18
|
+
readonly slugField?: string;
|
|
14
19
|
/** File extensions to include. Default `.md` and `.markdown`. */
|
|
15
20
|
readonly extensions?: readonly string[];
|
|
16
21
|
}
|
|
@@ -22,8 +27,16 @@ export interface ContentFile {
|
|
|
22
27
|
readonly contentPath: string;
|
|
23
28
|
/** Absolute path on disk. */
|
|
24
29
|
readonly absolutePath: string;
|
|
30
|
+
/** Base filename for this post's images (frontmatter slug, or path-derived). */
|
|
31
|
+
readonly slug: string;
|
|
25
32
|
readonly props: MetaImageProps;
|
|
26
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Derive a slug from a file path: the filename without extension, or the
|
|
36
|
+
* parent directory name when the file is `index.*` (the common page-bundle
|
|
37
|
+
* convention).
|
|
38
|
+
*/
|
|
39
|
+
export declare function slugFromPath(filePath: string): string;
|
|
27
40
|
/**
|
|
28
41
|
* Extract {@link MetaImageProps} from a parsed frontmatter object, or return
|
|
29
42
|
* `undefined` if this file should be skipped (no props object, or no usable
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAuBlD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,yDAAyD;IACzD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,6BAA6B;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIrD;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,OAAO,GAAE,IAAI,CACX,WAAW,EACX,UAAU,GAAG,eAAe,GAAG,iBAAiB,CAC5C,GACL,cAAc,GAAG,SAAS,CA4C5B;AA8BD;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,WAAW,EAAE,CAAC,CAgCxB"}
|
package/dist/content/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import matter from "gray-matter";
|
|
4
4
|
const defaultPropsKey = "meta_img_props";
|
|
5
5
|
const defaultTemplateField = "template";
|
|
6
|
+
const defaultSlugField = "slug";
|
|
6
7
|
const defaultExtensions = [".md", ".markdown"];
|
|
7
8
|
function coerceString(value) {
|
|
8
9
|
if (typeof value === "string") {
|
|
@@ -15,6 +16,16 @@ function coerceString(value) {
|
|
|
15
16
|
}
|
|
16
17
|
return undefined;
|
|
17
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Derive a slug from a file path: the filename without extension, or the
|
|
21
|
+
* parent directory name when the file is `index.*` (the common page-bundle
|
|
22
|
+
* convention).
|
|
23
|
+
*/
|
|
24
|
+
export function slugFromPath(filePath) {
|
|
25
|
+
const extension = path.extname(filePath);
|
|
26
|
+
const base = path.basename(filePath, extension);
|
|
27
|
+
return base === "index" ? path.basename(path.dirname(filePath)) : base;
|
|
28
|
+
}
|
|
18
29
|
function isRecord(value) {
|
|
19
30
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20
31
|
}
|
|
@@ -35,10 +46,9 @@ export function extractProps(frontmatter, options = {}) {
|
|
|
35
46
|
if (template === undefined) {
|
|
36
47
|
return undefined;
|
|
37
48
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
49
|
+
// Title is optional: templates such as `code` describe the image entirely
|
|
50
|
+
// from their own fields, and requiring a title would be pure boilerplate.
|
|
51
|
+
const title = coerceString(record["title"]);
|
|
42
52
|
const subtitle = coerceString(record["subtitle"]);
|
|
43
53
|
const version = coerceString(record["version"]);
|
|
44
54
|
const extras = {};
|
|
@@ -54,7 +64,7 @@ export function extractProps(frontmatter, options = {}) {
|
|
|
54
64
|
return {
|
|
55
65
|
...extras,
|
|
56
66
|
template,
|
|
57
|
-
title
|
|
67
|
+
...(title !== undefined && { title }),
|
|
58
68
|
...(subtitle !== undefined && { subtitle }),
|
|
59
69
|
...(version !== undefined && { version }),
|
|
60
70
|
};
|
|
@@ -82,15 +92,22 @@ async function collectContentFiles(dir, extensions) {
|
|
|
82
92
|
export async function walkContent(options) {
|
|
83
93
|
const extensions = options.extensions ?? defaultExtensions;
|
|
84
94
|
const filePaths = await collectContentFiles(options.dir, extensions);
|
|
95
|
+
const slugField = options.slugField ?? defaultSlugField;
|
|
85
96
|
const files = await Promise.all(filePaths.map(async (filePath) => {
|
|
86
97
|
const raw = await readFile(filePath, "utf8");
|
|
87
|
-
const
|
|
98
|
+
const frontmatter = matter(raw).data;
|
|
99
|
+
const props = extractProps(frontmatter, options);
|
|
88
100
|
if (props === undefined) {
|
|
89
101
|
return;
|
|
90
102
|
}
|
|
103
|
+
const declaredSlug = coerceString(frontmatter[slugField])?.trim();
|
|
104
|
+
const slug = declaredSlug !== undefined && declaredSlug !== ""
|
|
105
|
+
? declaredSlug
|
|
106
|
+
: slugFromPath(filePath);
|
|
91
107
|
return {
|
|
92
108
|
contentPath: path.relative(options.dir, filePath),
|
|
93
109
|
absolutePath: filePath,
|
|
110
|
+
slug,
|
|
94
111
|
props,
|
|
95
112
|
};
|
|
96
113
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,MAAM,MAAM,aAAa,CAAC;AAIjC,MAAM,eAAe,GAAG,gBAAgB,CAAC;AACzC,MAAM,oBAAoB,GAAG,UAAU,CAAC;AACxC,MAAM,iBAAiB,GAAsB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;AAElE,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,OAAO,KAAK,KAAK,SAAS;QAC1B,OAAO,KAAK,KAAK,QAAQ,EACzB,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"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"}
|
package/dist/generate.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ContentFile, WalkOptions } from "./content/index.js";
|
|
2
|
-
import type { ColophonConfig,
|
|
2
|
+
import type { ColophonConfig, OutputSize } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Outcome for one generated (or skipped) image.
|
|
5
5
|
*/
|
|
6
6
|
export interface GeneratedImage {
|
|
7
7
|
readonly contentPath: string;
|
|
8
|
-
readonly
|
|
8
|
+
readonly size: OutputSize;
|
|
9
9
|
readonly outputPath: string;
|
|
10
10
|
/** True when an existing file was left in place (no `overwrite`). */
|
|
11
11
|
readonly skipped: boolean;
|
|
@@ -17,21 +17,21 @@ export interface GenerateOptions {
|
|
|
17
17
|
/** Root content directory to walk. */
|
|
18
18
|
readonly contentDir: string;
|
|
19
19
|
readonly config?: ColophonConfig;
|
|
20
|
-
/** Extra walk options (props key, template field,
|
|
20
|
+
/** Extra walk options (props key, template field, slug field, extensions). */
|
|
21
21
|
readonly walk?: Omit<WalkOptions, "dir">;
|
|
22
22
|
/** Override where each image is written. */
|
|
23
|
-
readonly outputPath?: (file: ContentFile,
|
|
23
|
+
readonly outputPath?: (file: ContentFile, size: OutputSize) => string;
|
|
24
24
|
/** Re-render even when the output file already exists. Default `false`. */
|
|
25
25
|
readonly overwrite?: boolean;
|
|
26
26
|
/** Called after each image is written or skipped. */
|
|
27
27
|
readonly onResult?: (result: GeneratedImage) => void;
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* Default output path: alongside the content file, named
|
|
31
|
-
*
|
|
32
|
-
* keeps
|
|
30
|
+
* Default output path: alongside the content file, named `<slug>-<size>.png`.
|
|
31
|
+
* The slug carries the post's keywords into the filename and the size name
|
|
32
|
+
* keeps every image distinct (e.g. `my-post-og.png`, `my-post-square.png`).
|
|
33
33
|
*/
|
|
34
|
-
export declare function defaultOutputPath(file: ContentFile,
|
|
34
|
+
export declare function defaultOutputPath(file: ContentFile, size: OutputSize): string;
|
|
35
35
|
/**
|
|
36
36
|
* Walk a content tree and render meta images for every file that declares
|
|
37
37
|
* props, writing PNGs to disk. Existing files are skipped unless `overwrite`
|
package/dist/generate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACzC,4CAA4C;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,KAAK,MAAM,CAAC;IACtE,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CACtD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAG7E;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,EAAE,CAAC,CAgC3B"}
|
package/dist/generate.js
CHANGED
|
@@ -5,19 +5,13 @@ import { resolveConfig } from "./config.js";
|
|
|
5
5
|
import { walkContent } from "./content/index.js";
|
|
6
6
|
import { buildSvg, renderSvgToPng } from "./render.js";
|
|
7
7
|
/**
|
|
8
|
-
* Default output path: alongside the content file, named
|
|
9
|
-
*
|
|
10
|
-
* keeps
|
|
8
|
+
* Default output path: alongside the content file, named `<slug>-<size>.png`.
|
|
9
|
+
* The slug carries the post's keywords into the filename and the size name
|
|
10
|
+
* keeps every image distinct (e.g. `my-post-og.png`, `my-post-square.png`).
|
|
11
11
|
*/
|
|
12
|
-
export function defaultOutputPath(file,
|
|
12
|
+
export function defaultOutputPath(file, size) {
|
|
13
13
|
const directory = path.dirname(file.absolutePath);
|
|
14
|
-
|
|
15
|
-
const base = path.basename(file.contentPath, extension);
|
|
16
|
-
const stem = base === "index" ? path.basename(directory) : base;
|
|
17
|
-
const suffix = index === 0
|
|
18
|
-
? ""
|
|
19
|
-
: `-${String(dimensions.width)}x${String(dimensions.height)}`;
|
|
20
|
-
return path.join(directory, `${stem}${suffix}.png`);
|
|
14
|
+
return path.join(directory, `${file.slug}-${size.name}.png`);
|
|
21
15
|
}
|
|
22
16
|
/**
|
|
23
17
|
* Walk a content tree and render meta images for every file that declares
|
|
@@ -29,23 +23,20 @@ export async function generate(options) {
|
|
|
29
23
|
const resolved = resolveConfig(options.config);
|
|
30
24
|
const toOutputPath = options.outputPath ?? defaultOutputPath;
|
|
31
25
|
const files = await walkContent({ dir: options.contentDir, ...options.walk });
|
|
32
|
-
const jobs = files.flatMap((file) => resolved.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
index,
|
|
36
|
-
})));
|
|
37
|
-
return Promise.all(jobs.map(async ({ file, dimensions, index }) => {
|
|
38
|
-
const outputPath = toOutputPath(file, dimensions, index);
|
|
26
|
+
const jobs = files.flatMap((file) => resolved.sizes.map((size) => ({ file, size })));
|
|
27
|
+
return Promise.all(jobs.map(async ({ file, size }) => {
|
|
28
|
+
const outputPath = toOutputPath(file, size);
|
|
39
29
|
const isSkipped = existsSync(outputPath) && options.overwrite !== true;
|
|
40
30
|
if (!isSkipped) {
|
|
41
|
-
const
|
|
31
|
+
const dimensions = { width: size.width, height: size.height };
|
|
32
|
+
const svg = await buildSvg(file.props, resolved, dimensions);
|
|
42
33
|
const png = await renderSvgToPng(svg, dimensions);
|
|
43
34
|
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
44
35
|
await writeFile(outputPath, png);
|
|
45
36
|
}
|
|
46
37
|
const result = {
|
|
47
38
|
contentPath: file.contentPath,
|
|
48
|
-
|
|
39
|
+
size,
|
|
49
40
|
outputPath,
|
|
50
41
|
skipped: isSkipped,
|
|
51
42
|
};
|
package/dist/generate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA+BvD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAiB,EAAE,IAAgB;IACnE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,OAAwB;IAExB,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,IAAI,iBAAiB,CAAC;IAC7D,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAE9E,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAClC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAC/C,CAAC;IAEF,OAAO,OAAO,CAAC,GAAG,CAChB,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE;QAChC,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5C,MAAM,SAAS,GAAG,UAAU,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,SAAS,KAAK,IAAI,CAAC;QAEvE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,UAAU,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;YAC9D,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;YAC7D,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YAClD,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3D,MAAM,SAAS,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACnC,CAAC;QAED,MAAM,MAAM,GAAmB;YAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,IAAI;YACJ,UAAU;YACV,OAAO,EAAE,SAAS;SACnB,CAAC;QACF,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC;QAC3B,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CACH,CAAC;AACJ,CAAC"}
|