@stnd/layout 0.2.0 → 0.5.1
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/Base.astro +110 -130
- package/CHANGELOG.md +36 -0
- package/Encrypted.astro +187 -188
- package/Footer.astro +192 -0
- package/Header.astro +542 -0
- package/Meta.astro +281 -328
- package/README.md +546 -0
- package/StndInit.astro +74 -0
- package/package.json +14 -4
package/Meta.astro
CHANGED
|
@@ -1,260 +1,230 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @component Meta
|
|
4
|
+
* @description Universal `<head>` meta tags — canonical link, description,
|
|
5
|
+
* Open Graph, Twitter Cards, Schema.org JSON-LD, favicons, theme-color, and
|
|
6
|
+
* mobile/Windows tile tags. Self-contained and zero-config: reads site
|
|
7
|
+
* defaults from `virtual:stnd/config`, with every prop overriding its
|
|
8
|
+
* matching config value for the current page only.
|
|
4
9
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
10
|
+
* @example astro
|
|
11
|
+
* <Meta />
|
|
12
|
+
* <Meta title="My Page" />
|
|
13
|
+
* <Meta title="Article" type="article" created="2024-01-01" />
|
|
7
14
|
*/
|
|
8
|
-
import
|
|
9
|
-
// import { ClientRouter } from "astro:transitions";
|
|
15
|
+
import config from "virtual:stnd/config";
|
|
10
16
|
|
|
11
|
-
// Access site configuration from global
|
|
12
|
-
const config = (globalThis['__STANDARD_CONFIG__'] as any) || {};
|
|
13
|
-
|
|
14
|
-
// Props interface for universal usage
|
|
15
17
|
interface Props {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
18
|
+
// Per-page metadata (overrides config defaults)
|
|
19
|
+
title?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
image?: string;
|
|
22
|
+
author?: string;
|
|
23
|
+
language?: string;
|
|
24
|
+
created?: string;
|
|
25
|
+
modified?: string;
|
|
26
|
+
tags?: string[];
|
|
27
|
+
|
|
28
|
+
/** Controls `og:type` and the Schema.org `@type` (article → BlogPosting, profile → ProfilePage, else WebSite). Default: "website" */
|
|
29
|
+
type?: "website" | "article" | "profile";
|
|
30
|
+
|
|
31
|
+
// Appearance
|
|
32
|
+
/** `theme-color` meta tag. A single string is used for both color schemes; pass `{light, dark}` to vary by `prefers-color-scheme`. */
|
|
33
|
+
themeColor?: string | { light: string; dark: string };
|
|
34
|
+
/** `msapplication-TileColor` for Windows tiles. Default: "#ffffff" */
|
|
35
|
+
tileColor?: string;
|
|
36
|
+
|
|
37
|
+
/** "private"/"unlisted" emit `noindex, nofollow`; "public" (default) allows indexing. */
|
|
38
|
+
visibility?: "public" | "private" | "unlisted";
|
|
39
|
+
|
|
40
|
+
// Optional features
|
|
41
|
+
rssFeedUrl?: string | null;
|
|
42
|
+
rssFeedTitle?: string;
|
|
43
|
+
/** Path prefix for the generated favicon package (.ico, PNG sizes, browserconfig.xml). Default: "/assets/favicon" */
|
|
44
|
+
faviconPath?: string;
|
|
45
|
+
/**
|
|
46
|
+
* A single image URL used as *the* site icon, replacing the whole
|
|
47
|
+
* `faviconPath` set. Takes precedence over `faviconPath` when both are given.
|
|
48
|
+
* Modern browsers are happy with one SVG or PNG; use `faviconPath` when you
|
|
49
|
+
* have a full generated package (.ico, apple-touch-icon, browserconfig…).
|
|
50
|
+
*/
|
|
51
|
+
favicon?: string | null;
|
|
52
|
+
headExtensions?: string | string[];
|
|
53
|
+
|
|
54
|
+
/** Absolute origin overriding `Astro.site` (a build-time constant) — pass this when the actual per-request origin, e.g. a user's verified custom domain, differs from the build's default site. */
|
|
55
|
+
siteUrl?: string;
|
|
38
56
|
}
|
|
39
57
|
|
|
40
58
|
const {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Get user preferences if context is available
|
|
82
|
-
const userPrefs = contextData?.visitor?.raw?.preferences || contextData?.preferences || {
|
|
83
|
-
theme: "default",
|
|
84
|
-
colorMode: "light",
|
|
85
|
-
uiLanguage: "en",
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// Extract frontmatter from data source if available
|
|
89
|
-
const sourceFrontmatter = dataView?.frontmatter || effectiveDataSource?.raw?.frontmatter || {};
|
|
90
|
-
const effectiveFrontmatter = { ...frontmatter, ...sourceFrontmatter };
|
|
91
|
-
|
|
92
|
-
// Build metadata with dynamic data support
|
|
93
|
-
const ogData = dataView?.og || {};
|
|
94
|
-
const title = propsTitle || ogData.title || effectiveFrontmatter?.title || config?.title;
|
|
95
|
-
const description = propsDescription || ogData.description || effectiveFrontmatter?.description || config?.description;
|
|
96
|
-
const image = propsImage || ogData.image || effectiveFrontmatter?.image;
|
|
97
|
-
const created = propsCreated || effectiveFrontmatter?.created;
|
|
98
|
-
const modified = propsModified || effectiveFrontmatter?.modified;
|
|
99
|
-
const tags = propsTags || effectiveFrontmatter?.tags;
|
|
100
|
-
const securityHeaders = propsSecurityHeaders || effectiveFrontmatter?.securityHeaders || site?.securityHeaders;
|
|
101
|
-
|
|
102
|
-
const fullTitle = title && title !== site.title ? `${title} · ${site.title}` : site.title;
|
|
103
|
-
|
|
104
|
-
// Ensure we have a valid base URL
|
|
105
|
-
const baseUrl = site.url || config?.url || Astro.site || 'http://localhost:4321';
|
|
106
|
-
const pageUrl = new URL(Astro.url.pathname, baseUrl);
|
|
59
|
+
title: propsTitle,
|
|
60
|
+
description: propsDescription,
|
|
61
|
+
image: propsImage,
|
|
62
|
+
author: propsAuthor,
|
|
63
|
+
language: propsLanguage,
|
|
64
|
+
created: propsCreated,
|
|
65
|
+
modified: propsModified,
|
|
66
|
+
tags: propsTags,
|
|
67
|
+
type = "website",
|
|
68
|
+
themeColor,
|
|
69
|
+
tileColor,
|
|
70
|
+
visibility = "public",
|
|
71
|
+
rssFeedUrl,
|
|
72
|
+
rssFeedTitle,
|
|
73
|
+
faviconPath,
|
|
74
|
+
favicon,
|
|
75
|
+
headExtensions,
|
|
76
|
+
siteUrl: propsSiteUrl,
|
|
77
|
+
} = Astro.props as Props;
|
|
78
|
+
|
|
79
|
+
// ── Site defaults from standard() config ──
|
|
80
|
+
const siteTitle = config.title || "";
|
|
81
|
+
const siteDescription = config.description || "";
|
|
82
|
+
const siteAuthor = config.author?.name || "";
|
|
83
|
+
const siteLanguage = config.language || "en";
|
|
84
|
+
|
|
85
|
+
// ── Resolved metadata — props override config ──
|
|
86
|
+
const title = propsTitle || siteTitle;
|
|
87
|
+
const description = propsDescription || siteDescription;
|
|
88
|
+
const authorName = propsAuthor || siteAuthor;
|
|
89
|
+
const pageLanguage = propsLanguage || siteLanguage;
|
|
90
|
+
const image = propsImage;
|
|
91
|
+
const created = propsCreated;
|
|
92
|
+
const modified = propsModified;
|
|
93
|
+
const tags = propsTags;
|
|
94
|
+
|
|
95
|
+
// ── URL construction ──
|
|
96
|
+
const baseUrl = (propsSiteUrl || Astro.site?.toString() || "").replace(/\/$/, "");
|
|
97
|
+
const pageUrl = new URL(Astro.url.pathname, baseUrl || "http://localhost:4321");
|
|
107
98
|
const canonicalUrl = new URL(
|
|
108
|
-
|
|
109
|
-
|
|
99
|
+
Astro.url.pathname.replace(/index\.html$/, ""),
|
|
100
|
+
baseUrl || "http://localhost:4321",
|
|
110
101
|
);
|
|
111
102
|
|
|
112
|
-
// Description
|
|
113
|
-
const
|
|
114
|
-
const metaDescription = (description ||
|
|
115
|
-
getExcerpt(pageExcerpt, { maxChars: 150 }) ||
|
|
116
|
-
site.description ||
|
|
117
|
-
"").toString();
|
|
103
|
+
// ── Description ──
|
|
104
|
+
const metaDescription = (description || "").toString();
|
|
118
105
|
|
|
119
|
-
// Image
|
|
106
|
+
// ── Image ──
|
|
120
107
|
const imageUrl = image
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
: null;
|
|
136
|
-
const dateModified = modified || created
|
|
137
|
-
? new Date(modified || created).toISOString()
|
|
108
|
+
? image.startsWith("http")
|
|
109
|
+
? image
|
|
110
|
+
: `${baseUrl}/assets/img/${image}`
|
|
111
|
+
: baseUrl
|
|
112
|
+
? `${baseUrl}/assets/img/default.png`
|
|
113
|
+
: "/assets/img/default.png";
|
|
114
|
+
const imageAlt = title || "";
|
|
115
|
+
|
|
116
|
+
// ── Dates ──
|
|
117
|
+
const datePublished = created ? new Date(created).toISOString() : null;
|
|
118
|
+
const dateModified = modified
|
|
119
|
+
? new Date(modified).toISOString()
|
|
120
|
+
: created
|
|
121
|
+
? new Date(created).toISOString()
|
|
138
122
|
: null;
|
|
139
123
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
124
|
+
// ── Favicon ──
|
|
125
|
+
// A single `favicon` image replaces the generated package entirely. Anything
|
|
126
|
+
// else falls back to the `faviconPath` set (.ico + PNG sizes + browserconfig).
|
|
127
|
+
const faviconBase = faviconPath || "/assets/favicon";
|
|
128
|
+
const singleFavicon =
|
|
129
|
+
typeof favicon === "string" && favicon.trim() !== "" ? favicon.trim() : null;
|
|
130
|
+
|
|
131
|
+
const FAVICON_MIME = {
|
|
132
|
+
svg: "image/svg+xml",
|
|
133
|
+
png: "image/png",
|
|
134
|
+
ico: "image/x-icon",
|
|
135
|
+
gif: "image/gif",
|
|
136
|
+
jpg: "image/jpeg",
|
|
137
|
+
jpeg: "image/jpeg",
|
|
138
|
+
webp: "image/webp",
|
|
139
|
+
avif: "image/avif",
|
|
140
|
+
};
|
|
149
141
|
|
|
150
|
-
//
|
|
151
|
-
|
|
142
|
+
// The extension can carry a query string (`/cdn/logo.svg?v=2`); strip it before
|
|
143
|
+
// matching, and leave `type` off entirely when the URL gives us nothing to go on.
|
|
144
|
+
const singleFaviconType = singleFavicon
|
|
145
|
+
? (FAVICON_MIME[
|
|
146
|
+
(singleFavicon.split(/[?#]/)[0].split(".").pop() || "").toLowerCase()
|
|
147
|
+
] ?? undefined)
|
|
148
|
+
: undefined;
|
|
149
|
+
|
|
150
|
+
// ── Theme colors ──
|
|
151
|
+
const resolvedThemeColor =
|
|
152
|
+
typeof themeColor === "string"
|
|
153
|
+
? { light: themeColor, dark: themeColor }
|
|
154
|
+
: themeColor || { light: "#ffffff", dark: "#1a1a1a" };
|
|
155
|
+
const resolvedTileColor = tileColor || "#ffffff";
|
|
156
|
+
|
|
157
|
+
// ── OG & Schema.org types ──
|
|
158
|
+
const ogType = type;
|
|
159
|
+
const schemaType =
|
|
160
|
+
type === "article"
|
|
161
|
+
? "BlogPosting"
|
|
162
|
+
: type === "profile"
|
|
163
|
+
? "ProfilePage"
|
|
164
|
+
: "WebSite";
|
|
165
|
+
|
|
166
|
+
// ── Head extensions ──
|
|
167
|
+
const headExtraHtml = Array.isArray(headExtensions)
|
|
168
|
+
? headExtensions.join("\n")
|
|
169
|
+
: headExtensions || "";
|
|
152
170
|
---
|
|
153
171
|
|
|
154
172
|
<meta charset="utf-8" />
|
|
155
173
|
{headExtraHtml && <Fragment set:html={headExtraHtml} />}
|
|
156
|
-
<!-- Standard Framework | https://standard.ffp.co -->
|
|
157
174
|
|
|
158
175
|
<meta
|
|
159
|
-
|
|
160
|
-
|
|
176
|
+
name="viewport"
|
|
177
|
+
content="width=device-width, initial-scale=1, viewport-fit=cover"
|
|
161
178
|
/>
|
|
162
179
|
|
|
163
|
-
|
|
164
|
-
{contextData && (
|
|
165
|
-
<>
|
|
166
|
-
<meta name="mobile-web-app-capable" content="yes" />
|
|
167
|
-
<meta
|
|
168
|
-
name="apple-mobile-web-app-status-bar-style"
|
|
169
|
-
content="black-translucent"
|
|
170
|
-
/>
|
|
171
|
-
</>
|
|
172
|
-
)}
|
|
173
|
-
|
|
174
|
-
{fontsUrl && (
|
|
175
|
-
<!-- External fonts -->
|
|
176
|
-
<link rel="stylesheet" href={fontsUrl} />
|
|
177
|
-
)}
|
|
178
|
-
|
|
179
|
-
<title>{fullTitle}</title>
|
|
180
|
+
<title>{title}</title>
|
|
180
181
|
<meta name="description" content={metaDescription} />
|
|
181
|
-
<meta name="author" content={authorName} />
|
|
182
|
+
{authorName && <meta name="author" content={authorName} />}
|
|
182
183
|
<meta name="language" content={pageLanguage} />
|
|
183
184
|
<meta name="referrer" content="strict-origin-when-cross-origin" />
|
|
184
185
|
|
|
185
|
-
<!-- Canonical URL -->
|
|
186
186
|
<link rel="canonical" href={canonicalUrl} />
|
|
187
187
|
|
|
188
|
-
<!-- Feed & Sitemap -->
|
|
189
188
|
{
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
{site?.url && (
|
|
199
|
-
<link
|
|
200
|
-
rel="sitemap"
|
|
201
|
-
type="application/xml"
|
|
202
|
-
title="Sitemap"
|
|
203
|
-
href={`${site.url}/sitemap.xml`}
|
|
204
|
-
/>
|
|
205
|
-
)}
|
|
206
|
-
</>
|
|
207
|
-
) : null
|
|
189
|
+
rssFeedUrl && (
|
|
190
|
+
<link
|
|
191
|
+
rel="alternate"
|
|
192
|
+
type="application/atom+xml"
|
|
193
|
+
title={rssFeedTitle || title}
|
|
194
|
+
href={rssFeedUrl}
|
|
195
|
+
/>
|
|
196
|
+
)
|
|
208
197
|
}
|
|
209
198
|
|
|
210
199
|
{
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
{securityHeaders.referrerPolicy && (
|
|
220
|
-
<meta
|
|
221
|
-
http-equiv="Referrer-Policy"
|
|
222
|
-
content={securityHeaders.referrerPolicy}
|
|
223
|
-
/>
|
|
224
|
-
)}
|
|
225
|
-
{securityHeaders.permissions && (
|
|
226
|
-
<meta
|
|
227
|
-
http-equiv="Permissions-Policy"
|
|
228
|
-
content={securityHeaders.permissions}
|
|
229
|
-
/>
|
|
230
|
-
)}
|
|
231
|
-
{securityHeaders.xContentTypeOptions && (
|
|
232
|
-
<meta
|
|
233
|
-
http-equiv="X-Content-Type-Options"
|
|
234
|
-
content={securityHeaders.xContentTypeOptions}
|
|
235
|
-
/>
|
|
236
|
-
)}
|
|
237
|
-
</>
|
|
238
|
-
)
|
|
200
|
+
baseUrl && (
|
|
201
|
+
<link
|
|
202
|
+
rel="sitemap"
|
|
203
|
+
type="application/xml"
|
|
204
|
+
title="Sitemap"
|
|
205
|
+
href={`${baseUrl}/sitemap.xml`}
|
|
206
|
+
/>
|
|
207
|
+
)
|
|
239
208
|
}
|
|
240
209
|
|
|
241
|
-
<!--
|
|
210
|
+
<!-- CSP is handled natively by Astro 6 via security.csp config -->
|
|
211
|
+
|
|
242
212
|
{
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
213
|
+
visibility === "private" || visibility === "unlisted" ? (
|
|
214
|
+
<meta name="robots" content="noindex, nofollow" />
|
|
215
|
+
) : (
|
|
216
|
+
<meta
|
|
217
|
+
name="robots"
|
|
218
|
+
content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1"
|
|
219
|
+
/>
|
|
220
|
+
)
|
|
251
221
|
}
|
|
252
222
|
|
|
253
223
|
<!-- Open Graph -->
|
|
254
|
-
<meta property="og:title" content={title
|
|
255
|
-
<meta property="og:type" content={
|
|
224
|
+
<meta property="og:title" content={title} />
|
|
225
|
+
<meta property="og:type" content={ogType} />
|
|
256
226
|
<meta property="og:locale" content={pageLanguage} />
|
|
257
|
-
<meta property="og:site_name" content={
|
|
227
|
+
{siteTitle && <meta property="og:site_name" content={siteTitle} />}
|
|
258
228
|
<meta property="og:url" content={pageUrl} />
|
|
259
229
|
<meta property="og:description" content={metaDescription} />
|
|
260
230
|
|
|
@@ -264,139 +234,122 @@ const headExtraHtml = Array.isArray(headExtensions) ? headExtensions.join("\n")
|
|
|
264
234
|
<meta property="og:image:height" content="630" />
|
|
265
235
|
|
|
266
236
|
{
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
237
|
+
type === "article" && datePublished && (
|
|
238
|
+
<>
|
|
239
|
+
<meta property="article:published_time" content={datePublished} />
|
|
240
|
+
{authorName && <meta property="article:author" content={authorName} />}
|
|
241
|
+
{tags &&
|
|
242
|
+
tags.map((tag: string) => (
|
|
243
|
+
<meta property="article:tag" content={tag} />
|
|
244
|
+
))}
|
|
245
|
+
</>
|
|
246
|
+
)
|
|
277
247
|
}
|
|
278
248
|
|
|
279
249
|
<!-- Twitter Cards -->
|
|
280
|
-
<meta name="twitter:card" content={
|
|
250
|
+
<meta name="twitter:card" content={image ? "summary_large_image" : "summary"} />
|
|
281
251
|
<meta name="twitter:image" content={imageUrl} />
|
|
282
252
|
<meta name="twitter:image:alt" content={imageAlt} />
|
|
283
|
-
<meta name="twitter:
|
|
284
|
-
<meta name="twitter:creator" content={`@${twitterUsername}`} />
|
|
285
|
-
<meta name="twitter:title" content={fullTitle} />
|
|
253
|
+
<meta name="twitter:title" content={title} />
|
|
286
254
|
<meta name="twitter:description" content={metaDescription} />
|
|
287
255
|
|
|
288
256
|
<!-- Schema.org -->
|
|
289
257
|
<script
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
258
|
+
type="application/ld+json"
|
|
259
|
+
set:html={JSON.stringify({
|
|
260
|
+
"@context": "https://schema.org",
|
|
261
|
+
"@type": schemaType,
|
|
262
|
+
...(type === "article"
|
|
263
|
+
? {
|
|
264
|
+
headline: title,
|
|
265
|
+
description: metaDescription,
|
|
266
|
+
...(authorName
|
|
267
|
+
? {
|
|
268
|
+
author: { "@type": "Person", name: authorName },
|
|
269
|
+
publisher: { "@type": "Person", name: authorName },
|
|
270
|
+
}
|
|
271
|
+
: {}),
|
|
272
|
+
...(datePublished ? { datePublished } : {}),
|
|
273
|
+
...(dateModified ? { dateModified } : {}),
|
|
274
|
+
image: {
|
|
275
|
+
"@type": "ImageObject",
|
|
276
|
+
url: imageUrl,
|
|
277
|
+
width: 1200,
|
|
278
|
+
height: 630,
|
|
279
|
+
},
|
|
280
|
+
mainEntityOfPage: { "@type": "WebPage", "@id": pageUrl },
|
|
281
|
+
}
|
|
282
|
+
: {
|
|
283
|
+
name: siteTitle || title,
|
|
284
|
+
description: metaDescription,
|
|
285
|
+
url: baseUrl,
|
|
286
|
+
...(authorName
|
|
295
287
|
? {
|
|
296
|
-
|
|
297
|
-
description: metaDescription,
|
|
298
|
-
author: {
|
|
299
|
-
"@type": "Person",
|
|
300
|
-
name: authorName,
|
|
301
|
-
url: `${site.url}/now/`,
|
|
302
|
-
},
|
|
303
|
-
publisher: {
|
|
304
|
-
"@type": "Person",
|
|
305
|
-
name: authorName,
|
|
306
|
-
url: site.url,
|
|
307
|
-
},
|
|
308
|
-
...(datePublished ? { datePublished } : {}),
|
|
309
|
-
...(dateModified ? { dateModified } : {}),
|
|
310
|
-
image: {
|
|
311
|
-
"@type": "ImageObject",
|
|
312
|
-
url: imageUrl,
|
|
313
|
-
width: 1200,
|
|
314
|
-
height: 630,
|
|
315
|
-
},
|
|
316
|
-
mainEntityOfPage: {
|
|
317
|
-
"@type": "WebPage",
|
|
318
|
-
"@id": pageUrl,
|
|
319
|
-
},
|
|
288
|
+
author: { "@type": "Person", name: authorName },
|
|
320
289
|
}
|
|
321
|
-
: {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
url: site.url,
|
|
325
|
-
author: {
|
|
326
|
-
"@type": "Person",
|
|
327
|
-
name: authorName,
|
|
328
|
-
url: site.url,
|
|
329
|
-
},
|
|
330
|
-
}),
|
|
331
|
-
})}
|
|
290
|
+
: {}),
|
|
291
|
+
}),
|
|
292
|
+
})}
|
|
332
293
|
/>
|
|
333
294
|
|
|
334
295
|
<!-- Favicons -->
|
|
335
|
-
{
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
/>
|
|
366
|
-
<link
|
|
367
|
-
rel="icon"
|
|
368
|
-
type="image/png"
|
|
369
|
-
sizes="16x16"
|
|
370
|
-
href={`${faviconBasePath}/favicon-16x16.png`}
|
|
371
|
-
/>
|
|
372
|
-
<link rel="manifest" href="/site.webmanifest" />
|
|
373
|
-
<link
|
|
374
|
-
rel="mask-icon"
|
|
375
|
-
href={`${faviconBasePath}/safari-pinned-tab.svg`}
|
|
376
|
-
color="#000000"
|
|
377
|
-
/>
|
|
378
|
-
<link rel="shortcut icon" href={`${faviconBasePath}/favicon.ico`} />
|
|
379
|
-
</>
|
|
380
|
-
)}
|
|
296
|
+
{
|
|
297
|
+
singleFavicon ? (
|
|
298
|
+
<Fragment>
|
|
299
|
+
<link rel="icon" type={singleFaviconType} href={singleFavicon} />
|
|
300
|
+
<link rel="apple-touch-icon" href={singleFavicon} />
|
|
301
|
+
</Fragment>
|
|
302
|
+
) : (
|
|
303
|
+
<Fragment>
|
|
304
|
+
<link
|
|
305
|
+
rel="icon"
|
|
306
|
+
type="image/png"
|
|
307
|
+
href={`${faviconBase}/favicon-96x96.png`}
|
|
308
|
+
sizes="96x96"
|
|
309
|
+
/>
|
|
310
|
+
<link
|
|
311
|
+
rel="icon"
|
|
312
|
+
type="image/svg+xml"
|
|
313
|
+
href={`${faviconBase}/favicon.svg`}
|
|
314
|
+
/>
|
|
315
|
+
<link rel="shortcut icon" href={`${faviconBase}/favicon.ico`} />
|
|
316
|
+
<link
|
|
317
|
+
rel="apple-touch-icon"
|
|
318
|
+
sizes="180x180"
|
|
319
|
+
href={`${faviconBase}/apple-touch-icon.png`}
|
|
320
|
+
/>
|
|
321
|
+
</Fragment>
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
<meta name="apple-mobile-web-app-title" content={siteTitle || title || ""} />
|
|
325
|
+
<link rel="manifest" href="/site.webmanifest" />
|
|
381
326
|
|
|
382
327
|
<!-- Mobile -->
|
|
383
328
|
<meta
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
329
|
+
name="theme-color"
|
|
330
|
+
content={resolvedThemeColor.light}
|
|
331
|
+
media="(prefers-color-scheme: light)"
|
|
387
332
|
/>
|
|
388
333
|
<meta
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
334
|
+
name="theme-color"
|
|
335
|
+
content={resolvedThemeColor.dark}
|
|
336
|
+
media="(prefers-color-scheme: dark)"
|
|
392
337
|
/>
|
|
393
|
-
|
|
394
|
-
<!-- Windows Tiles -->
|
|
395
|
-
<meta name="msapplication-TileColor" content="#000000" />
|
|
338
|
+
<meta name="mobile-web-app-capable" content="yes" />
|
|
396
339
|
<meta
|
|
397
|
-
|
|
398
|
-
|
|
340
|
+
name="apple-mobile-web-app-status-bar-style"
|
|
341
|
+
content="black-translucent"
|
|
399
342
|
/>
|
|
400
343
|
|
|
401
|
-
<!--
|
|
402
|
-
|
|
344
|
+
<!-- Windows Tiles -->
|
|
345
|
+
<meta name="msapplication-TileColor" content={resolvedTileColor} />
|
|
346
|
+
{
|
|
347
|
+
/* Only meaningful alongside the generated package — a single `favicon`
|
|
348
|
+
has no browserconfig.xml to point at. */
|
|
349
|
+
!singleFavicon && (
|
|
350
|
+
<meta
|
|
351
|
+
name="msapplication-config"
|
|
352
|
+
content={`${faviconBase}/browserconfig.xml`}
|
|
353
|
+
/>
|
|
354
|
+
)
|
|
355
|
+
}
|