@soonit/rspress-plugin-og 0.0.0 → 0.0.2

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,4 +1,4 @@
1
- import { t as Options } from "./types-d5VtTQ1o.mjs";
1
+ import { t as Options } from "./types-ChngGzQc.mjs";
2
2
  import * as _rspress_core0 from "@rspress/core";
3
3
  import { PageIndexInfo } from "@rspress/core";
4
4
 
package/dist/index.mjs CHANGED
@@ -51,19 +51,76 @@ const templates = /* @__PURE__ */ new Map();
51
51
  function escapeHtml(unsafe) {
52
52
  return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
53
53
  }
54
+ /**
55
+ * Wrap a title string into at most three lines with a best-effort “do not break words” strategy.
56
+ *
57
+ * The input is first split by explicit newlines into paragraphs (empty lines are ignored). Then each
58
+ * paragraph is wrapped into lines whose length does not exceed {@link maxSizePerLine} when possible:
59
+ *
60
+ * - If the paragraph contains whitespace, it is tokenized by whitespace and tokens are joined with a
61
+ * single space.
62
+ * - If the paragraph contains no whitespace (e.g. CJK text), it is tokenized by individual characters.
63
+ * - If a single token is longer than the line limit, it is hard-split into fixed-size chunks.
64
+ *
65
+ * Wrapping stops once three lines have been produced in total.
66
+ *
67
+ * @param input - The title text to wrap. `null`/`undefined`/empty (after trimming) results in `[]`.
68
+ * @param maxSizePerLine - Maximum allowed character count per line. Values `<= 0` are treated as `1`.
69
+ * @returns An array of wrapped lines, with a maximum length of 3.
70
+ */
71
+ function wrapTitleToLines(input, maxSizePerLine) {
72
+ const text = (input ?? "").trim();
73
+ if (!text) return [];
74
+ const max = Math.max(1, maxSizePerLine || 1);
75
+ const paragraphs = text.split(/\r?\n+/).map((s) => s.trim()).filter(Boolean);
76
+ const out = [];
77
+ for (const para of paragraphs) {
78
+ if (out.length >= 3) break;
79
+ const tokens = /\s/.test(para) ? para.split(/\s+/).filter(Boolean) : [...para];
80
+ let line = "";
81
+ for (const token of tokens) {
82
+ if (out.length >= 3) break;
83
+ const next = line ? /\s/.test(para) ? `${line} ${token}` : `${line}${token}` : token;
84
+ if (next.length <= max) {
85
+ line = next;
86
+ continue;
87
+ }
88
+ if (line) {
89
+ out.push(line);
90
+ line = "";
91
+ if (out.length >= 3) break;
92
+ }
93
+ if (token.length > max) {
94
+ const chunks = token.match(new RegExp(`.{1,${max}}`, "g")) ?? [];
95
+ for (const chunk of chunks) {
96
+ if (out.length >= 3) break;
97
+ if (chunk.length === max) out.push(chunk);
98
+ else line = chunk;
99
+ }
100
+ } else line = token;
101
+ }
102
+ if (out.length >= 3) break;
103
+ if (line) out.push(line);
104
+ }
105
+ return out.slice(0, 3);
106
+ }
54
107
  async function generateOgImage({ title }, output, options) {
55
108
  if (existsSync(output)) return;
56
109
  if (!templates.has(options.ogTemplate)) templates.set(options.ogTemplate, readFileSync(options.ogTemplate, "utf-8"));
57
110
  const ogTemplate = templates.get(options.ogTemplate);
58
111
  mkdirSync(dirname(output), { recursive: true });
59
- const lines = title.trim().split(new RegExp(`(.{0,${options.maxTitleSizePerLine}})(?:\\s|$)`, "g")).filter(Boolean);
112
+ const lines = wrapTitleToLines(title, options.maxTitleSizePerLine);
113
+ if (lines.length === 0) {
114
+ console.warn("[vitepress-plugin-og] Missing page title, skip og image generation.");
115
+ return;
116
+ }
60
117
  const data = {
61
118
  line1: lines[0] ? escapeHtml(lines[0]) : "",
62
119
  line2: lines[1] ? escapeHtml(lines[1]) : "",
63
120
  line3: lines[2] ? escapeHtml(lines[2]) : ""
64
121
  };
65
122
  const svg = ogTemplate.replace(/\{\{([^}]+)\}\}/g, (_, name) => data[name] || "");
66
- await sharp(Buffer.from(svg)).resize(1200, 630).png().toFile(output);
123
+ await sharp(Buffer.from(svg), options.sharpOptions).resize(1200, 630).png().toFile(output);
67
124
  }
68
125
 
69
126
  //#endregion
@@ -326,6 +383,7 @@ function resolveOptions(userOptions) {
326
383
  outDir: "og",
327
384
  ogTemplate: "og-template.svg",
328
385
  maxTitleSizePerLine: 30,
386
+ sharpOptions: {},
329
387
  ...userOptions
330
388
  };
331
389
  }
@@ -1,3 +1,5 @@
1
+ import sharp from "sharp";
2
+
1
3
  //#region ../core/src/types.d.ts
2
4
  interface Options {
3
5
  /**
@@ -23,6 +25,12 @@ interface Options {
23
25
  * @default 'og-template.svg' for Rspress
24
26
  */
25
27
  ogTemplate?: string;
28
+ /**
29
+ * Options to pass to the `sharp` image processing library.
30
+ *
31
+ * @default {}
32
+ */
33
+ sharpOptions?: sharp.SharpOptions;
26
34
  }
27
35
  interface ResolvedOptions extends Required<Options> {}
28
36
  //#endregion
package/dist/types.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as ResolvedOptions, t as Options } from "./types-d5VtTQ1o.mjs";
1
+ import { n as ResolvedOptions, t as Options } from "./types-ChngGzQc.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.0",
4
+ "version": "0.0.2",
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",
@@ -11,11 +11,10 @@
11
11
  "type": "git",
12
12
  "url": "https://github.com/Barbapapazes/vitepress-plugin-og"
13
13
  },
14
- "bugs": "https://github.com/Barbapapazes/vitepress-plugin-og/issues",
15
14
  "publishConfig": {
16
- "access": "public",
17
- "registry": "https://registry.npmjs.org/"
15
+ "access": "public"
18
16
  },
17
+ "bugs": "https://github.com/Barbapapazes/vitepress-plugin-og/issues",
19
18
  "exports": {
20
19
  ".": {
21
20
  "types": "./dist/index.d.mts",