2026-07-23 01:04:37 +00:00
import Elementary
import Foundation
/// A page of a website: an HTML document with the shared scaffolding assembled around the page's content.
///
/// A conforming page supplies its locale, its title, the stylesheets and scripts it needs, its head metadata, and its content; the protocol assembles the
2026-08-01 17:21:16 +00:00
/// rest of the document around them: the viewport declaration, the summary, canonical, and social card tags, the structured data script, and the
/// metadata followed by the stylesheet links in the head, and the content followed by the script tags in the body.
2026-07-23 01:04:37 +00:00
public protocol Page : HTMLDocument , Sendable {
// MARK: Associated types
/// The type of the page's markup.
associatedtype Content : HTML
/// The type of the page's head metadata markup.
associatedtype Metadata : HTML
// MARK: Properties
/// The version token appended to the page's asset URLs, or `nil` to leave them unversioned.
var assetVersion : String ? { get }
2026-08-01 10:57:29 +00:00
/// The canonical URL the page is served at, rendered as a `link rel="canonical"` tag in the document head, or `nil` (the default) to omit the tag.
var canonicalURL : String ? { get }
2026-07-23 01:04:37 +00:00
/// The page's markup, rendered before the ``scripts``.
@ HTMLBuilder
var content : Content { get }
/// The locale the page content is localized to.
var locale : Locale { get }
2026-08-01 10:57:29 +00:00
/// The markup placed in the document head before the ``stylesheets`` links: icon and manifest links, extra meta tags, and the like.
2026-07-23 01:04:37 +00:00
@ HTMLBuilder
var metadata : Metadata { get }
/// The scripts loaded at the end of the document body, in order.
var scripts : [ any Asset ] { get }
2026-08-01 10:57:29 +00:00
/// The card controlling the page's link previews, rendered as Open Graph and Twitter meta tags in the document head, or `nil` (the default)
/// to omit them.
var socialCard : SocialCard ? { get }
2026-08-01 17:21:16 +00:00
/// The page's structured data, rendered as a JSON-LD script in the document head, or `nil` (the default) to omit it.
var structuredData : StructuredData ? { get }
2026-07-23 01:04:37 +00:00
/// The stylesheets linked in the document head, in order.
var stylesheets : [ any Asset ] { get }
2026-08-01 10:57:29 +00:00
/// The page's summary, rendered as a `meta name="description"` tag in the document head, or `nil` (the default) to omit the tag.
var summary : String ? { get }
2026-07-23 01:04:37 +00:00
}
// MARK: - Implementations
public extension Page {
2026-08-01 10:57:29 +00:00
2026-07-23 01:04:37 +00:00
// MARK: Computed
2026-08-01 10:57:29 +00:00
/// The canonical URL is omitted unless the page provides one.
var canonicalURL : String ? {
nil
}
2026-07-23 01:04:37 +00:00
/// The page ``content`` followed by its ``scripts``.
@ HTMLBuilder
var body : some HTML {
content
2026-08-01 10:57:29 +00:00
2026-07-23 01:04:37 +00:00
for file in scripts {
script (. src ( file . urlPath (
for : . js ,
version : assetVersion
))) {}
}
}
2026-08-01 10:57:29 +00:00
2026-08-01 17:21:16 +00:00
/// The viewport declaration, the ``summary``, ``canonicalURL``, and ``socialCard`` tags and the ``structuredData`` script
/// (when provided), and the ``metadata`` followed by the ``stylesheets`` links, placed in the document head.
2026-07-23 01:04:37 +00:00
///
2026-07-30 06:33:57 +00:00
/// The charset declaration is omitted: Elementary's `HTMLDocument` scaffolding already emits `<meta charset="UTF-8">` before this markup,
/// and HTML5 allows only one.
2026-08-01 17:21:16 +00:00
///
/// The structured data is an inert data block — browsers never execute it, so a site's `Content-Security-Policy` does not apply to it —
/// that search engines read for the organization's name, logo, and profiles.
2026-07-23 01:04:37 +00:00
@ HTMLBuilder
var head : some HTML {
meta (
. name (. viewport ),
. content ( "width=device-width, initial-scale=1" )
)
2026-08-01 10:57:29 +00:00
if let summary {
meta (
. name (. description ),
. content ( summary )
)
}
if let canonicalURL {
link (
. rel ( "canonical" ),
. href ( canonicalURL )
)
}
if let socialCard {
for tag in socialCard . tags {
meta (
. custom (
name : tag . attribute . rawValue ,
value : tag . name . rawValue
),
. content ( tag . content )
)
}
}
2026-07-23 01:04:37 +00:00
2026-08-01 17:21:16 +00:00
if let structuredData {
script (. custom (
name : "type" ,
value : "application/ld+json"
)) {
HTMLRaw ( structuredData . payload )
}
}
2026-07-23 01:04:37 +00:00
metadata
2026-08-01 10:57:29 +00:00
2026-07-23 01:04:37 +00:00
for file in stylesheets {
link (
. rel (. stylesheet ),
. href ( file . urlPath (
for : . css ,
version : assetVersion
))
)
}
}
2026-08-01 10:57:29 +00:00
/// The social card is omitted unless the page provides one.
var socialCard : SocialCard ? {
nil
}
2026-07-23 01:04:37 +00:00
2026-08-01 17:21:16 +00:00
/// The structured data is omitted unless the page provides one.
var structuredData : StructuredData ? {
nil
}
2026-08-01 10:57:29 +00:00
/// The summary is omitted unless the page provides one.
var summary : String ? {
nil
}
2026-07-23 01:04:37 +00:00
}