vlite3 1.4.16 → 1.4.18
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/components/CategoryManager/CategoryManager.vue2.js +2 -2
- package/components/Invoice/InvoiceVariant1.vue.js +8 -8
- package/components/Invoice/InvoiceVariant2.vue.js +9 -9
- package/components/Invoice/InvoiceVariant3.vue.js +7 -7
- package/components/Invoice/InvoiceVariant4.vue.js +56 -56
- package/components/Invoice/types.d.ts +2 -0
- package/components/QRCode/QRCode.vue.d.ts +4 -10
- package/components/QRCode/QRCode.vue.js +45 -15
- package/components/QRCode/index.d.ts +1 -0
- package/components/QRCode/types.d.ts +14 -0
- package/components/RichTextEditor/RichTextEditor.vue.js +4 -4
- package/components/RichTextEditor/RichTextLinkPopover.vue3.js +2 -2
- package/components/RichTextEditor/RichTextToolbar.vue3.js +2 -2
- package/components/ScaleGenerator/ScaleGenerator.vue.d.ts +1 -1
- package/components/Screen/ScreenFilter.vue.js +2 -2
- package/components/SeoProvider/SeoProvider.vue.d.ts +70 -0
- package/components/SeoProvider/SeoProvider.vue.js +52 -0
- package/components/SeoProvider/SeoProvider.vue2.js +4 -0
- package/components/SeoProvider/domAdapter.d.ts +6 -0
- package/components/SeoProvider/domAdapter.js +68 -0
- package/components/SeoProvider/index.d.ts +4 -0
- package/components/SeoProvider/normalizeSeo.d.ts +61 -0
- package/components/SeoProvider/normalizeSeo.js +195 -0
- package/components/SeoProvider/types.d.ts +146 -0
- package/components/ThemeToggle.vue.d.ts +1 -1
- package/composables/useSeo.d.ts +34 -0
- package/composables/useSeo.js +28 -0
- package/index.d.ts +2 -0
- package/index.js +230 -219
- package/package.json +1 -1
- /package/components/RichTextEditor/{RichTextLinkPopover.vue.js → RichTextLinkPopover.vue2.js} +0 -0
- /package/components/RichTextEditor/{RichTextToolbar.vue.js → RichTextToolbar.vue2.js} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { defineComponent as te, ref as v, shallowRef as ne, watch as oe, computed as h, onMounted as le, onUnmounted as ae, openBlock as C, createElementBlock as L, normalizeClass as re, toDisplayString as I, createCommentVNode as U, createElementVNode as w, createVNode as q, unref as a, normalizeStyle as ie } from "vue";
|
|
2
2
|
/* empty css */
|
|
3
|
-
import se from "./RichTextToolbar.
|
|
4
|
-
/* empty css
|
|
5
|
-
import de from "./RichTextLinkPopover.
|
|
6
|
-
/* empty css
|
|
3
|
+
import se from "./RichTextToolbar.vue2.js";
|
|
4
|
+
/* empty css */
|
|
5
|
+
import de from "./RichTextLinkPopover.vue2.js";
|
|
6
|
+
/* empty css */
|
|
7
7
|
import { useRichTextImageUpload as ue } from "./composables/useRichTextImageUpload.js";
|
|
8
8
|
import { useRichTextLinks as ce } from "./composables/useRichTextLinks.js";
|
|
9
9
|
const fe = ["aria-labelledby", "aria-describedby"], me = ["id", "for"], ve = { class: "rte-wrapper" }, ye = { class: "rte-body-wrap" }, ge = ["id", "contenteditable", "aria-label", "aria-readonly", "aria-disabled", "aria-invalid"], be = ["id"], Se = /* @__PURE__ */ te({
|
|
@@ -25,10 +25,10 @@ declare const __VLS_component: import('vue').DefineComponent<ScaleGeneratorProps
|
|
|
25
25
|
maxWidth: number;
|
|
26
26
|
contentClass: string;
|
|
27
27
|
enabled: boolean;
|
|
28
|
+
scale: number | null;
|
|
28
29
|
targetWidth: number;
|
|
29
30
|
minScale: number;
|
|
30
31
|
maxScale: number;
|
|
31
|
-
scale: number | null;
|
|
32
32
|
isEditMode: boolean;
|
|
33
33
|
horizontalAlign: ScaleHorizontalAlign;
|
|
34
34
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { SeoAdapter, SeoData, SeoPayload } from './types';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/**
|
|
4
|
+
* Site-level baseline. Anything here is used as a
|
|
5
|
+
* last-resort fallback before the normalizer returns
|
|
6
|
+
* empty strings. Usually mapped from the store's
|
|
7
|
+
* `pagesSeo.home`, `assets.openGraphImage`, `assets.logo`,
|
|
8
|
+
* `assets.favicon`, `storeName`, `customDomain`, etc.
|
|
9
|
+
*/
|
|
10
|
+
defaults?: Partial<SeoData> | null;
|
|
11
|
+
/**
|
|
12
|
+
* Domain-level fallback. Typically resolved from
|
|
13
|
+
* `pagesSeo[pageType]` (the shop does this). Applied
|
|
14
|
+
* **after** `defaults` and **before** the active page's
|
|
15
|
+
* `useSeo` context.
|
|
16
|
+
*/
|
|
17
|
+
fallback?: Partial<SeoData> | null;
|
|
18
|
+
/**
|
|
19
|
+
* Current page key. Defaults to whatever the most recent
|
|
20
|
+
* `useSeo({ pageType })` call supplied.
|
|
21
|
+
*/
|
|
22
|
+
pageType?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Global replacements merged into every `useSeo({…})` call.
|
|
25
|
+
* The shop uses this to inject `{{storeName}}` once.
|
|
26
|
+
*/
|
|
27
|
+
globalReplacements?: Record<string, string | number | null | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* The actual emitter. Defaults to the DOM adapter so
|
|
30
|
+
* non-Nuxt hosts get a working document head out of the
|
|
31
|
+
* box. Nuxt apps should pass a Nuxt-specific adapter for
|
|
32
|
+
* SSR-safe meta.
|
|
33
|
+
*/
|
|
34
|
+
adapter?: SeoAdapter | null;
|
|
35
|
+
/**
|
|
36
|
+
* Optional list of page-type → fallback `SeoData` entries
|
|
37
|
+
* baked in. Mirrors the `pagesSeo[pageType]` map from the
|
|
38
|
+
* shop. Provided as a convenience for hosts that don't
|
|
39
|
+
* compute the fallback themselves.
|
|
40
|
+
*/
|
|
41
|
+
pageFallbacks?: Record<string, Partial<SeoData>> | null;
|
|
42
|
+
};
|
|
43
|
+
declare function __VLS_template(): {
|
|
44
|
+
attrs: Partial<{}>;
|
|
45
|
+
slots: {
|
|
46
|
+
default?(_: {}): any;
|
|
47
|
+
};
|
|
48
|
+
refs: {};
|
|
49
|
+
rootEl: any;
|
|
50
|
+
};
|
|
51
|
+
type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
|
|
52
|
+
declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
|
|
53
|
+
update: (payload: SeoPayload) => any;
|
|
54
|
+
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
55
|
+
onUpdate?: (payload: SeoPayload) => any;
|
|
56
|
+
}>, {
|
|
57
|
+
fallback: Partial<SeoData> | null;
|
|
58
|
+
defaults: Partial<SeoData> | null;
|
|
59
|
+
pageType: string;
|
|
60
|
+
globalReplacements: Record<string, string | number | null | undefined>;
|
|
61
|
+
adapter: SeoAdapter | null;
|
|
62
|
+
pageFallbacks: Record<string, Partial<SeoData>> | null;
|
|
63
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
|
64
|
+
declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
|
|
65
|
+
export default _default;
|
|
66
|
+
type __VLS_WithTemplateSlots<T, S> = T & {
|
|
67
|
+
new (): {
|
|
68
|
+
$slots: S;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { defineComponent as m, ref as v, provide as g, computed as l, watch as y, renderSlot as b } from "vue";
|
|
2
|
+
import { normalizeSeo as T } from "./normalizeSeo.js";
|
|
3
|
+
import { createDomSeoAdapter as k } from "./domAdapter.js";
|
|
4
|
+
import { SEO_CONTEXT_KEY as _ } from "../../composables/useSeo.js";
|
|
5
|
+
const x = /* @__PURE__ */ m({
|
|
6
|
+
__name: "SeoProvider",
|
|
7
|
+
props: {
|
|
8
|
+
defaults: { default: null },
|
|
9
|
+
fallback: { default: null },
|
|
10
|
+
pageType: { default: void 0 },
|
|
11
|
+
globalReplacements: { default: void 0 },
|
|
12
|
+
adapter: { default: null },
|
|
13
|
+
pageFallbacks: { default: null }
|
|
14
|
+
},
|
|
15
|
+
emits: ["update"],
|
|
16
|
+
setup(p, { emit: s }) {
|
|
17
|
+
const o = p, i = s, n = v(/* @__PURE__ */ new Map());
|
|
18
|
+
g(_, n);
|
|
19
|
+
const d = l(() => o.adapter ?? k()), r = l(() => {
|
|
20
|
+
const t = Array.from(n.value.values());
|
|
21
|
+
if (t.length === 0) return null;
|
|
22
|
+
const a = {};
|
|
23
|
+
for (const e of t)
|
|
24
|
+
e.pageType && (a.pageType = e.pageType), e.title !== void 0 && (a.title = e.title), e.description !== void 0 && (a.description = e.description), e.keywords !== void 0 && (a.keywords = e.keywords), e.image !== void 0 && (a.image = e.image), e.canonicalUrl !== void 0 && (a.canonicalUrl = e.canonicalUrl), e.robots !== void 0 && (a.robots = e.robots), e.overrides && (a.overrides = { ...a.overrides ?? {}, ...e.overrides }), e.replacements && (a.replacements = { ...a.replacements ?? {}, ...e.replacements });
|
|
25
|
+
return a;
|
|
26
|
+
}), c = l(() => {
|
|
27
|
+
const t = r.value?.pageType ?? o.pageType ?? "home";
|
|
28
|
+
return o.pageFallbacks && o.pageFallbacks[t] ? o.pageFallbacks[t] : o.fallback ?? null;
|
|
29
|
+
}), u = l(
|
|
30
|
+
() => r.value?.pageType ?? o.pageType ?? "home"
|
|
31
|
+
), f = l(() => {
|
|
32
|
+
const t = {
|
|
33
|
+
defaults: o.defaults,
|
|
34
|
+
context: r.value,
|
|
35
|
+
fallback: c.value,
|
|
36
|
+
pageType: u.value,
|
|
37
|
+
globalReplacements: o.globalReplacements
|
|
38
|
+
};
|
|
39
|
+
return T(t);
|
|
40
|
+
});
|
|
41
|
+
return y(
|
|
42
|
+
f,
|
|
43
|
+
(t) => {
|
|
44
|
+
d.value.apply(t), i("update", t);
|
|
45
|
+
},
|
|
46
|
+
{ immediate: !0, deep: !0 }
|
|
47
|
+
), (t, a) => b(t.$slots, "default");
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
export {
|
|
51
|
+
x as default
|
|
52
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const m = () => typeof document < "u" && typeof document.head < "u", a = (o, s, e, t) => {
|
|
2
|
+
let i = o.head.querySelector(
|
|
3
|
+
`meta[${s}="${e}"]`
|
|
4
|
+
);
|
|
5
|
+
return i || (i = o.createElement("meta"), i.setAttribute(s, e), o.head.appendChild(i)), i.setAttribute("content", t), i;
|
|
6
|
+
}, p = (o, s, e) => {
|
|
7
|
+
const t = o.head.querySelector(`meta[${s}="${e}"]`);
|
|
8
|
+
t && t.remove();
|
|
9
|
+
}, g = (o, s, e, t = {}) => {
|
|
10
|
+
let i = o.head.querySelector(
|
|
11
|
+
`link[rel="${s}"]${t.sizes ? `[sizes="${t.sizes}"]` : ""}`
|
|
12
|
+
);
|
|
13
|
+
if (!i) {
|
|
14
|
+
i = o.createElement("link"), i.setAttribute("rel", s);
|
|
15
|
+
for (const [n, r] of Object.entries(t))
|
|
16
|
+
r && i.setAttribute(n, r);
|
|
17
|
+
o.head.appendChild(i);
|
|
18
|
+
}
|
|
19
|
+
return i.setAttribute("href", e), i;
|
|
20
|
+
}, f = (o, s) => {
|
|
21
|
+
const e = o.head.querySelector(`link[rel="${s}"]`);
|
|
22
|
+
e && e.remove();
|
|
23
|
+
}, u = () => {
|
|
24
|
+
const o = [], s = [];
|
|
25
|
+
return {
|
|
26
|
+
apply(e) {
|
|
27
|
+
if (!m()) return;
|
|
28
|
+
const t = document, i = e.titleTemplate ? e.titleTemplate.includes("%s") ? e.titleTemplate.replace("%s", e.title) : `${e.title} · ${e.titleTemplate}` : e.title;
|
|
29
|
+
i && (t.title = i);
|
|
30
|
+
const n = (r, c, l) => {
|
|
31
|
+
l && l.length > 0 ? (a(t, r, c, l), o.push({ attribute: r, key: c })) : p(t, r, c);
|
|
32
|
+
};
|
|
33
|
+
if (n("name", "description", e.description), n("name", "keywords", e.keywords), n("name", "robots", e.robots), n("property", "og:title", e.ogTitle), n("property", "og:description", e.ogDescription), n("property", "og:image", e.ogImage), n("property", "og:image:alt", e.ogImageAlt), n("property", "og:url", e.ogUrl), n("property", "og:type", e.ogType), n("property", "og:site_name", e.ogSiteName), n("property", "og:locale", e.ogLocale), n("name", "twitter:card", e.twitterCard), n("name", "twitter:title", e.twitterTitle), n("name", "twitter:description", e.twitterDescription), n("name", "twitter:image", e.twitterImage), n("name", "twitter:site", e.twitterSite), n("name", "twitter:creator", e.twitterCreator), e.canonicalUrl) {
|
|
34
|
+
let r = t.head.querySelector('link[rel="canonical"]');
|
|
35
|
+
r || (r = t.createElement("link"), r.setAttribute("rel", "canonical"), t.head.appendChild(r)), r.setAttribute("href", e.canonicalUrl), s.push({ rel: "canonical" });
|
|
36
|
+
} else
|
|
37
|
+
f(t, "canonical");
|
|
38
|
+
if (e.favicon && (g(t, "icon", e.favicon), s.push({ rel: "icon" })), e.jsonLd) {
|
|
39
|
+
let r = t.head.querySelector(
|
|
40
|
+
'script[type="application/ld+json"][data-vlite3-seo]'
|
|
41
|
+
);
|
|
42
|
+
r || (r = t.createElement("script"), r.setAttribute("type", "application/ld+json"), r.setAttribute("data-vlite3-seo", ""), t.head.appendChild(r)), r.textContent = JSON.stringify(e.jsonLd);
|
|
43
|
+
} else {
|
|
44
|
+
const r = t.head.querySelector(
|
|
45
|
+
'script[type="application/ld+json"][data-vlite3-seo]'
|
|
46
|
+
);
|
|
47
|
+
r && r.remove();
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
reset() {
|
|
51
|
+
if (!m()) return;
|
|
52
|
+
const e = document;
|
|
53
|
+
for (const { attribute: i, key: n } of o)
|
|
54
|
+
p(e, i, n);
|
|
55
|
+
o.length = 0;
|
|
56
|
+
for (const { rel: i } of s)
|
|
57
|
+
f(e, i);
|
|
58
|
+
s.length = 0;
|
|
59
|
+
const t = e.head.querySelector(
|
|
60
|
+
'script[type="application/ld+json"][data-vlite3-seo]'
|
|
61
|
+
);
|
|
62
|
+
t && t.remove();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
export {
|
|
67
|
+
u as createDomSeoAdapter
|
|
68
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as SeoProvider } from './SeoProvider.vue';
|
|
2
|
+
export { normalizeSeo, noopSeoAdapter, replaceSeoTokens, type NormalizeSeoInput, type SeoReplacements, } from './normalizeSeo';
|
|
3
|
+
export { createDomSeoAdapter } from './domAdapter';
|
|
4
|
+
export type { SeoData, SeoPayload, SeoContextInput, SeoAdapter, } from './types';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { SeoAdapter, SeoContextInput, SeoData, SeoPayload } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The two token replacement scopes. Global tokens are always
|
|
4
|
+
* applied (e.g. `{{storeName}}`); per-call tokens are supplied
|
|
5
|
+
* by the page (`useSeo({ replacements: { productName } })`).
|
|
6
|
+
*/
|
|
7
|
+
export type SeoReplacements = Record<string, string | number | null | undefined>;
|
|
8
|
+
/**
|
|
9
|
+
* Replace `{{key}}` occurrences in `value` using the supplied
|
|
10
|
+
* replacements. Unknown / null / undefined keys collapse to an
|
|
11
|
+
* empty string (matches the existing `shop-data` behaviour so
|
|
12
|
+
* SEO strings with `{{productName}}` still resolve to a valid
|
|
13
|
+
* meta tag when the page is missing the data).
|
|
14
|
+
*/
|
|
15
|
+
export declare const replaceSeoTokens: (value: string | null | undefined, replacements?: SeoReplacements) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Inputs to the normalizer.
|
|
18
|
+
*
|
|
19
|
+
* `defaults` is the **store / site-level** baseline (logo,
|
|
20
|
+
* favicon, OG image, store name, …). `context` is the **page-
|
|
21
|
+
* level** overlay (`useSeo({ … })`). `resolveFallback` lets the
|
|
22
|
+
* caller plug in a domain-specific fallback (e.g. resolve
|
|
23
|
+
* `pagesSeo[pageType]` for the shop). `pageType` is the final
|
|
24
|
+
* string used in the payload (falls back to `'home'`).
|
|
25
|
+
*/
|
|
26
|
+
export interface NormalizeSeoInput {
|
|
27
|
+
defaults?: Partial<SeoData> | null;
|
|
28
|
+
context?: SeoContextInput | null;
|
|
29
|
+
fallback?: Partial<SeoData> | null;
|
|
30
|
+
pageType?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Extra global replacements applied **before** per-context ones.
|
|
33
|
+
* Used by the shop layer to inject `{{storeName}}` automatically.
|
|
34
|
+
*/
|
|
35
|
+
globalReplacements?: SeoReplacements;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the final SEO payload.
|
|
39
|
+
*
|
|
40
|
+
* Precedence (highest wins):
|
|
41
|
+
*
|
|
42
|
+
* 1. `context.overrides` — raw, unrestricted fields from
|
|
43
|
+
* the page (`useSeo({ overrides })`).
|
|
44
|
+
* 2. `context.<field>` — per-page explicit value.
|
|
45
|
+
* 3. `fallback.<field>` — store / domain fallback
|
|
46
|
+
* (e.g. `pagesSeo[pageType]`).
|
|
47
|
+
* 4. `defaults.<field>` — site-level baseline (logo, OG
|
|
48
|
+
* image, site name, …).
|
|
49
|
+
*
|
|
50
|
+
* Empty strings are treated as "use the next layer", matching
|
|
51
|
+
* the existing `resolveStoreSeo` contract. `null` is treated as
|
|
52
|
+
* "explicitly clear" and forces `null` in the output. (Useful
|
|
53
|
+
* for `noindex` overrides etc.)
|
|
54
|
+
*/
|
|
55
|
+
export declare const normalizeSeo: ({ defaults, context, fallback, pageType, globalReplacements, }: NormalizeSeoInput) => SeoPayload;
|
|
56
|
+
/**
|
|
57
|
+
* No-op adapter. Useful as a fallback in tests / SSR-only
|
|
58
|
+
* contexts where SEO is already rendered by the host template
|
|
59
|
+
* and the generic provider should not touch the DOM.
|
|
60
|
+
*/
|
|
61
|
+
export declare const noopSeoAdapter: SeoAdapter;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
const P = /\{\{(\w+)\}\}/g, q = (o, i = {}) => o ? o.replace(P, (e, n) => {
|
|
2
|
+
const d = i[n];
|
|
3
|
+
return d == null ? "" : String(d);
|
|
4
|
+
}) : "", v = (...o) => {
|
|
5
|
+
for (const i of o)
|
|
6
|
+
if (typeof i == "string" && i.length > 0) return i;
|
|
7
|
+
return "";
|
|
8
|
+
}, r = (...o) => {
|
|
9
|
+
for (const i of o) {
|
|
10
|
+
if (typeof i == "string")
|
|
11
|
+
return i.length > 0 ? i : null;
|
|
12
|
+
if (i === null) return null;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}, B = (o, i) => ({
|
|
16
|
+
...o ?? {},
|
|
17
|
+
...i ?? {}
|
|
18
|
+
}), F = ({
|
|
19
|
+
defaults: o,
|
|
20
|
+
context: i,
|
|
21
|
+
fallback: e,
|
|
22
|
+
pageType: n = "home",
|
|
23
|
+
globalReplacements: d
|
|
24
|
+
}) => {
|
|
25
|
+
const c = B(
|
|
26
|
+
d,
|
|
27
|
+
i?.replacements
|
|
28
|
+
), t = (O) => q(O, c), g = t(
|
|
29
|
+
v(
|
|
30
|
+
i?.title ?? void 0,
|
|
31
|
+
e?.title ?? void 0,
|
|
32
|
+
o?.title ?? void 0
|
|
33
|
+
)
|
|
34
|
+
), m = t(
|
|
35
|
+
v(
|
|
36
|
+
i?.description ?? void 0,
|
|
37
|
+
e?.description ?? void 0,
|
|
38
|
+
o?.description ?? void 0
|
|
39
|
+
)
|
|
40
|
+
), T = t(
|
|
41
|
+
v(
|
|
42
|
+
i?.keywords ?? void 0,
|
|
43
|
+
e?.keywords ?? void 0,
|
|
44
|
+
o?.keywords ?? void 0
|
|
45
|
+
)
|
|
46
|
+
), p = r(
|
|
47
|
+
i?.canonicalUrl ?? void 0,
|
|
48
|
+
e?.canonicalUrl ?? void 0,
|
|
49
|
+
o?.canonicalUrl ?? void 0
|
|
50
|
+
), S = r(
|
|
51
|
+
i?.robots ?? void 0,
|
|
52
|
+
e?.robots ?? void 0,
|
|
53
|
+
o?.robots ?? void 0
|
|
54
|
+
), y = r(
|
|
55
|
+
i?.overrides?.favicon ?? void 0,
|
|
56
|
+
e?.favicon ?? void 0,
|
|
57
|
+
o?.favicon ?? void 0
|
|
58
|
+
), s = r(
|
|
59
|
+
i?.image ?? void 0,
|
|
60
|
+
e?.image ?? void 0,
|
|
61
|
+
o?.image ?? void 0
|
|
62
|
+
), I = t(
|
|
63
|
+
v(
|
|
64
|
+
i?.overrides?.ogTitle ?? void 0,
|
|
65
|
+
i?.title ?? void 0,
|
|
66
|
+
e?.ogTitle ?? void 0,
|
|
67
|
+
e?.title ?? void 0,
|
|
68
|
+
o?.ogTitle ?? void 0,
|
|
69
|
+
o?.title ?? void 0,
|
|
70
|
+
g
|
|
71
|
+
)
|
|
72
|
+
), U = t(
|
|
73
|
+
v(
|
|
74
|
+
i?.overrides?.ogDescription ?? void 0,
|
|
75
|
+
i?.description ?? void 0,
|
|
76
|
+
e?.ogDescription ?? void 0,
|
|
77
|
+
e?.description ?? void 0,
|
|
78
|
+
o?.ogDescription ?? void 0,
|
|
79
|
+
o?.description ?? void 0,
|
|
80
|
+
m
|
|
81
|
+
)
|
|
82
|
+
), L = r(
|
|
83
|
+
i?.overrides?.ogImage ?? void 0,
|
|
84
|
+
i?.image ?? void 0,
|
|
85
|
+
e?.ogImage ?? void 0,
|
|
86
|
+
e?.image ?? void 0,
|
|
87
|
+
o?.ogImage ?? void 0,
|
|
88
|
+
s
|
|
89
|
+
), C = r(
|
|
90
|
+
i?.overrides?.ogImageAlt ?? void 0,
|
|
91
|
+
e?.ogImageAlt ?? void 0,
|
|
92
|
+
o?.ogImageAlt ?? void 0
|
|
93
|
+
), D = r(
|
|
94
|
+
i?.overrides?.ogUrl ?? void 0,
|
|
95
|
+
i?.canonicalUrl ?? void 0,
|
|
96
|
+
e?.ogUrl ?? void 0,
|
|
97
|
+
e?.canonicalUrl ?? void 0,
|
|
98
|
+
o?.ogUrl ?? void 0,
|
|
99
|
+
o?.canonicalUrl ?? void 0,
|
|
100
|
+
p
|
|
101
|
+
), N = r(
|
|
102
|
+
i?.overrides?.ogType ?? void 0,
|
|
103
|
+
e?.ogType ?? void 0,
|
|
104
|
+
o?.ogType ?? void 0,
|
|
105
|
+
n === "home" ? "website" : null
|
|
106
|
+
), w = r(
|
|
107
|
+
i?.overrides?.ogSiteName ?? void 0,
|
|
108
|
+
e?.ogSiteName ?? void 0,
|
|
109
|
+
o?.ogSiteName ?? void 0,
|
|
110
|
+
o?.siteName ?? void 0
|
|
111
|
+
), A = r(
|
|
112
|
+
i?.overrides?.ogLocale ?? void 0,
|
|
113
|
+
e?.ogLocale ?? void 0,
|
|
114
|
+
o?.ogLocale ?? void 0
|
|
115
|
+
), h = r(
|
|
116
|
+
i?.overrides?.twitterCard ?? void 0,
|
|
117
|
+
e?.twitterCard ?? void 0,
|
|
118
|
+
o?.twitterCard ?? void 0,
|
|
119
|
+
s ? "summary_large_image" : "summary"
|
|
120
|
+
), j = t(
|
|
121
|
+
v(
|
|
122
|
+
i?.overrides?.twitterTitle ?? void 0,
|
|
123
|
+
i?.title ?? void 0,
|
|
124
|
+
e?.twitterTitle ?? void 0,
|
|
125
|
+
e?.title ?? void 0,
|
|
126
|
+
o?.twitterTitle ?? void 0,
|
|
127
|
+
o?.title ?? void 0,
|
|
128
|
+
g
|
|
129
|
+
)
|
|
130
|
+
), _ = t(
|
|
131
|
+
v(
|
|
132
|
+
i?.overrides?.twitterDescription ?? void 0,
|
|
133
|
+
i?.description ?? void 0,
|
|
134
|
+
e?.twitterDescription ?? void 0,
|
|
135
|
+
e?.description ?? void 0,
|
|
136
|
+
o?.twitterDescription ?? void 0,
|
|
137
|
+
o?.description ?? void 0,
|
|
138
|
+
m
|
|
139
|
+
)
|
|
140
|
+
), E = r(
|
|
141
|
+
i?.overrides?.twitterImage ?? void 0,
|
|
142
|
+
i?.image ?? void 0,
|
|
143
|
+
e?.twitterImage ?? void 0,
|
|
144
|
+
e?.image ?? void 0,
|
|
145
|
+
o?.twitterImage ?? void 0,
|
|
146
|
+
s
|
|
147
|
+
), R = r(
|
|
148
|
+
i?.overrides?.twitterSite ?? void 0,
|
|
149
|
+
e?.twitterSite ?? void 0,
|
|
150
|
+
o?.twitterSite ?? void 0
|
|
151
|
+
), z = r(
|
|
152
|
+
i?.overrides?.twitterCreator ?? void 0,
|
|
153
|
+
e?.twitterCreator ?? void 0,
|
|
154
|
+
o?.twitterCreator ?? void 0
|
|
155
|
+
), H = w ?? o?.siteName ?? null;
|
|
156
|
+
return {
|
|
157
|
+
pageType: n,
|
|
158
|
+
title: g,
|
|
159
|
+
titleTemplate: r(
|
|
160
|
+
i?.overrides?.titleTemplate ?? void 0,
|
|
161
|
+
e?.titleTemplate ?? void 0,
|
|
162
|
+
o?.titleTemplate ?? void 0
|
|
163
|
+
),
|
|
164
|
+
description: m,
|
|
165
|
+
keywords: T,
|
|
166
|
+
canonicalUrl: p,
|
|
167
|
+
robots: S,
|
|
168
|
+
favicon: y,
|
|
169
|
+
image: s,
|
|
170
|
+
ogTitle: I,
|
|
171
|
+
ogDescription: U,
|
|
172
|
+
ogImage: L,
|
|
173
|
+
ogImageAlt: C,
|
|
174
|
+
ogUrl: D,
|
|
175
|
+
ogType: N,
|
|
176
|
+
ogSiteName: w,
|
|
177
|
+
ogLocale: A,
|
|
178
|
+
twitterCard: h,
|
|
179
|
+
twitterTitle: j,
|
|
180
|
+
twitterDescription: _,
|
|
181
|
+
twitterImage: E,
|
|
182
|
+
twitterSite: R,
|
|
183
|
+
twitterCreator: z,
|
|
184
|
+
siteName: H,
|
|
185
|
+
jsonLd: i?.overrides?.jsonLd ?? e?.jsonLd ?? o?.jsonLd ?? null
|
|
186
|
+
};
|
|
187
|
+
}, G = {
|
|
188
|
+
apply: () => {
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
export {
|
|
192
|
+
G as noopSeoAdapter,
|
|
193
|
+
F as normalizeSeo,
|
|
194
|
+
q as replaceSeoTokens
|
|
195
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic, framework-agnostic SEO contract.
|
|
3
|
+
*
|
|
4
|
+
* The goal of this layer is to keep SEO **normalization** (turning
|
|
5
|
+
* "store config + route context + page-specific overrides" into one
|
|
6
|
+
* canonical object) reusable, while letting the **application**
|
|
7
|
+
* (Nuxt, plain Vue + DOM, server-rendered HTML, …) decide **how** the
|
|
8
|
+
* data is actually emitted to the document head.
|
|
9
|
+
*
|
|
10
|
+
* The split is:
|
|
11
|
+
*
|
|
12
|
+
* 1. `SeoData` — the final shape every consumer (Nuxt
|
|
13
|
+
* `useSeoMeta`, Open Graph crawler, plain
|
|
14
|
+
* `<meta>` injection) understands.
|
|
15
|
+
* 2. `SeoAdapter` — a tiny interface the host app provides
|
|
16
|
+
* to apply `SeoData` to the document. In
|
|
17
|
+
* Nuxt this wraps `useSeoMeta` / `useHead`;
|
|
18
|
+
* in a Vue admin preview it mutates
|
|
19
|
+
* `document.head` directly. The provider
|
|
20
|
+
* itself never imports Nuxt or DOM APIs.
|
|
21
|
+
* 3. `useSeo` — a Vue composable for components that
|
|
22
|
+
* want to declare per-page SEO context
|
|
23
|
+
* (page type, dynamic replacements, image).
|
|
24
|
+
* 4. `SeoProvider` — the Vue component that owns the
|
|
25
|
+
* lifecycle: it merges the current page
|
|
26
|
+
* context, runs the normalizer, and calls
|
|
27
|
+
* the adapter on every change.
|
|
28
|
+
*
|
|
29
|
+
* Keeping the layer UI-agnostic means `vlite3` can ship SEO support
|
|
30
|
+
* without forcing every consumer onto Nuxt — and tests can run with
|
|
31
|
+
* a stub adapter that just records the last call.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Final, fully-resolved SEO payload.
|
|
35
|
+
*
|
|
36
|
+
* Every field is **already normalized** — null vs empty string,
|
|
37
|
+
* fallbacks applied, dynamic tokens replaced. Consumers (Nuxt
|
|
38
|
+
* `useSeoMeta`, DOM mutation, SSR string templating) should be
|
|
39
|
+
* able to read this object and emit `<meta>` tags without further
|
|
40
|
+
* computation.
|
|
41
|
+
*/
|
|
42
|
+
export interface SeoData {
|
|
43
|
+
/** Page title, ready to feed `useSeoMeta({ title })` / `<title>`. */
|
|
44
|
+
title: string;
|
|
45
|
+
/**
|
|
46
|
+
* Optional title template, e.g. `"%s · Acme"`. Falsy → leave the
|
|
47
|
+
* host app's default behaviour in place.
|
|
48
|
+
*/
|
|
49
|
+
titleTemplate?: string | null;
|
|
50
|
+
/** Meta description, 1–2 sentences, crawler-friendly. */
|
|
51
|
+
description: string;
|
|
52
|
+
/** Comma- or space-separated keywords. Empty string if unset. */
|
|
53
|
+
keywords: string;
|
|
54
|
+
/** Absolute canonical URL. Falsy → host decides (use `useRequestURL` etc.). */
|
|
55
|
+
canonicalUrl?: string | null;
|
|
56
|
+
/** Robots directive, e.g. `"index, follow"` or `"noindex, nofollow"`. */
|
|
57
|
+
robots?: string | null;
|
|
58
|
+
/** Absolute favicon URL (`.ico` / `.png`). */
|
|
59
|
+
favicon?: string | null;
|
|
60
|
+
/** Absolute share-image URL (used for both OG and Twitter by default). */
|
|
61
|
+
image?: string | null;
|
|
62
|
+
ogTitle?: string | null;
|
|
63
|
+
ogDescription?: string | null;
|
|
64
|
+
ogImage?: string | null;
|
|
65
|
+
ogImageAlt?: string | null;
|
|
66
|
+
ogUrl?: string | null;
|
|
67
|
+
ogType?: string | null;
|
|
68
|
+
ogSiteName?: string | null;
|
|
69
|
+
ogLocale?: string | null;
|
|
70
|
+
twitterCard?: string | null;
|
|
71
|
+
twitterTitle?: string | null;
|
|
72
|
+
twitterDescription?: string | null;
|
|
73
|
+
twitterImage?: string | null;
|
|
74
|
+
twitterSite?: string | null;
|
|
75
|
+
twitterCreator?: string | null;
|
|
76
|
+
/**
|
|
77
|
+
* Site name used in OG `site_name`. Today the `ogSiteName` field
|
|
78
|
+
* carries the same value — `siteName` is exposed separately for
|
|
79
|
+
* adapters that prefer to render it as JSON-LD instead of a meta
|
|
80
|
+
* tag.
|
|
81
|
+
*/
|
|
82
|
+
siteName?: string | null;
|
|
83
|
+
/** Free-form JSON-LD block. Adapter decides whether to render. */
|
|
84
|
+
jsonLd?: Record<string, unknown> | null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The full, normalized context the `SeoProvider` hands to its
|
|
88
|
+
* adapter. Same as `SeoData` plus the canonical `pageType` key
|
|
89
|
+
* (used for analytics / structured data) and the unresolved raw
|
|
90
|
+
* inputs in case the adapter needs them.
|
|
91
|
+
*/
|
|
92
|
+
export interface SeoPayload extends SeoData {
|
|
93
|
+
/**
|
|
94
|
+
* Logical page key, e.g. `"home"`, `"product"`, `"collection"`.
|
|
95
|
+
* Adapter can use it for breadcrumb JSON-LD, custom `<meta>`,
|
|
96
|
+
* or just logging.
|
|
97
|
+
*/
|
|
98
|
+
pageType: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Partial SEO data a page (or any component) can push into the
|
|
102
|
+
* provider via `useSeo({ … })`. Anything not supplied is filled by
|
|
103
|
+
* the provider's defaults / store-level fallback. Keys with `null`
|
|
104
|
+
* explicitly clear the inherited value.
|
|
105
|
+
*/
|
|
106
|
+
export interface SeoContextInput {
|
|
107
|
+
pageType?: string;
|
|
108
|
+
title?: string | null;
|
|
109
|
+
description?: string | null;
|
|
110
|
+
keywords?: string | null;
|
|
111
|
+
image?: string | null;
|
|
112
|
+
canonicalUrl?: string | null;
|
|
113
|
+
robots?: string | null;
|
|
114
|
+
/**
|
|
115
|
+
* Token replacements applied to every string field, e.g.
|
|
116
|
+
* `{ productName: 'Blue Tee' }` for `{{productName}}`.
|
|
117
|
+
*/
|
|
118
|
+
replacements?: Record<string, string | number | null | undefined>;
|
|
119
|
+
/**
|
|
120
|
+
* Free-form extras merged on top of the resolved payload. Use
|
|
121
|
+
* sparingly — the canonical fields above are preferred.
|
|
122
|
+
*/
|
|
123
|
+
overrides?: Partial<SeoData>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The contract a host application (Nuxt app, Vue admin shell,
|
|
127
|
+
* SSR template) provides so the generic `SeoProvider` can emit
|
|
128
|
+
* the resolved SEO without depending on any specific framework.
|
|
129
|
+
*
|
|
130
|
+
* The adapter receives the final, normalized payload. The
|
|
131
|
+
* provider calls it whenever the merged SEO context changes.
|
|
132
|
+
*/
|
|
133
|
+
export interface SeoAdapter {
|
|
134
|
+
/**
|
|
135
|
+
* Apply the SEO payload to the document. Called reactively on
|
|
136
|
+
* every change; may be called multiple times during a session
|
|
137
|
+
* (e.g. on route change, then on data load).
|
|
138
|
+
*/
|
|
139
|
+
apply(payload: SeoPayload): void;
|
|
140
|
+
/**
|
|
141
|
+
* Optional cleanup hook, e.g. clearing injected meta tags when
|
|
142
|
+
* the provider unmounts. The default implementation in
|
|
143
|
+
* `normalizeSeo` no-ops.
|
|
144
|
+
*/
|
|
145
|
+
reset?(): void;
|
|
146
|
+
}
|