This PR contains the latest updates from the generic Website template, which have been added while working on #loud-amsterdam. Reviewed-on: #1 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
163 lines
5.8 KiB
Swift
163 lines
5.8 KiB
Swift
import Elementary
|
|
import Foundation
|
|
import Infrastructure
|
|
import Localization
|
|
|
|
/// The site-wide defaults shared by every page of the website.
|
|
extension Page {
|
|
|
|
// MARK: Constants
|
|
|
|
/// The fonts preloaded on every page: one per face the stylesheets actually render.
|
|
///
|
|
/// Listed rather than derived from every WOFF2 in ``StaticFile/all``: a subset gated by a `unicode-range` no page reaches would add a
|
|
/// download that never otherwise happens. Empty until the site ships fonts of its own.
|
|
static var preloadedFonts: [StaticFile] {
|
|
[]
|
|
}
|
|
|
|
/// The territories paired with the site's languages, in Open Graph's `language_TERRITORY` form.
|
|
///
|
|
/// English ships as `en_US`, Open Graph's conventional default. A site serving a language in a territory of its own repoints or extends the
|
|
/// map (`en_NL`, `nl_NL`, …); a language with no entry stays a bare code, which scrapers also accept.
|
|
static var ogLocales: [String: String] {
|
|
["en": "en_US"]
|
|
}
|
|
|
|
// MARK: Computed
|
|
|
|
/// The document language, derived from the page's locale and falling back to the default language.
|
|
var lang: String {
|
|
locale.language.languageCode?.identifier
|
|
?? LanguageList().default
|
|
}
|
|
|
|
/// The locale of the page's social card, in Open Graph's `language_TERRITORY` form where ``ogLocales`` pairs the page's language with a
|
|
/// territory; a language it does not name stays a bare code, which scrapers also accept.
|
|
///
|
|
/// Keyed off ``lang``, so the card's locale and the document's `lang` attribute never describe the document differently. Nothing reads it
|
|
/// until a page supplies a `socialCard` — like ``preloadedFonts``, it is a hook a generated site fills in.
|
|
var ogLocale: String {
|
|
Self.ogLocales[lang] ?? lang
|
|
}
|
|
|
|
/// The site-wide head metadata; a page that adds tags of its own composes ``siteMetadata`` rather than replacing it.
|
|
var metadata: some HTML {
|
|
siteMetadata
|
|
}
|
|
|
|
/// The `hreflang` alternates tying the page's language editions together, or nothing without an origin — the annotations require absolute URLs.
|
|
///
|
|
/// Nothing is emitted for a single-language site either: a set naming one edition tells a search engine nothing it cannot already see.
|
|
/// `x-default` points at the catalog's default language, whose bare URL negotiates the language and so is the right landing for everyone
|
|
/// unmatched — it stays the catalog's default even when `languages` names a subset.
|
|
/// - Parameters:
|
|
/// - origin: the site's public origin, or `nil` to emit nothing.
|
|
/// - path: the page's bare (default-language) path.
|
|
/// - languages: the languages the page is published in; every catalog language by default. A page translated into only some of them
|
|
/// narrows the set, so it never advertises an edition that does not exist.
|
|
/// - Returns: one `alternate` link per language, followed by the `x-default` link.
|
|
@HTMLBuilder
|
|
func languageAlternates(
|
|
origin: String?,
|
|
path: String,
|
|
languages: [Language] = Language.all
|
|
) -> some HTML {
|
|
if let origin, languages.count > 1 {
|
|
ForEach(languages) { language in
|
|
link(
|
|
.rel("alternate"),
|
|
.custom(
|
|
name: "hreflang",
|
|
value: language.identifier
|
|
),
|
|
.href(language.url(
|
|
origin: origin,
|
|
path: path
|
|
))
|
|
)
|
|
}
|
|
link(
|
|
.rel("alternate"),
|
|
.custom(
|
|
name: "hreflang",
|
|
value: "x-default"
|
|
),
|
|
.href(Language.default.url(
|
|
origin: origin,
|
|
path: path
|
|
))
|
|
)
|
|
}
|
|
}
|
|
|
|
/// The ``preloadedFonts`` links, then the icon, manifest, and theme colour metadata shared by every page of the website.
|
|
///
|
|
/// A preload URL must match the stylesheet's `@font-face` source exactly — unversioned, with `crossorigin` — or the browser fetches the
|
|
/// font twice.
|
|
@HTMLBuilder
|
|
var siteMetadata: some HTML {
|
|
for file in Self.preloadedFonts {
|
|
link(
|
|
.rel("preload"),
|
|
.href(file.urlPath(for: .woff2)),
|
|
.as(.font),
|
|
.custom(
|
|
name: "type",
|
|
value: AssetExtension.woff2.contentType
|
|
),
|
|
.crossorigin(.anonymous)
|
|
)
|
|
}
|
|
link(
|
|
.rel(.icon),
|
|
.href(StaticFile.favicon.urlPath(
|
|
for: .ico,
|
|
version: assetVersion
|
|
)),
|
|
.custom(
|
|
name: "sizes",
|
|
value: "any"
|
|
)
|
|
)
|
|
link(
|
|
.rel(.icon),
|
|
.href(StaticFile.icon.urlPath(
|
|
for: .svg,
|
|
version: assetVersion
|
|
)),
|
|
.custom(
|
|
name: "type",
|
|
value: "image/svg+xml"
|
|
)
|
|
)
|
|
link(
|
|
.rel("apple-touch-icon"),
|
|
.href(StaticFile.appleTouchIcon.urlPath(
|
|
for: .png,
|
|
version: assetVersion
|
|
))
|
|
)
|
|
link(
|
|
.rel("manifest"),
|
|
.href(StaticFile.site.urlPath(
|
|
for: .webmanifest,
|
|
version: assetVersion
|
|
))
|
|
)
|
|
meta(
|
|
.name("theme-color"),
|
|
.content("#fafafa")
|
|
)
|
|
meta(
|
|
.name("theme-color"),
|
|
.content("#0c0710"),
|
|
.custom(
|
|
name: "media",
|
|
value: "(prefers-color-scheme: dark)"
|
|
)
|
|
)
|
|
}
|
|
|
|
}
|