@tokenoftrust/storefront-runner 2.4.80 → 2.4.81
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/apps/storefront/src/components/blog/BlogPostArticle.astro +2 -0
- package/apps/storefront/src/lib/seo/documentTitle.ts +5 -5
- package/apps/storefront/src/pages/collections/[handle].astro +2 -2
- package/apps/storefront/src/pages/products/[handle].astro +2 -2
- package/package.json +1 -1
- package/packages/cli/src/declared-config.mjs +3 -3
- package/packages/public-runtime/src/declared-tenant-config.mjs +3 -3
- package/packages/public-runtime/src/tenant.ts +4 -4
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* the request.
|
|
9
9
|
*/
|
|
10
10
|
import Layout from "@/layouts/Layout.astro";
|
|
11
|
+
import { sourceSeoTitle } from "@/lib/seo/documentTitle";
|
|
11
12
|
import Breadcrumbs from "@/components/Breadcrumbs.astro";
|
|
12
13
|
import BlogPaywall from "@/components/blog/BlogPaywall.astro";
|
|
13
14
|
import ReactionBar from "@/components/blog/ReactionBar.astro";
|
|
@@ -26,6 +27,7 @@ const {
|
|
|
26
27
|
|
|
27
28
|
<Layout
|
|
28
29
|
title={post.seoTitle || post.title}
|
|
30
|
+
seoTitle={sourceSeoTitle(Astro.locals.tenant.seo, post.seoTitle)}
|
|
29
31
|
description={post.description}
|
|
30
32
|
path={path}
|
|
31
33
|
ogType="article"
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
* renders as written, any other is suffixed.
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
16
|
-
* A product's or
|
|
17
|
-
* the tenant declares `seo.
|
|
16
|
+
* A product's, collection's or blog post's own SEO title, as the `seoTitle` to render verbatim,
|
|
17
|
+
* when the tenant declares `seo.verbatimSeoTitles`; otherwise undefined, so it is suffixed.
|
|
18
18
|
*/
|
|
19
|
-
export function
|
|
20
|
-
seo: {
|
|
19
|
+
export function sourceSeoTitle(
|
|
20
|
+
seo: { verbatimSeoTitles?: boolean } | undefined,
|
|
21
21
|
ownSeoTitle: string | undefined,
|
|
22
22
|
): string | undefined {
|
|
23
|
-
return seo?.
|
|
23
|
+
return seo?.verbatimSeoTitles && ownSeoTitle?.trim() ? ownSeoTitle : undefined;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function documentTitle(title: string, seoTitle: string | undefined, storeName: string): string {
|
|
@@ -19,7 +19,7 @@ import { parseQuery, buildQueryString } from "@/lib/url";
|
|
|
19
19
|
import { breadcrumbLd, itemListLd } from "@/lib/jsonld";
|
|
20
20
|
import { truncate } from "@/lib/format";
|
|
21
21
|
import { pageUrlForm } from "@/lib/seo/urlForm";
|
|
22
|
-
import {
|
|
22
|
+
import { sourceSeoTitle } from "@/lib/seo/documentTitle";
|
|
23
23
|
import { withBase } from "@/lib/basePath";
|
|
24
24
|
import { isBlankDescriptionHtml, sanitizeDescriptionHtml } from "@/lib/content/sanitizeDescriptionHtml";
|
|
25
25
|
|
|
@@ -92,7 +92,7 @@ if (query.priceMin != null || query.priceMax != null) {
|
|
|
92
92
|
|
|
93
93
|
<Layout
|
|
94
94
|
title={collection.seo.title || collection.title}
|
|
95
|
-
seoTitle={
|
|
95
|
+
seoTitle={sourceSeoTitle(tenant.seo, collection.seo.title)}
|
|
96
96
|
description={collection.seo.description || (desc ? truncate(desc, 160) : `Shop ${collection.title} at ${tenant.displayName}.`)}
|
|
97
97
|
path={basePath}
|
|
98
98
|
noindex={isEmptyCollection}
|
|
@@ -47,7 +47,7 @@ import { isUsableImageUrl } from "@/lib/placeholder";
|
|
|
47
47
|
import { withBase } from "@/lib/basePath";
|
|
48
48
|
import { productPageType, structuredDataProfile } from "@/lib/seo/structuredData";
|
|
49
49
|
import { pageUrlForm } from "@/lib/seo/urlForm";
|
|
50
|
-
import {
|
|
50
|
+
import { sourceSeoTitle } from "@/lib/seo/documentTitle";
|
|
51
51
|
import { shippingPolicyPath } from "@/lib/commerce/shippingPolicy";
|
|
52
52
|
import AppWidgetFrame from "@/components/apps/AppWidgetFrame.astro";
|
|
53
53
|
import { getRegistryService } from "@/lib/apps/registryAccess.js";
|
|
@@ -314,7 +314,7 @@ const structuredData = {
|
|
|
314
314
|
|
|
315
315
|
<Layout
|
|
316
316
|
title={product.seo.title || product.title}
|
|
317
|
-
seoTitle={
|
|
317
|
+
seoTitle={sourceSeoTitle(tenant.seo, product.seo.title)}
|
|
318
318
|
description={product.seo.description || truncate(product.description_text, 160)}
|
|
319
319
|
path={`/products/${product.handle}`}
|
|
320
320
|
ogType="product"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.81",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
@@ -31,7 +31,7 @@ export const FEATURE_KEYS = [
|
|
|
31
31
|
export const SITE_TYPES = ["commerce", "marketing"];
|
|
32
32
|
|
|
33
33
|
/** Every `seo` key (mirrors packages/public-runtime/src/declared-tenant-config.mjs). */
|
|
34
|
-
export const SEO_KEYS = Object.freeze(["trailingSlash", "
|
|
34
|
+
export const SEO_KEYS = Object.freeze(["trailingSlash", "verbatimSeoTitles", "structuredData"]);
|
|
35
35
|
|
|
36
36
|
/** Every `seo.structuredData` key. */
|
|
37
37
|
export const STRUCTURED_DATA_KEYS = Object.freeze(["graphs", "names", "reviews", "onlyOn"]);
|
|
@@ -86,8 +86,8 @@ export function seoDeclarationProblems(seo) {
|
|
|
86
86
|
if (source.trailingSlash !== undefined && typeof source.trailingSlash !== "boolean") {
|
|
87
87
|
problems.push("`seo.trailingSlash` must be true or false");
|
|
88
88
|
}
|
|
89
|
-
if (source.
|
|
90
|
-
problems.push("`seo.
|
|
89
|
+
if (source.verbatimSeoTitles !== undefined && typeof source.verbatimSeoTitles !== "boolean") {
|
|
90
|
+
problems.push("`seo.verbatimSeoTitles` must be true or false");
|
|
91
91
|
}
|
|
92
92
|
const sd = source.structuredData;
|
|
93
93
|
if (sd === undefined) return problems;
|
|
@@ -75,7 +75,7 @@ export const FEATURE_KEYS = Object.freeze([
|
|
|
75
75
|
export const SITE_TYPES_LIST = Object.freeze(["commerce", "marketing"]);
|
|
76
76
|
|
|
77
77
|
/** Every `seo` key (SeoDeclaration, tenant.ts). */
|
|
78
|
-
export const SEO_KEYS = Object.freeze(["trailingSlash", "
|
|
78
|
+
export const SEO_KEYS = Object.freeze(["trailingSlash", "verbatimSeoTitles", "structuredData"]);
|
|
79
79
|
|
|
80
80
|
/** Every `seo.structuredData` key. */
|
|
81
81
|
export const STRUCTURED_DATA_KEYS = Object.freeze(["graphs", "names", "reviews", "onlyOn"]);
|
|
@@ -132,8 +132,8 @@ export function seoDeclarationProblems(seo) {
|
|
|
132
132
|
if (source.trailingSlash !== undefined && typeof source.trailingSlash !== "boolean") {
|
|
133
133
|
problems.push("`seo.trailingSlash` must be true or false");
|
|
134
134
|
}
|
|
135
|
-
if (source.
|
|
136
|
-
problems.push("`seo.
|
|
135
|
+
if (source.verbatimSeoTitles !== undefined && typeof source.verbatimSeoTitles !== "boolean") {
|
|
136
|
+
problems.push("`seo.verbatimSeoTitles` must be true or false");
|
|
137
137
|
}
|
|
138
138
|
const sd = source.structuredData;
|
|
139
139
|
if (sd === undefined) return problems;
|
|
@@ -778,11 +778,11 @@ export interface SeoDeclaration {
|
|
|
778
778
|
*/
|
|
779
779
|
trailingSlash?: boolean;
|
|
780
780
|
/**
|
|
781
|
-
* A product's or
|
|
782
|
-
* written (a source that already ends its titles in "| Brand"). Absent or false ⇒ it is
|
|
783
|
-
* suffixed with the store name
|
|
781
|
+
* A product's, collection's or blog post's own SEO title is the complete `<title>`, rendered
|
|
782
|
+
* as written (a source that already ends its titles in "| Brand"). Absent or false ⇒ it is
|
|
783
|
+
* suffixed with the store name unless it already names the store.
|
|
784
784
|
*/
|
|
785
|
-
|
|
785
|
+
verbatimSeoTitles?: boolean;
|
|
786
786
|
/**
|
|
787
787
|
* The structured-data profile. When present it is the ONLY JSON-LD a declared page type
|
|
788
788
|
* emits: one `@graph` holding exactly the listed nodes, in order. A node whose data the
|