@soonit/rspress-plugin-og 0.0.1 → 0.0.5
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/dist/index.d.mts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import { t as Options } from "./types-
|
|
2
|
-
import
|
|
3
|
-
import { PageIndexInfo } from "@rspress/core";
|
|
1
|
+
import { t as Options } from "./types-R_GC9oNb.mjs";
|
|
2
|
+
import { RspressPlugin } from "@rspress/core";
|
|
4
3
|
|
|
5
4
|
//#region src/index.d.ts
|
|
6
|
-
declare function export_default(userOptions: Options):
|
|
7
|
-
name: string;
|
|
8
|
-
config(config: _rspress_core0.UserConfig): _rspress_core0.UserConfig;
|
|
9
|
-
extendPageData: (pageData: PageIndexInfo) => void;
|
|
10
|
-
afterBuild(config: _rspress_core0.UserConfig): Promise<void>;
|
|
11
|
-
};
|
|
5
|
+
declare function export_default(userOptions: Options): RspressPlugin;
|
|
12
6
|
//#endregion
|
|
13
7
|
export { export_default as default };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { dirname, join, relative } from "node:path";
|
|
2
|
+
import { performance } from "node:perf_hooks";
|
|
2
3
|
import node_process, { cwd } from "node:process";
|
|
3
|
-
import { Buffer } from "node:buffer";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
5
|
-
import
|
|
5
|
+
import { writeFile } from "node:fs/promises";
|
|
6
|
+
import { Resvg } from "@resvg/resvg-js";
|
|
6
7
|
import node_os from "node:os";
|
|
7
8
|
import node_tty from "node:tty";
|
|
8
9
|
import { joinURL } from "ufo";
|
|
@@ -51,19 +52,81 @@ const templates = /* @__PURE__ */ new Map();
|
|
|
51
52
|
function escapeHtml(unsafe) {
|
|
52
53
|
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
53
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Wrap a title string into at most three lines with a best-effort “do not break words” strategy.
|
|
57
|
+
*
|
|
58
|
+
* The input is first split by explicit newlines into paragraphs (empty lines are ignored). Then each
|
|
59
|
+
* paragraph is wrapped into lines whose length does not exceed {@link maxSizePerLine} when possible:
|
|
60
|
+
*
|
|
61
|
+
* - If the paragraph contains whitespace, it is tokenized by whitespace and tokens are joined with a
|
|
62
|
+
* single space.
|
|
63
|
+
* - If the paragraph contains no whitespace (e.g. CJK text), it is tokenized by individual characters.
|
|
64
|
+
* - If a single token is longer than the line limit, it is hard-split into fixed-size chunks.
|
|
65
|
+
*
|
|
66
|
+
* Wrapping stops once three lines have been produced in total.
|
|
67
|
+
*
|
|
68
|
+
* @param input - The title text to wrap. `null`/`undefined`/empty (after trimming) results in `[]`.
|
|
69
|
+
* @param maxSizePerLine - Maximum allowed character count per line. Values `<= 0` are treated as `1`.
|
|
70
|
+
* @returns An array of wrapped lines, with a maximum length of 3.
|
|
71
|
+
*/
|
|
72
|
+
function wrapTitleToLines(input, maxSizePerLine) {
|
|
73
|
+
const text = (input ?? "").trim();
|
|
74
|
+
if (!text) return [];
|
|
75
|
+
const max = Math.max(1, maxSizePerLine || 1);
|
|
76
|
+
const paragraphs = text.split(/\r?\n+/).map((s) => s.trim()).filter(Boolean);
|
|
77
|
+
const out = [];
|
|
78
|
+
for (const para of paragraphs) {
|
|
79
|
+
if (out.length >= 3) break;
|
|
80
|
+
const tokens = /\s/.test(para) ? para.split(/\s+/).filter(Boolean) : [...para];
|
|
81
|
+
let line = "";
|
|
82
|
+
for (const token of tokens) {
|
|
83
|
+
if (out.length >= 3) break;
|
|
84
|
+
const next = line ? /\s/.test(para) ? `${line} ${token}` : `${line}${token}` : token;
|
|
85
|
+
if (next.length <= max) {
|
|
86
|
+
line = next;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (line) {
|
|
90
|
+
out.push(line);
|
|
91
|
+
line = "";
|
|
92
|
+
if (out.length >= 3) break;
|
|
93
|
+
}
|
|
94
|
+
if (token.length > max) {
|
|
95
|
+
const chunks = token.match(new RegExp(`.{1,${max}}`, "g")) ?? [];
|
|
96
|
+
for (const chunk of chunks) {
|
|
97
|
+
if (out.length >= 3) break;
|
|
98
|
+
if (chunk.length === max) out.push(chunk);
|
|
99
|
+
else line = chunk;
|
|
100
|
+
}
|
|
101
|
+
} else line = token;
|
|
102
|
+
}
|
|
103
|
+
if (out.length >= 3) break;
|
|
104
|
+
if (line) out.push(line);
|
|
105
|
+
}
|
|
106
|
+
return out.slice(0, 3);
|
|
107
|
+
}
|
|
54
108
|
async function generateOgImage({ title }, output, options) {
|
|
55
109
|
if (existsSync(output)) return;
|
|
56
110
|
if (!templates.has(options.ogTemplate)) templates.set(options.ogTemplate, readFileSync(options.ogTemplate, "utf-8"));
|
|
57
111
|
const ogTemplate = templates.get(options.ogTemplate);
|
|
58
112
|
mkdirSync(dirname(output), { recursive: true });
|
|
59
|
-
const lines = title
|
|
113
|
+
const lines = wrapTitleToLines(title, options.maxTitleSizePerLine);
|
|
114
|
+
if (lines.length === 0) {
|
|
115
|
+
console.warn("[vitepress-plugin-og] Missing page title, skip og image generation.");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
60
118
|
const data = {
|
|
61
119
|
line1: lines[0] ? escapeHtml(lines[0]) : "",
|
|
62
120
|
line2: lines[1] ? escapeHtml(lines[1]) : "",
|
|
63
121
|
line3: lines[2] ? escapeHtml(lines[2]) : ""
|
|
64
122
|
};
|
|
65
|
-
|
|
66
|
-
|
|
123
|
+
await writeFile(output, new Resvg(ogTemplate.replace(/\{\{([^}]+)\}\}/g, (_, name) => data[name] || ""), {
|
|
124
|
+
fitTo: {
|
|
125
|
+
mode: "width",
|
|
126
|
+
value: 1200
|
|
127
|
+
},
|
|
128
|
+
...options.resvgOptions
|
|
129
|
+
}).render().asPng());
|
|
67
130
|
}
|
|
68
131
|
|
|
69
132
|
//#endregion
|
|
@@ -320,14 +383,17 @@ let src_logger = createLogger();
|
|
|
320
383
|
|
|
321
384
|
//#endregion
|
|
322
385
|
//#region src/options.ts
|
|
323
|
-
function resolveOptions(userOptions) {
|
|
324
|
-
|
|
386
|
+
async function resolveOptions(userOptions) {
|
|
387
|
+
const options = {
|
|
325
388
|
domain: "",
|
|
326
389
|
outDir: "og",
|
|
327
390
|
ogTemplate: "og-template.svg",
|
|
328
391
|
maxTitleSizePerLine: 30,
|
|
392
|
+
resvgOptions: void 0,
|
|
329
393
|
...userOptions
|
|
330
394
|
};
|
|
395
|
+
if (typeof options.resvgOptions === "function") options.resvgOptions = await options.resvgOptions();
|
|
396
|
+
return options;
|
|
331
397
|
}
|
|
332
398
|
|
|
333
399
|
//#endregion
|
|
@@ -335,7 +401,7 @@ function resolveOptions(userOptions) {
|
|
|
335
401
|
const NAME = "rspress-plugin-og";
|
|
336
402
|
const LOG_PREFIX = `[${NAME}]`;
|
|
337
403
|
function src_default(userOptions) {
|
|
338
|
-
|
|
404
|
+
let options;
|
|
339
405
|
const images = /* @__PURE__ */ new Map();
|
|
340
406
|
const headCreators = [
|
|
341
407
|
(url) => createTwitterImageHead(url),
|
|
@@ -347,7 +413,8 @@ function src_default(userOptions) {
|
|
|
347
413
|
];
|
|
348
414
|
return {
|
|
349
415
|
name: NAME,
|
|
350
|
-
config(config) {
|
|
416
|
+
async config(config) {
|
|
417
|
+
options = await resolveOptions(userOptions);
|
|
351
418
|
config.head = [...config.head || [], ...headCreators.map((creator) => (route) => {
|
|
352
419
|
const imageInfo = images.get(route.routePath);
|
|
353
420
|
if (!imageInfo) return;
|
|
@@ -370,10 +437,12 @@ function src_default(userOptions) {
|
|
|
370
437
|
async afterBuild(config) {
|
|
371
438
|
const outputFolder = join(cwd(), config.outDir ?? "doc_build", options.outDir);
|
|
372
439
|
src_logger.info(`${LOG_PREFIX} Generating OG images to ${relative(cwd(), outputFolder)} ...`);
|
|
440
|
+
const start = performance.now();
|
|
373
441
|
await Promise.all(Array.from(images.entries()).map(([_, { title, imageName }]) => {
|
|
374
442
|
return generateOgImage({ title }, join(outputFolder, imageName), options);
|
|
375
443
|
}));
|
|
376
|
-
|
|
444
|
+
const duration = (performance.now() - start) / 1e3;
|
|
445
|
+
src_logger.success(`${LOG_PREFIX} ${images.size} OG images generated in ${duration.toFixed(2)}s.`);
|
|
377
446
|
}
|
|
378
447
|
};
|
|
379
448
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ResvgRenderOptions } from "@resvg/resvg-js";
|
|
2
|
+
|
|
1
3
|
//#region ../core/src/types.d.ts
|
|
2
4
|
interface Options {
|
|
3
5
|
/**
|
|
@@ -23,8 +25,14 @@ interface Options {
|
|
|
23
25
|
* @default 'og-template.svg' for Rspress
|
|
24
26
|
*/
|
|
25
27
|
ogTemplate?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Options for resvg rendering
|
|
30
|
+
*/
|
|
31
|
+
resvgOptions?: ResvgRenderOptions | (() => Promise<ResvgRenderOptions>);
|
|
32
|
+
}
|
|
33
|
+
interface ResolvedOptions extends Required<Omit<Options, 'resvgOptions'>> {
|
|
34
|
+
resvgOptions?: ResvgRenderOptions;
|
|
26
35
|
}
|
|
27
|
-
interface ResolvedOptions extends Required<Options> {}
|
|
28
36
|
//#endregion
|
|
29
37
|
//#region src/types.d.ts
|
|
30
38
|
interface Options$1 extends Options {}
|
package/dist/types.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as ResolvedOptions, t as Options } from "./types-
|
|
1
|
+
import { n as ResolvedOptions, t as Options } from "./types-R_GC9oNb.mjs";
|
|
2
2
|
export { Options, ResolvedOptions };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soonit/rspress-plugin-og",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.5",
|
|
5
5
|
"description": "Automatically generate Open Graph images for your Rspress pages.",
|
|
6
6
|
"author": "Estéban Soubiran <esteban@soubiran.dev>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -12,10 +12,6 @@
|
|
|
12
12
|
"url": "https://github.com/Barbapapazes/vitepress-plugin-og"
|
|
13
13
|
},
|
|
14
14
|
"bugs": "https://github.com/Barbapapazes/vitepress-plugin-og/issues",
|
|
15
|
-
"publishConfig": {
|
|
16
|
-
"access": "public",
|
|
17
|
-
"registry": "https://registry.npmjs.org/"
|
|
18
|
-
},
|
|
19
15
|
"exports": {
|
|
20
16
|
".": {
|
|
21
17
|
"types": "./dist/index.d.mts",
|
|
@@ -39,7 +35,7 @@
|
|
|
39
35
|
"@rspress/core": "^2.0.0-rc.1 || ^2.0.0"
|
|
40
36
|
},
|
|
41
37
|
"dependencies": {
|
|
42
|
-
"
|
|
38
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
43
39
|
"ufo": "^1.6.1"
|
|
44
40
|
},
|
|
45
41
|
"devDependencies": {
|