nuxt-og-image 6.7.7 → 6.8.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/dist/chunks/tw4.mjs +2 -2
- package/dist/chunks/uno.mjs +4 -4
- package/dist/cli.mjs +1 -1
- package/dist/devtools/nuxt.config.ts +4 -1
- package/dist/module.d.mts +10 -0
- package/dist/module.d.ts +10 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +3 -3
- package/dist/runtime/app/client-utils.js +4 -3
- package/dist/runtime/app/utils.js +3 -2
- package/dist/runtime/server/og-image/bindings/satori/harfbuzz.d.ts +4 -0
- package/dist/runtime/server/og-image/bindings/satori/harfbuzz.js +33 -0
- package/dist/runtime/server/og-image/font-subsets.d.ts +3 -11
- package/dist/runtime/server/og-image/font-subsets.js +50 -27
- package/dist/runtime/server/og-image/fonts.d.ts +1 -1
- package/dist/runtime/server/util/eventHandlers.js +3 -3
- package/dist/runtime/shared/urlEncoding.js +2 -1
- package/dist/runtime/shared.d.ts +10 -1
- package/dist/runtime/shared.js +2 -2
- package/dist/runtime/types.d.ts +2 -0
- package/dist/shared/{nuxt-og-image.BMHzg1Vz.mjs → nuxt-og-image.D936Fp6O.mjs} +610 -467
- package/package.json +66 -59
- package/types/virtual.d.ts +18 -0
package/dist/chunks/tw4.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
3
|
import { resolveModulePath } from 'exsolve';
|
|
4
4
|
import { dirname, join } from 'pathe';
|
|
5
|
-
import { e as extractVariantBaseClasses, r as resolveVariantPrefixes, h as resolveExtractedVars, l as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, m as loadLightningCss, a as extractCssVars, c as extractUniversalVars, d as extractPerClassVars, g as extractVarsFromCss, f as extractClassStyles } from '../shared/nuxt-og-image.
|
|
5
|
+
import { e as extractVariantBaseClasses, r as resolveVariantPrefixes, h as resolveExtractedVars, l as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, m as loadLightningCss, a as extractCssVars, c as extractUniversalVars, d as extractPerClassVars, g as extractVarsFromCss, f as extractClassStyles } from '../shared/nuxt-og-image.D936Fp6O.mjs';
|
|
6
6
|
import 'node:crypto';
|
|
7
7
|
import 'node:fs';
|
|
8
8
|
import '@nuxt/kit';
|
|
@@ -12,6 +12,7 @@ import 'nuxt-site-config/kit';
|
|
|
12
12
|
import 'nuxtseo-shared/kit';
|
|
13
13
|
import 'pkg-types';
|
|
14
14
|
import 'std-env';
|
|
15
|
+
import 'magic-string';
|
|
15
16
|
import 'oxc-walker';
|
|
16
17
|
import '../../dist/runtime/logger.js';
|
|
17
18
|
import 'chrome-launcher';
|
|
@@ -20,7 +21,6 @@ import 'nuxtseo-shared/devtools';
|
|
|
20
21
|
import 'unstorage';
|
|
21
22
|
import 'unstorage/drivers/fs-lite';
|
|
22
23
|
import '../../dist/runtime/server/og-image/utils/css.js';
|
|
23
|
-
import 'magic-string';
|
|
24
24
|
import 'strip-literal';
|
|
25
25
|
import 'ufo';
|
|
26
26
|
import 'unplugin';
|
package/dist/chunks/uno.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { defu } from 'defu';
|
|
3
3
|
import { logger } from '../../dist/runtime/logger.js';
|
|
4
|
-
import { e as extractVariantBaseClasses, s as simplifyCss, a as extractCssVars, b as extractPropertyInitialValues, c as extractUniversalVars, d as extractPerClassVars, f as extractClassStyles, p as postProcessStyles, r as resolveVariantPrefixes, i as importOptionalPeer, g as extractVarsFromCss, h as resolveExtractedVars } from '../shared/nuxt-og-image.
|
|
5
|
-
export { j as importResolvedModule, k as resolveOptionalModulePath } from '../shared/nuxt-og-image.
|
|
4
|
+
import { e as extractVariantBaseClasses, s as simplifyCss, a as extractCssVars, b as extractPropertyInitialValues, c as extractUniversalVars, d as extractPerClassVars, f as extractClassStyles, p as postProcessStyles, r as resolveVariantPrefixes, i as importOptionalPeer, g as extractVarsFromCss, h as resolveExtractedVars } from '../shared/nuxt-og-image.D936Fp6O.mjs';
|
|
5
|
+
export { j as importResolvedModule, k as resolveOptionalModulePath } from '../shared/nuxt-og-image.D936Fp6O.mjs';
|
|
6
6
|
import 'node:crypto';
|
|
7
7
|
import 'node:fs';
|
|
8
8
|
import 'node:url';
|
|
@@ -13,14 +13,15 @@ import 'nuxtseo-shared/kit';
|
|
|
13
13
|
import 'pathe';
|
|
14
14
|
import 'pkg-types';
|
|
15
15
|
import 'std-env';
|
|
16
|
+
import 'magic-string';
|
|
16
17
|
import 'oxc-walker';
|
|
18
|
+
import 'exsolve';
|
|
17
19
|
import 'chrome-launcher';
|
|
18
20
|
import 'nypm';
|
|
19
21
|
import 'nuxtseo-shared/devtools';
|
|
20
22
|
import 'unstorage';
|
|
21
23
|
import 'unstorage/drivers/fs-lite';
|
|
22
24
|
import '../../dist/runtime/server/og-image/utils/css.js';
|
|
23
|
-
import 'magic-string';
|
|
24
25
|
import 'strip-literal';
|
|
25
26
|
import 'ufo';
|
|
26
27
|
import 'unplugin';
|
|
@@ -28,7 +29,6 @@ import '@vue/compiler-sfc';
|
|
|
28
29
|
import 'ofetch';
|
|
29
30
|
import 'ultrahtml';
|
|
30
31
|
import '../../dist/runtime/server/og-image/core/transforms/emojis/emoji-utils.js';
|
|
31
|
-
import 'exsolve';
|
|
32
32
|
import '../shared/nuxt-og-image.CJa2KCie.mjs';
|
|
33
33
|
import 'magicast';
|
|
34
34
|
import 'magicast/helpers';
|
package/dist/cli.mjs
CHANGED
|
@@ -1211,7 +1211,7 @@ async function runCreate(name, args2) {
|
|
|
1211
1211
|
message: "Add defineOgImage to a page? (relative path from pages/, leave empty to skip)",
|
|
1212
1212
|
placeholder: "index.vue"
|
|
1213
1213
|
});
|
|
1214
|
-
if (
|
|
1214
|
+
if (typeof pageInput === "string" && pageInput.trim()) {
|
|
1215
1215
|
let pagePath = pageInput.trim();
|
|
1216
1216
|
if (!pagePath.endsWith(".vue"))
|
|
1217
1217
|
pagePath += ".vue";
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
1
2
|
import { resolve } from 'pathe'
|
|
2
3
|
|
|
4
|
+
const currentDir = fileURLToPath(new URL('.', import.meta.url))
|
|
5
|
+
|
|
3
6
|
// Nuxt SEO devtools panel, shipped as a layer (Model C). Components flat-registered.
|
|
4
7
|
export default defineNuxtConfig({
|
|
5
|
-
components: [{ path: resolve(
|
|
8
|
+
components: [{ path: resolve(currentDir, './components'), pathPrefix: false }],
|
|
6
9
|
})
|
package/dist/module.d.mts
CHANGED
|
@@ -118,6 +118,16 @@ interface ModuleOptions {
|
|
|
118
118
|
* @default 60 * 60 * 24 * 3 (3 days)
|
|
119
119
|
*/
|
|
120
120
|
cacheMaxAgeSeconds?: number;
|
|
121
|
+
/**
|
|
122
|
+
* Include Twitter card meta tags (`twitter:card`, `twitter:image`, etc)
|
|
123
|
+
* alongside the Open Graph image tags.
|
|
124
|
+
*
|
|
125
|
+
* Set to `false` to only emit `og:image` tags. When unset, the module respects
|
|
126
|
+
* `seo.automaticTwitterTags` (nuxt-seo-kit) before defaulting to `true`.
|
|
127
|
+
*
|
|
128
|
+
* @default true
|
|
129
|
+
*/
|
|
130
|
+
includeTwitter?: boolean;
|
|
121
131
|
/**
|
|
122
132
|
* Font subsets to download when resolving missing font families via fontless.
|
|
123
133
|
*
|
package/dist/module.d.ts
CHANGED
|
@@ -118,6 +118,16 @@ interface ModuleOptions {
|
|
|
118
118
|
* @default 60 * 60 * 24 * 3 (3 days)
|
|
119
119
|
*/
|
|
120
120
|
cacheMaxAgeSeconds?: number;
|
|
121
|
+
/**
|
|
122
|
+
* Include Twitter card meta tags (`twitter:card`, `twitter:image`, etc)
|
|
123
|
+
* alongside the Open Graph image tags.
|
|
124
|
+
*
|
|
125
|
+
* Set to `false` to only emit `og:image` tags. When unset, the module respects
|
|
126
|
+
* `seo.automaticTwitterTags` (nuxt-seo-kit) before defaulting to `true`.
|
|
127
|
+
*
|
|
128
|
+
* @default true
|
|
129
|
+
*/
|
|
130
|
+
includeTwitter?: boolean;
|
|
121
131
|
/**
|
|
122
132
|
* Font subsets to download when resolving missing font families via fontless.
|
|
123
133
|
*
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -10,16 +10,17 @@ import 'nuxtseo-shared/kit';
|
|
|
10
10
|
import 'pathe';
|
|
11
11
|
import 'pkg-types';
|
|
12
12
|
import 'std-env';
|
|
13
|
-
export { n as default } from './shared/nuxt-og-image.
|
|
13
|
+
export { n as default } from './shared/nuxt-og-image.D936Fp6O.mjs';
|
|
14
14
|
import '../dist/runtime/logger.js';
|
|
15
|
+
import 'magic-string';
|
|
15
16
|
import 'oxc-walker';
|
|
17
|
+
import 'exsolve';
|
|
16
18
|
import 'chrome-launcher';
|
|
17
19
|
import 'nypm';
|
|
18
20
|
import 'nuxtseo-shared/devtools';
|
|
19
21
|
import 'unstorage';
|
|
20
22
|
import 'unstorage/drivers/fs-lite';
|
|
21
23
|
import '../dist/runtime/server/og-image/utils/css.js';
|
|
22
|
-
import 'magic-string';
|
|
23
24
|
import 'strip-literal';
|
|
24
25
|
import 'ufo';
|
|
25
26
|
import 'unplugin';
|
|
@@ -27,7 +28,6 @@ import '@vue/compiler-sfc';
|
|
|
27
28
|
import 'ofetch';
|
|
28
29
|
import 'ultrahtml';
|
|
29
30
|
import '../dist/runtime/server/og-image/core/transforms/emojis/emoji-utils.js';
|
|
30
|
-
import 'exsolve';
|
|
31
31
|
import './shared/nuxt-og-image.CJa2KCie.mjs';
|
|
32
32
|
import 'magicast';
|
|
33
33
|
import 'magicast/helpers';
|
|
@@ -63,6 +63,7 @@ export function clientProcessOgImageOptions(input, route, basePath) {
|
|
|
63
63
|
const baseURL = rc.app.baseURL;
|
|
64
64
|
const publicCfg = rc.public?.["nuxt-og-image"] || {};
|
|
65
65
|
const defaults = publicCfg.defaults || {};
|
|
66
|
+
const metaOptions = { includeTwitter: publicCfg.includeTwitter };
|
|
66
67
|
const paths = [];
|
|
67
68
|
for (const rawInput of inputs) {
|
|
68
69
|
const resolved = resolveReactiveOptions(rawInput);
|
|
@@ -78,13 +79,13 @@ export function clientProcessOgImageOptions(input, route, basePath) {
|
|
|
78
79
|
const ogKey = validOptions.key || "og";
|
|
79
80
|
if (validOptions.url) {
|
|
80
81
|
const url = validOptions.url;
|
|
81
|
-
registerClientOgHead(ogKey, { meta: generateMeta(url, validOptions) }, { tagPriority: "high" });
|
|
82
|
+
registerClientOgHead(ogKey, { meta: generateMeta(url, validOptions, metaOptions) }, { tagPriority: "high" });
|
|
82
83
|
paths.push(url);
|
|
83
84
|
continue;
|
|
84
85
|
}
|
|
85
86
|
if (publicCfg.hasServerRuntime) {
|
|
86
87
|
const finalUrl2 = buildResolverUrl(baseURL, basePath, ogKey, route.query);
|
|
87
|
-
registerClientOgHead(ogKey, { meta: generateMeta(finalUrl2, validOptions) }, { tagPriority: 35 });
|
|
88
|
+
registerClientOgHead(ogKey, { meta: generateMeta(finalUrl2, validOptions, metaOptions) }, { tagPriority: 35 });
|
|
88
89
|
paths.push(finalUrl2);
|
|
89
90
|
continue;
|
|
90
91
|
}
|
|
@@ -98,7 +99,7 @@ export function clientProcessOgImageOptions(input, route, basePath) {
|
|
|
98
99
|
const result = buildOgImageUrl(urlOpts, extension, true, defaults, void 0);
|
|
99
100
|
const resolvedUrl = joinURL("/", baseURL, result.url);
|
|
100
101
|
const finalUrl = opts._query && Object.keys(opts._query).length ? withQuery(resolvedUrl, { _query: opts._query }) : resolvedUrl;
|
|
101
|
-
registerClientOgHead(ogKey, { meta: generateMeta(finalUrl, opts) }, { processTemplateParams: true, tagPriority: 35 });
|
|
102
|
+
registerClientOgHead(ogKey, { meta: generateMeta(finalUrl, opts, metaOptions) }, { processTemplateParams: true, tagPriority: 35 });
|
|
102
103
|
paths.push(finalUrl);
|
|
103
104
|
}
|
|
104
105
|
return paths;
|
|
@@ -53,7 +53,8 @@ export function setHeadOgImagePrebuilt(input) {
|
|
|
53
53
|
const url = input.url;
|
|
54
54
|
if (!url)
|
|
55
55
|
return;
|
|
56
|
-
const
|
|
56
|
+
const { includeTwitter } = useOgImageRuntimeConfig();
|
|
57
|
+
const meta = generateMeta(url, input, { includeTwitter });
|
|
57
58
|
useHead({ meta }, { tagPriority: "high" });
|
|
58
59
|
}
|
|
59
60
|
export function createOgImageMeta(src, input, ssrContext, pagePath, head) {
|
|
@@ -117,7 +118,7 @@ export function createOgImageMeta(src, input, ssrContext, pagePath, head) {
|
|
|
117
118
|
prerenderPaths.set(ogKey, (finalUrl.split("?")[0] || finalUrl).replace(/,/g, "%2C"));
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
|
-
return generateMeta(finalUrl, opts);
|
|
121
|
+
return generateMeta(finalUrl, opts, { includeTwitter: ogImageConfig.includeTwitter });
|
|
121
122
|
});
|
|
122
123
|
}
|
|
123
124
|
}, {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
readonly then: <TResult1 = unknown, TResult2 = never>(onfulfilled?: ((value: unknown) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>;
|
|
3
|
+
};
|
|
4
|
+
export default _default;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import wrapHarfBuzz from "#og-image/harfbuzz-adapter";
|
|
2
|
+
import callbackModules from "#og-image/harfbuzz-callbacks";
|
|
3
|
+
import createHarfBuzz from "#og-image/harfbuzz-factory";
|
|
4
|
+
function loadHarfBuzz() {
|
|
5
|
+
return import("#og-image/harfbuzz-wasm").then(async ({ default: importedWasm }) => {
|
|
6
|
+
const wasm = await importedWasm;
|
|
7
|
+
const callbacks = callbackModules && Object.fromEntries(await Promise.all(
|
|
8
|
+
Object.entries(callbackModules).map(async ([signature, module]) => [signature, await module])
|
|
9
|
+
));
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
createHarfBuzz({
|
|
12
|
+
...callbacks && {
|
|
13
|
+
convertJsFunctionToWasm(callback, signature) {
|
|
14
|
+
return new WebAssembly.Instance(callbacks[signature], { e: { f: callback } }).exports.f;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
instantiateWasm(imports, receiveInstance) {
|
|
18
|
+
Promise.resolve().then(() => typeof wasm === "function" ? wasm(imports) : new WebAssembly.Instance(wasm, imports)).then((result) => {
|
|
19
|
+
receiveInstance("instance" in result ? result.instance : result);
|
|
20
|
+
}).catch(reject);
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}).then(wrapHarfBuzz).then(resolve, reject);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
let runtime;
|
|
28
|
+
export default {
|
|
29
|
+
get then() {
|
|
30
|
+
const promise = runtime ||= loadHarfBuzz();
|
|
31
|
+
return promise.then.bind(promise);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import type { RuntimeFontConfig } from '../../types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Both Satori and Takumi pick the first loaded font file for a given family
|
|
6
|
-
* name and don't fall back to other font files for missing glyphs. When CJK
|
|
7
|
-
* fonts like "Noto Sans SC" are split into 100+ unicode-range subsets by
|
|
8
|
-
* fontsource, each covering ~200 characters, this means only one subset's
|
|
9
|
-
* glyphs render while the rest show as .notdef boxes.
|
|
10
|
-
*
|
|
11
|
-
* Fix: rename each subset to "Family__N" and use font-family fallback chains
|
|
12
|
-
* so renderers try each subset in order per character.
|
|
3
|
+
* Give subsets stable names so renderers can fall back between their glyphs.
|
|
4
|
+
* Renderers cache registrations by name across renders with different filtered subsets.
|
|
13
5
|
*/
|
|
14
6
|
export declare function renameSubsetFonts(fonts: RuntimeFontConfig[]): RuntimeFontConfig[];
|
|
15
7
|
/**
|
|
16
8
|
* Build a mapping from original family names to their renamed subset chain.
|
|
17
|
-
* E.g., "Noto Sans SC" → ["Noto Sans
|
|
9
|
+
* E.g., "Noto Sans SC" → ["Noto Sans SC__<hash-a>", "Noto Sans SC__<hash-b>", ...]
|
|
18
10
|
*/
|
|
19
11
|
export declare function buildSubsetFamilyChain(fonts: RuntimeFontConfig[]): Map<string, string[]>;
|
|
20
12
|
/**
|
|
@@ -1,44 +1,67 @@
|
|
|
1
|
+
import { fnv1a64Base36 } from "fnv1a-64";
|
|
1
2
|
export function renameSubsetFonts(fonts) {
|
|
2
3
|
const groups = /* @__PURE__ */ new Map();
|
|
3
|
-
for (const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
arr.push(f);
|
|
4
|
+
for (const font of fonts) {
|
|
5
|
+
const members = groups.get(font.family);
|
|
6
|
+
if (members)
|
|
7
|
+
members.push(font);
|
|
8
8
|
else
|
|
9
|
-
groups.set(
|
|
9
|
+
groups.set(font.family, [font]);
|
|
10
10
|
}
|
|
11
|
-
const
|
|
12
|
-
let changed = false;
|
|
11
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
13
12
|
for (const members of groups.values()) {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const faces = /* @__PURE__ */ new Map();
|
|
14
|
+
for (const font of members) {
|
|
15
|
+
const face = `${font.weight}\0${font.style}`;
|
|
16
|
+
const subsets = faces.get(face);
|
|
17
|
+
if (subsets)
|
|
18
|
+
subsets.push(font);
|
|
19
|
+
else
|
|
20
|
+
faces.set(face, [font]);
|
|
18
21
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const needsRename = members.some((font) => font.unicodeRange) || [...faces.values()].some((subsets) => new Set(subsets.map((font) => font.cacheKey)).size > 1);
|
|
23
|
+
if (!needsRename)
|
|
24
|
+
continue;
|
|
25
|
+
const slots = [];
|
|
26
|
+
for (const subsets of faces.values()) {
|
|
27
|
+
const binaries = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const font of subsets) {
|
|
29
|
+
const source = font.src || font.localPath || font.cacheKey;
|
|
30
|
+
const entries = binaries.get(source);
|
|
31
|
+
if (entries)
|
|
32
|
+
entries.push(font);
|
|
33
|
+
else
|
|
34
|
+
binaries.set(source, [font]);
|
|
35
|
+
}
|
|
36
|
+
const ordered = [...binaries.values()].sort((a, b) => {
|
|
37
|
+
const left = `${a[0].unicodeRange || ""}\0${a[0].src || a[0].localPath || a[0].cacheKey}`;
|
|
38
|
+
const right = `${b[0].unicodeRange || ""}\0${b[0].src || b[0].localPath || b[0].cacheKey}`;
|
|
39
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
26
40
|
});
|
|
41
|
+
for (const [index, entries] of ordered.entries())
|
|
42
|
+
(slots[index] ||= []).push(...entries);
|
|
43
|
+
}
|
|
44
|
+
for (const slot of slots) {
|
|
45
|
+
const identities = [...new Set(slot.map((font) => JSON.stringify([
|
|
46
|
+
font.weight,
|
|
47
|
+
font.style,
|
|
48
|
+
font.src || font.localPath || font.cacheKey
|
|
49
|
+
])))].sort();
|
|
50
|
+
const alias = `${slot[0].family}__${fnv1a64Base36(JSON.stringify(identities))}`;
|
|
51
|
+
for (const font of slot)
|
|
52
|
+
aliases.set(font, alias);
|
|
27
53
|
}
|
|
28
54
|
}
|
|
29
|
-
return
|
|
55
|
+
return aliases.size ? fonts.map((font) => aliases.has(font) ? { ...font, originalFamily: font.originalFamily || font.family, family: aliases.get(font) } : font) : fonts;
|
|
30
56
|
}
|
|
31
57
|
export function buildSubsetFamilyChain(fonts) {
|
|
32
58
|
const chains = /* @__PURE__ */ new Map();
|
|
33
59
|
for (const f of fonts) {
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
const arr = chains.get(f.originalFamily);
|
|
37
|
-
if (arr)
|
|
38
|
-
arr.push(f.family);
|
|
39
|
-
else
|
|
40
|
-
chains.set(f.originalFamily, [f.family]);
|
|
60
|
+
if (f.originalFamily)
|
|
61
|
+
chains.set(f.originalFamily, []);
|
|
41
62
|
}
|
|
63
|
+
for (const f of fonts)
|
|
64
|
+
chains.get(f.originalFamily || f.family)?.push(f.family);
|
|
42
65
|
return chains;
|
|
43
66
|
}
|
|
44
67
|
export function resolveSubsetChain(family, chains) {
|
|
@@ -51,7 +51,7 @@ export interface LoadFontsForRendererOptions extends LoadFontsOptions {
|
|
|
51
51
|
*
|
|
52
52
|
* When a font family has multiple unicode-range subsets loaded (e.g., CJK fonts
|
|
53
53
|
* split into many small chunks), renames each subset to a unique family name
|
|
54
|
-
* (e.g., "Noto Sans
|
|
54
|
+
* (e.g., "Noto Sans SC__<stable hash per binary>") so renderers use font-family
|
|
55
55
|
* fallback chains for per-character glyph coverage. Without this, both Satori
|
|
56
56
|
* and Takumi pick the first font file and show .notdef for characters in other subsets.
|
|
57
57
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createError, getRequestHost, H3Error, setHeader } from "#nuxtseo/h3";
|
|
1
|
+
import { appendResponseHeader, createError, getRequestHost, H3Error, setHeader } from "#nuxtseo/h3";
|
|
2
2
|
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
3
3
|
import { logger } from "../../logger.js";
|
|
4
4
|
import { getBuildCachedImage, setBuildCachedImage } from "../og-image/cache/buildCache.js";
|
|
@@ -22,8 +22,8 @@ export async function imageEventHandler(e) {
|
|
|
22
22
|
} finally {
|
|
23
23
|
timings.record("total", performance.now() - reqStart);
|
|
24
24
|
const header = timings.header();
|
|
25
|
-
if (header)
|
|
26
|
-
|
|
25
|
+
if (header && !e.node?.res?.headersSent)
|
|
26
|
+
appendResponseHeader(e, "Server-Timing", header);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
async function renderOgImage(e, ctx) {
|
|
@@ -18,6 +18,7 @@ const RE_HASH_SEGMENT = /^o_([a-z0-9]+)$/i;
|
|
|
18
18
|
const RE_COMMA_PARAM_SEPARATOR = /,(?=\w+_)/;
|
|
19
19
|
const RE_SIGNATURE_SUFFIX = /,s_[^,]+$/;
|
|
20
20
|
const RE_NON_ASCII = /[^\u0000-\u007F]/;
|
|
21
|
+
const RE_WINDOWS_RESERVED_FILENAME_CHARACTERS = /[<>:"/\\|?*\u0000-\u001F]/;
|
|
21
22
|
const PARAM_ALIASES = {
|
|
22
23
|
w: "width",
|
|
23
24
|
h: "height",
|
|
@@ -149,7 +150,7 @@ export function encodeOgImageParams(options, defaults) {
|
|
|
149
150
|
} else {
|
|
150
151
|
const escaped = str.startsWith("~") ? `~${str}` : str;
|
|
151
152
|
const encoded = encodeURIComponent(escaped.replace(RE_UNDERSCORE, "__")).replace(RE_PERCENT20, "+");
|
|
152
|
-
if (encoded.includes("%")) {
|
|
153
|
+
if (encoded.includes("%") || RE_WINDOWS_RESERVED_FILENAME_CHARACTERS.test(str)) {
|
|
153
154
|
parts.push(`${alias}_~${b64Encode(str)}`);
|
|
154
155
|
} else {
|
|
155
156
|
parts.push(`${alias}_${encoded}`);
|
package/dist/runtime/shared.d.ts
CHANGED
|
@@ -2,7 +2,16 @@ import type { ResolvableMeta } from '@unhead/vue';
|
|
|
2
2
|
import type { OgImageOptions, OgImagePrebuilt } from './types.js';
|
|
3
3
|
export { extractSocialPreviewTags, toBase64Image } from './pure.js';
|
|
4
4
|
export { buildOgImageUrl, decodeOgImageParams, encodeOgImageParams, extractEncodedSegment, hashOgImageOptions, parseOgImageUrl, signEncodedParams, verifyOgImageSignature } from './shared/urlEncoding.js';
|
|
5
|
-
export
|
|
5
|
+
export interface GenerateMetaOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Emit Twitter card meta tags (`twitter:card`, `twitter:image`, etc) alongside
|
|
8
|
+
* the Open Graph image tags.
|
|
9
|
+
*
|
|
10
|
+
* @default true
|
|
11
|
+
*/
|
|
12
|
+
includeTwitter?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function generateMeta(url: OgImagePrebuilt['url'] | string, resolvedOptions: OgImageOptions | OgImagePrebuilt, metaOptions?: GenerateMetaOptions): ResolvableMeta[];
|
|
6
15
|
export declare function isInternalRoute(path: string): boolean;
|
|
7
16
|
export declare function separateProps(options: OgImageOptions | undefined, ignoreKeys?: string[]): OgImageOptions;
|
|
8
17
|
/**
|
package/dist/runtime/shared.js
CHANGED
|
@@ -3,10 +3,10 @@ import { toValue } from "vue";
|
|
|
3
3
|
export { extractSocialPreviewTags, toBase64Image } from "./pure.js";
|
|
4
4
|
export { buildOgImageUrl, decodeOgImageParams, encodeOgImageParams, extractEncodedSegment, hashOgImageOptions, parseOgImageUrl, signEncodedParams, verifyOgImageSignature } from "./shared/urlEncoding.js";
|
|
5
5
|
const RE_KEBAB_CASE = /-([a-z])/g;
|
|
6
|
-
export function generateMeta(url, resolvedOptions) {
|
|
6
|
+
export function generateMeta(url, resolvedOptions, metaOptions = {}) {
|
|
7
7
|
const key = resolvedOptions.key || "og";
|
|
8
8
|
const isTwitterOnly = key === "twitter";
|
|
9
|
-
const includeTwitter = key === "og" || key === "twitter";
|
|
9
|
+
const includeTwitter = (key === "og" || key === "twitter") && metaOptions.includeTwitter !== false;
|
|
10
10
|
const meta = [];
|
|
11
11
|
if (includeTwitter) {
|
|
12
12
|
meta.push({ name: "twitter:card", content: "summary_large_image" });
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface OgImageRuntimeConfig {
|
|
|
37
37
|
publicStoragePath: string;
|
|
38
38
|
defaults: Omit<OgImageOptions, 'component' | 'renderer' | 'props' | 'url' | 'html' | 'key' | 'cacheKey' | '_query' | '_hash' | 'socialPreview'>;
|
|
39
39
|
debug: boolean;
|
|
40
|
+
/** Whether twitter:* meta tags are emitted alongside og:image tags */
|
|
41
|
+
includeTwitter: boolean;
|
|
40
42
|
baseCacheKey: string;
|
|
41
43
|
hasNuxtIcon: boolean;
|
|
42
44
|
hasNuxtContent?: boolean;
|