@mannisto/astro-metadata 2.0.0-alpha.4 → 2.0.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/README.md +247 -108
- package/package.json +2 -2
- package/src/components/Canonical.astro +10 -0
- package/src/components/Description.astro +10 -0
- package/src/components/Favicon.astro +9 -0
- package/src/components/Feeds.astro +29 -0
- package/src/components/LanguageAlternates.astro +13 -0
- package/src/components/Manifest.astro +11 -0
- package/src/components/Metadata.astro +27 -199
- package/src/components/OpenGraph.astro +70 -0
- package/src/components/Robots.astro +10 -0
- package/src/components/Schema.astro +42 -0
- package/src/components/ThemeColor.astro +15 -0
- package/src/components/Title.astro +10 -0
- package/src/components/Twitter.astro +21 -0
- package/src/components/index.ts +13 -1
- package/src/integration.js +0 -6
- package/src/lib/checks.js +6 -1
- package/src/lib/manifest.ts +0 -1
- package/src/lib/open-graph.ts +54 -0
- package/src/lib/page.ts +164 -0
- package/src/lib/rules.js +0 -1
- package/src/lib/schema.ts +5 -2
- package/src/lib/site.ts +65 -43
- package/src/lib/url.ts +4 -1
- package/src/runtime.ts +1 -3
- package/src/toolbar/app.ts +1 -15
- package/src/toolbar/data.ts +0 -2
- package/src/toolbar/highlight.ts +0 -3
- package/src/toolbar/ui/alert/alert.css +0 -2
- package/src/toolbar/ui/section/section.css +0 -7
- package/src/toolbar/ui/table/table.css +0 -2
- package/src/toolbar/views/metadata-panel.css +0 -7
- package/src/toolbar/views/metadata-panel.ts +1 -5
- package/src/toolbar/views/overview-view.css +0 -2
- package/src/toolbar/views/overview-view.ts +13 -0
- package/src/toolbar/views/previews/previews-view.css +0 -1
- package/src/toolbar/views/previews/previews-view.ts +0 -1
|
@@ -1,206 +1,34 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type Props = {
|
|
20
|
-
/** The page title. The title template of the config adds the site name. */
|
|
21
|
-
title?: string
|
|
22
|
-
/** Defaults to the description of the config. */
|
|
23
|
-
description?: string
|
|
24
|
-
/** The social image. Defaults to the image of the config. */
|
|
25
|
-
image?: MetadataImage
|
|
26
|
-
/**
|
|
27
|
-
* The Open Graph type. Defaults to "website". "article" also adds the `Article`
|
|
28
|
-
* structured data, with the title, the description, the image and the dates.
|
|
29
|
-
*/
|
|
30
|
-
type?: string
|
|
31
|
-
/**
|
|
32
|
-
* The author of an article: a name, or `{ name, url, twitter }`. The Twitter (X) handle
|
|
33
|
-
* gives `twitter:creator`.
|
|
34
|
-
*/
|
|
35
|
-
author?: SchemaAuthor | SchemaAuthor[]
|
|
36
|
-
/** The publication date of an article. */
|
|
37
|
-
published?: Date | string
|
|
38
|
-
/** The date of the last change of an article. */
|
|
39
|
-
modified?: Date | string
|
|
40
|
-
/**
|
|
41
|
-
* The canonical path or URL. Defaults to the current page. With `@mannisto/astro-i18n`,
|
|
42
|
-
* the default is the public URL of the page.
|
|
43
|
-
*/
|
|
44
|
-
canonical?: string
|
|
45
|
-
/** Set to false to keep the page out of search results. Defaults to true. */
|
|
46
|
-
index?: boolean
|
|
47
|
-
/** Set to false to tell search engines not to follow the links. Defaults to true. */
|
|
48
|
-
follow?: boolean
|
|
49
|
-
/**
|
|
50
|
-
* The hreflang links: the page in each locale, and `x-default`. With `@mannisto/astro-i18n`
|
|
51
|
-
* or the `i18n` option of Astro, the links are made for you. `[]` turns them off.
|
|
52
|
-
*/
|
|
53
|
-
hreflang?: HreflangLink[]
|
|
54
|
-
/** The path from the home page to this page. It adds the `BreadcrumbList` structured data. */
|
|
55
|
-
breadcrumbs?: SchemaBreadcrumb[]
|
|
56
|
-
/**
|
|
57
|
-
* Structured data items of your own, e.g. a `Product`. They go into the structured data
|
|
58
|
-
* of the page, with the site from the config.
|
|
59
|
-
*/
|
|
60
|
-
schema?: SchemaData | SchemaData[]
|
|
61
|
-
/**
|
|
62
|
-
* The locale of the page. Defaults to `Astro.currentLocale`, or the locale of
|
|
63
|
-
* `@mannisto/astro-i18n`.
|
|
64
|
-
*/
|
|
65
|
-
locale?: string
|
|
66
|
-
}
|
|
2
|
+
import type { MetadataProps } from "../lib/page.ts"
|
|
3
|
+
import Canonical from "./Canonical.astro"
|
|
4
|
+
import Description from "./Description.astro"
|
|
5
|
+
import Favicon from "./Favicon.astro"
|
|
6
|
+
import Feeds from "./Feeds.astro"
|
|
7
|
+
import LanguageAlternates from "./LanguageAlternates.astro"
|
|
8
|
+
import Manifest from "./Manifest.astro"
|
|
9
|
+
import OpenGraph from "./OpenGraph.astro"
|
|
10
|
+
import Robots from "./Robots.astro"
|
|
11
|
+
import Schema from "./Schema.astro"
|
|
12
|
+
import ThemeColor from "./ThemeColor.astro"
|
|
13
|
+
import Title from "./Title.astro"
|
|
14
|
+
import Twitter from "./Twitter.astro"
|
|
15
|
+
|
|
16
|
+
export type Props = MetadataProps
|
|
67
17
|
|
|
68
18
|
const props = Astro.props
|
|
69
|
-
const config = Site.config
|
|
70
|
-
const locale = props.locale ?? Astro.currentLocale ?? i18n?.locale(Astro)
|
|
71
|
-
|
|
72
|
-
const siteName = Site.name(locale)
|
|
73
|
-
const title = Site.title(props.title, locale)
|
|
74
|
-
const description = props.description ?? localize(config.description, locale)
|
|
75
|
-
const image =
|
|
76
|
-
props.image ??
|
|
77
|
-
(config.image ? { ...config.image, alt: localize(config.image.alt, locale) } : undefined)
|
|
78
|
-
|
|
79
|
-
// Social cards show the page title without the template: og:site_name has the site name
|
|
80
|
-
const socialTitle = props.title ?? siteName
|
|
81
|
-
|
|
82
|
-
const canonical = Site.pageUrl(props.canonical ?? i18n?.path(Astro) ?? Astro.url.pathname)
|
|
83
|
-
|
|
84
|
-
// The page in each locale: from the props, or from the i18n setup of the project
|
|
85
|
-
const hreflang = props.hreflang ?? i18n?.alternates(Astro) ?? []
|
|
86
|
-
|
|
87
|
-
// Search engines index and follow by default, so the tag is only necessary for other values
|
|
88
|
-
const index = props.index ?? config.robots?.index ?? true
|
|
89
|
-
const follow = props.follow ?? config.robots?.follow ?? true
|
|
90
|
-
const robots = [!index && "noindex", !follow && "nofollow", config.robots?.extra]
|
|
91
|
-
.filter(Boolean)
|
|
92
|
-
.join(", ")
|
|
93
|
-
|
|
94
|
-
// Twitter (X) uses the Open Graph title, description and image, so only the tags that
|
|
95
|
-
// are specific to Twitter (X) are necessary
|
|
96
|
-
const card = image ? (config.twitter?.card ?? "summary_large_image") : "summary"
|
|
97
|
-
|
|
98
|
-
// The authors as a list, for the Twitter (X) handles and the article tags
|
|
99
|
-
const authors = props.author === undefined ? [] : [props.author].flat()
|
|
100
|
-
const creator = authors
|
|
101
|
-
.map((author) => {
|
|
102
|
-
return typeof author === "string" ? undefined : author.twitter
|
|
103
|
-
})
|
|
104
|
-
.find(Boolean)
|
|
105
|
-
const article = props.type === "article"
|
|
106
|
-
|
|
107
|
-
const schema = Schema.graph({
|
|
108
|
-
url: canonical,
|
|
109
|
-
title: props.title ?? siteName,
|
|
110
|
-
description,
|
|
111
|
-
image: image ? Site.url(image.url) : undefined,
|
|
112
|
-
locale,
|
|
113
|
-
type: props.type,
|
|
114
|
-
published: props.published,
|
|
115
|
-
modified: props.modified,
|
|
116
|
-
author: props.author,
|
|
117
|
-
breadcrumbs: props.breadcrumbs,
|
|
118
|
-
items: props.schema === undefined ? undefined : [props.schema].flat(),
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
const themeColor = config.themeColor
|
|
122
|
-
const manifest = Manifest.url(locale)
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Returns a date as an ISO 8601 text. A text stays unchanged.
|
|
126
|
-
*/
|
|
127
|
-
function isoDate(value: Date | string): string {
|
|
128
|
-
return typeof value === "string" ? value : value.toISOString()
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const FEED_TYPES = {
|
|
132
|
-
rss: "application/rss+xml",
|
|
133
|
-
atom: "application/atom+xml",
|
|
134
|
-
json: "application/feed+json",
|
|
135
|
-
}
|
|
136
19
|
---
|
|
137
20
|
|
|
138
21
|
<meta charset="UTF-8" />
|
|
139
22
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
{socialTitle && <meta property="og:title" content={socialTitle} />}
|
|
155
|
-
{description && <meta property="og:description" content={description} />}
|
|
156
|
-
<meta property="og:url" content={canonical} />
|
|
157
|
-
<meta property="og:type" content={props.type ?? "website"} />
|
|
158
|
-
{siteName && <meta property="og:site_name" content={siteName} />}
|
|
159
|
-
{
|
|
160
|
-
image && (
|
|
161
|
-
<>
|
|
162
|
-
<meta property="og:image" content={Site.url(image.url)} />
|
|
163
|
-
{image.alt && <meta property="og:image:alt" content={image.alt} />}
|
|
164
|
-
{image.width && <meta property="og:image:width" content={String(image.width)} />}
|
|
165
|
-
{image.height && <meta property="og:image:height" content={String(image.height)} />}
|
|
166
|
-
</>
|
|
167
|
-
)
|
|
168
|
-
}
|
|
169
|
-
{
|
|
170
|
-
article && props.published && (
|
|
171
|
-
<meta property="article:published_time" content={isoDate(props.published)} />
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
{
|
|
175
|
-
article && props.modified && (
|
|
176
|
-
<meta property="article:modified_time" content={isoDate(props.modified)} />
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
<meta name="twitter:card" content={card} />
|
|
180
|
-
{config.twitter?.site && <meta name="twitter:site" content={config.twitter.site} />}
|
|
181
|
-
{creator && <meta name="twitter:creator" content={creator} />}
|
|
182
|
-
|
|
183
|
-
{
|
|
184
|
-
Favicon.links().map((link) => {
|
|
185
|
-
return <link rel={link.rel} type={link.type} href={link.href} sizes={link.sizes} />
|
|
186
|
-
})
|
|
187
|
-
}
|
|
188
|
-
{manifest && <link rel="manifest" href={manifest} />}
|
|
189
|
-
{
|
|
190
|
-
config.feeds?.map((feed) => {
|
|
191
|
-
return (
|
|
192
|
-
<link
|
|
193
|
-
rel="alternate"
|
|
194
|
-
type={FEED_TYPES[feed.type ?? "rss"]}
|
|
195
|
-
title={localize(feed.title, locale)}
|
|
196
|
-
href={withBase(feed.href)}
|
|
197
|
-
/>
|
|
198
|
-
)
|
|
199
|
-
})
|
|
200
|
-
}
|
|
201
|
-
{
|
|
202
|
-
hreflang.map((link) => {
|
|
203
|
-
return <link rel="alternate" hreflang={link.hreflang} href={Site.pageUrl(link.href)} />
|
|
204
|
-
})
|
|
205
|
-
}
|
|
206
|
-
{schema && <script is:inline type="application/ld+json" set:html={Schema.stringify(schema)} />}
|
|
23
|
+
<Title {...props} />
|
|
24
|
+
<Description {...props} />
|
|
25
|
+
<Canonical {...props} />
|
|
26
|
+
<Robots {...props} />
|
|
27
|
+
<ThemeColor />
|
|
28
|
+
<OpenGraph {...props} />
|
|
29
|
+
<Twitter {...props} />
|
|
30
|
+
<Favicon />
|
|
31
|
+
<Manifest {...props} />
|
|
32
|
+
<Feeds {...props} />
|
|
33
|
+
<LanguageAlternates {...props} />
|
|
34
|
+
<Schema {...props} />
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getConfig, localize } from "../lib/config.ts"
|
|
3
|
+
import {
|
|
4
|
+
isoDate,
|
|
5
|
+
pageCanonical,
|
|
6
|
+
pageDescription,
|
|
7
|
+
pageHreflang,
|
|
8
|
+
pageImage,
|
|
9
|
+
pageLocale,
|
|
10
|
+
pageOpenGraphLocales,
|
|
11
|
+
} from "../lib/page.ts"
|
|
12
|
+
import type { MetadataProps } from "../lib/page.ts"
|
|
13
|
+
import { url } from "../lib/url.ts"
|
|
14
|
+
|
|
15
|
+
type Keys =
|
|
16
|
+
| "title"
|
|
17
|
+
| "description"
|
|
18
|
+
| "image"
|
|
19
|
+
| "type"
|
|
20
|
+
| "published"
|
|
21
|
+
| "modified"
|
|
22
|
+
| "canonical"
|
|
23
|
+
| "hreflang"
|
|
24
|
+
| "locale"
|
|
25
|
+
|
|
26
|
+
export type Props = Pick<MetadataProps, Keys>
|
|
27
|
+
|
|
28
|
+
const props = Astro.props
|
|
29
|
+
const locale = pageLocale(Astro, props)
|
|
30
|
+
const siteName = localize(getConfig().siteName, locale)
|
|
31
|
+
const description = pageDescription(props, locale)
|
|
32
|
+
const image = pageImage(props, locale)
|
|
33
|
+
const locales = pageOpenGraphLocales(locale, pageHreflang(Astro, props))
|
|
34
|
+
const article = props.type === "article"
|
|
35
|
+
|
|
36
|
+
// Social cards show the page title without the template: og:site_name has the site name
|
|
37
|
+
const title = props.title ?? siteName
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
{title && <meta property="og:title" content={title} />}
|
|
41
|
+
{description && <meta property="og:description" content={description} />}
|
|
42
|
+
<meta property="og:url" content={pageCanonical(Astro, props)} />
|
|
43
|
+
<meta property="og:type" content={props.type ?? "website"} />
|
|
44
|
+
{siteName && <meta property="og:site_name" content={siteName} />}
|
|
45
|
+
{locales.locale && <meta property="og:locale" content={locales.locale} />}
|
|
46
|
+
{
|
|
47
|
+
locales.alternates.map((alternate) => {
|
|
48
|
+
return <meta property="og:locale:alternate" content={alternate} />
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
{
|
|
52
|
+
image && (
|
|
53
|
+
<>
|
|
54
|
+
<meta property="og:image" content={url(image.url)} />
|
|
55
|
+
{image.alt && <meta property="og:image:alt" content={image.alt} />}
|
|
56
|
+
{image.width && <meta property="og:image:width" content={String(image.width)} />}
|
|
57
|
+
{image.height && <meta property="og:image:height" content={String(image.height)} />}
|
|
58
|
+
</>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
{
|
|
62
|
+
article && props.published && (
|
|
63
|
+
<meta property="article:published_time" content={isoDate(props.published)} />
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
{
|
|
67
|
+
article && props.modified && (
|
|
68
|
+
<meta property="article:modified_time" content={isoDate(props.modified)} />
|
|
69
|
+
)
|
|
70
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { pageRobots } from "../lib/page.ts"
|
|
3
|
+
import type { MetadataProps } from "../lib/page.ts"
|
|
4
|
+
|
|
5
|
+
export type Props = Pick<MetadataProps, "index" | "follow">
|
|
6
|
+
|
|
7
|
+
const robots = pageRobots(Astro.props)
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
{robots && <meta name="robots" content={robots} />}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getConfig, localize } from "../lib/config.ts"
|
|
3
|
+
import { pageCanonical, pageDescription, pageImage, pageLocale } from "../lib/page.ts"
|
|
4
|
+
import type { MetadataProps } from "../lib/page.ts"
|
|
5
|
+
import { Schema } from "../lib/schema.ts"
|
|
6
|
+
import { url } from "../lib/url.ts"
|
|
7
|
+
|
|
8
|
+
type Keys =
|
|
9
|
+
| "title"
|
|
10
|
+
| "description"
|
|
11
|
+
| "image"
|
|
12
|
+
| "type"
|
|
13
|
+
| "published"
|
|
14
|
+
| "modified"
|
|
15
|
+
| "author"
|
|
16
|
+
| "canonical"
|
|
17
|
+
| "breadcrumbs"
|
|
18
|
+
| "schema"
|
|
19
|
+
| "locale"
|
|
20
|
+
|
|
21
|
+
export type Props = Pick<MetadataProps, Keys>
|
|
22
|
+
|
|
23
|
+
const props = Astro.props
|
|
24
|
+
const locale = pageLocale(Astro, props)
|
|
25
|
+
const image = pageImage(props, locale)
|
|
26
|
+
|
|
27
|
+
const schema = Schema.graph({
|
|
28
|
+
url: pageCanonical(Astro, props),
|
|
29
|
+
title: props.title ?? localize(getConfig().siteName, locale),
|
|
30
|
+
description: pageDescription(props, locale),
|
|
31
|
+
image: image ? url(image.url) : undefined,
|
|
32
|
+
locale,
|
|
33
|
+
type: props.type,
|
|
34
|
+
published: props.published,
|
|
35
|
+
modified: props.modified,
|
|
36
|
+
author: props.author,
|
|
37
|
+
breadcrumbs: props.breadcrumbs,
|
|
38
|
+
items: props.schema === undefined ? undefined : [props.schema].flat(),
|
|
39
|
+
})
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
{schema && <script is:inline type="application/ld+json" set:html={Schema.stringify(schema)} />}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getConfig } from "../lib/config.ts"
|
|
3
|
+
|
|
4
|
+
const themeColor = getConfig().themeColor
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
{typeof themeColor === "string" && <meta name="theme-color" content={themeColor} />}
|
|
8
|
+
{
|
|
9
|
+
typeof themeColor === "object" && (
|
|
10
|
+
<>
|
|
11
|
+
<meta name="theme-color" content={themeColor.light} media="(prefers-color-scheme: light)" />
|
|
12
|
+
<meta name="theme-color" content={themeColor.dark} media="(prefers-color-scheme: dark)" />
|
|
13
|
+
</>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { fullTitle, pageLocale } from "../lib/page.ts"
|
|
3
|
+
import type { MetadataProps } from "../lib/page.ts"
|
|
4
|
+
|
|
5
|
+
export type Props = Pick<MetadataProps, "title" | "locale">
|
|
6
|
+
|
|
7
|
+
const title = fullTitle(Astro.props.title, pageLocale(Astro, Astro.props))
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
{title && <title>{title}</title>}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getConfig } from "../lib/config.ts"
|
|
3
|
+
import { pageAuthors, pageImage } from "../lib/page.ts"
|
|
4
|
+
import type { MetadataProps } from "../lib/page.ts"
|
|
5
|
+
|
|
6
|
+
export type Props = Pick<MetadataProps, "image" | "author">
|
|
7
|
+
|
|
8
|
+
// Twitter (X) uses the Open Graph title, description and image, so only the tags that
|
|
9
|
+
// are specific to Twitter (X) are necessary
|
|
10
|
+
const twitter = getConfig().twitter
|
|
11
|
+
const card = pageImage(Astro.props) ? (twitter?.card ?? "summary_large_image") : "summary"
|
|
12
|
+
const creator = pageAuthors(Astro.props)
|
|
13
|
+
.map((author) => {
|
|
14
|
+
return typeof author === "string" ? undefined : author.twitter
|
|
15
|
+
})
|
|
16
|
+
.find(Boolean)
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<meta name="twitter:card" content={card} />
|
|
20
|
+
{twitter?.site && <meta name="twitter:site" content={twitter.site} />}
|
|
21
|
+
{creator && <meta name="twitter:creator" content={creator} />}
|
package/src/components/index.ts
CHANGED
|
@@ -1,2 +1,14 @@
|
|
|
1
|
-
export type { HreflangLink,
|
|
1
|
+
export type { HreflangLink, MetadataProps } from "../lib/page.ts"
|
|
2
|
+
export { default as Canonical } from "./Canonical.astro"
|
|
3
|
+
export { default as Description } from "./Description.astro"
|
|
4
|
+
export { default as Favicon } from "./Favicon.astro"
|
|
5
|
+
export { default as Feeds } from "./Feeds.astro"
|
|
6
|
+
export { default as LanguageAlternates } from "./LanguageAlternates.astro"
|
|
7
|
+
export { default as Manifest } from "./Manifest.astro"
|
|
2
8
|
export { default as Metadata } from "./Metadata.astro"
|
|
9
|
+
export { default as OpenGraph } from "./OpenGraph.astro"
|
|
10
|
+
export { default as Robots } from "./Robots.astro"
|
|
11
|
+
export { default as Schema } from "./Schema.astro"
|
|
12
|
+
export { default as ThemeColor } from "./ThemeColor.astro"
|
|
13
|
+
export { default as Title } from "./Title.astro"
|
|
14
|
+
export { default as Twitter } from "./Twitter.astro"
|
package/src/integration.js
CHANGED
|
@@ -8,7 +8,6 @@ import { defaultFaviconFiles, resolveFaviconFiles } from "./lib/favicon-files.js
|
|
|
8
8
|
|
|
9
9
|
const NAME = "@mannisto/astro-metadata"
|
|
10
10
|
|
|
11
|
-
// The site values of the options. The components read them at build time.
|
|
12
11
|
const SITE_KEYS = [
|
|
13
12
|
"siteName",
|
|
14
13
|
"titleTemplate",
|
|
@@ -22,7 +21,6 @@ const SITE_KEYS = [
|
|
|
22
21
|
"schema",
|
|
23
22
|
]
|
|
24
23
|
|
|
25
|
-
// The Lit modules of the dev toolbar app
|
|
26
24
|
const LIT_MODULES = [
|
|
27
25
|
"lit",
|
|
28
26
|
"lit/directives/style-map.js",
|
|
@@ -285,7 +283,6 @@ function findHtmlFiles(dir) {
|
|
|
285
283
|
.sort()
|
|
286
284
|
}
|
|
287
285
|
|
|
288
|
-
// The MIME types of the image files, by their extension
|
|
289
286
|
const MIME_TYPES = {
|
|
290
287
|
".avif": "image/avif",
|
|
291
288
|
".gif": "image/gif",
|
|
@@ -464,7 +461,6 @@ async function checkPages(context, logger) {
|
|
|
464
461
|
}
|
|
465
462
|
}
|
|
466
463
|
|
|
467
|
-
// The path of the check endpoint of the dev server
|
|
468
464
|
const CHECK_PATH = "/__astro-metadata/check"
|
|
469
465
|
|
|
470
466
|
/**
|
|
@@ -546,7 +542,6 @@ export default function metadata(options = {}) {
|
|
|
546
542
|
})
|
|
547
543
|
}
|
|
548
544
|
|
|
549
|
-
// The dev toolbar app runs only in dev, and the `debug` option can turn it off
|
|
550
545
|
const toolbar = command === "dev" && debug.client
|
|
551
546
|
context.root = config.root
|
|
552
547
|
context.site = config.site
|
|
@@ -597,7 +592,6 @@ export default function metadata(options = {}) {
|
|
|
597
592
|
})
|
|
598
593
|
}
|
|
599
594
|
|
|
600
|
-
// One route for each favicon file, all with the same endpoint
|
|
601
595
|
if (favicon) {
|
|
602
596
|
for (const file of favicon.files) {
|
|
603
597
|
injectRoute({
|
package/src/lib/checks.js
CHANGED
|
@@ -102,7 +102,12 @@ export function isNoindex(head) {
|
|
|
102
102
|
* @returns {string}
|
|
103
103
|
*/
|
|
104
104
|
function trimSlash(path) {
|
|
105
|
-
|
|
105
|
+
if (path.length <= 1) return path
|
|
106
|
+
|
|
107
|
+
// A loop and not a regular expression: /\/+$/ is slow for a path with many slashes
|
|
108
|
+
let end = path.length
|
|
109
|
+
while (end > 0 && path[end - 1] === "/") end--
|
|
110
|
+
return path.slice(0, end)
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
/**
|
package/src/lib/manifest.ts
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the most likely ISO 639-1 language and ISO 3166-1 alpha-2 country of a locale,
|
|
3
|
+
* or undefined. A numeric region, e.g. "419" in "es-419", is not a country.
|
|
4
|
+
*/
|
|
5
|
+
function languageAndCountry(locale: string): { language: string; country?: string } | undefined {
|
|
6
|
+
try {
|
|
7
|
+
const parsed = new Intl.Locale(locale)
|
|
8
|
+
// "und" is the undefined language: maximize() changes it to "en"
|
|
9
|
+
if (!parsed.language || parsed.language === "und") return undefined
|
|
10
|
+
|
|
11
|
+
const { language, region } = parsed.maximize()
|
|
12
|
+
if (!/^[a-z]{2}$/.test(language)) return undefined
|
|
13
|
+
return { language, country: region && /^[A-Z]{2}$/.test(region) ? region : undefined }
|
|
14
|
+
} catch {
|
|
15
|
+
return undefined
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns a locale in the format of Open Graph: an ISO 639-1 language and an ISO 3166-1
|
|
21
|
+
* alpha-2 country, e.g. "fi_FI". A locale without a country gets the most likely country
|
|
22
|
+
* of its language: "fi" becomes "fi_FI", "en" becomes "en_US" and "es-419" becomes "es_ES".
|
|
23
|
+
* Returns undefined for a locale without this format, e.g. "x-default" or "fil".
|
|
24
|
+
*
|
|
25
|
+
* @param locale - A locale, e.g. "fi", "en-GB" or "en_GB".
|
|
26
|
+
*/
|
|
27
|
+
export function openGraphLocale(locale: string | undefined): string | undefined {
|
|
28
|
+
if (!locale) return undefined
|
|
29
|
+
|
|
30
|
+
const value = languageAndCountry(locale.replace("_", "-"))
|
|
31
|
+
if (!value) return undefined
|
|
32
|
+
|
|
33
|
+
const country = value.country ?? languageAndCountry(value.language)?.country
|
|
34
|
+
return country ? `${value.language}_${country}` : undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns the other locales of the page in the format of Open Graph, for the
|
|
39
|
+
* `og:locale:alternate` tags. The list has no duplicates and not the locale of the page.
|
|
40
|
+
*
|
|
41
|
+
* @param locale - The locale of the page, e.g. "fi".
|
|
42
|
+
* @param hreflang - The locales of the hreflang links, e.g. ["en", "fi", "x-default"].
|
|
43
|
+
*/
|
|
44
|
+
export function openGraphAlternates(locale: string | undefined, hreflang: string[]): string[] {
|
|
45
|
+
const current = openGraphLocale(locale)
|
|
46
|
+
const alternates = hreflang
|
|
47
|
+
.map((value) => {
|
|
48
|
+
return openGraphLocale(value)
|
|
49
|
+
})
|
|
50
|
+
.filter((value): value is string => {
|
|
51
|
+
return value !== undefined && value !== current
|
|
52
|
+
})
|
|
53
|
+
return [...new Set(alternates)]
|
|
54
|
+
}
|