import Elementary import Foundation import Localization /// A page of the website: an HTML document with the shared scaffolding assembled around the page's content. /// /// A conforming page supplies its locale, its localized title, the stylesheets and scripts it needs, and its content; the protocol assembles the rest of the /// document around them: the metadata, stylesheet, icon, and manifest links in the head, the content followed by the script tags in the body, and the /// document language derived from the locale. protocol Page: HTMLDocument, Sendable { // MARK: Associated types /// The type of the page's markup. associatedtype Content: HTML // MARK: Properties /// 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 } /// The scripts loaded at the end of the document body, in order. var scripts: [StaticFile] { get } /// The stylesheets linked in the document head, in order. var stylesheets: [StaticFile] { get } } // MARK: - Implementations extension Page { // MARK: Computed /// The page ``content`` followed by its ``scripts``. @HTMLBuilder var body: some HTML { content for file in scripts { script(.src(file.urlPath(for: .js))) {} } } /// The metadata, ``stylesheets``, icon, and manifest links placed in the document head. @HTMLBuilder var head: some HTML { meta(.charset(.utf8)) meta( .name(.viewport), .content("width=device-width, initial-scale=1") ) for file in stylesheets { link( .rel(.stylesheet), .href(file.urlPath(for: .css)) ) } link( .rel(.icon), .href(StaticFile.favicon.urlPath(for: .ico)), .custom( name: "sizes", value: "any" ) ) link( .rel(.icon), .href(StaticFile.icon.urlPath(for: .svg)), .custom( name: "type", value: "image/svg+xml" ) ) link( .rel("apple-touch-icon"), .href(StaticFile.appleTouchIcon.urlPath(for: .png)) ) link( .rel("manifest"), .href(StaticFile.site.urlPath(for: .webmanifest)) ) meta( .name("theme-color"), .content("#fafafa") ) meta( .name("theme-color"), .content("#0c0710"), .custom( name: "media", value: "(prefers-color-scheme: dark)" ) ) } /// The document language, derived from the page's locale and falling back to the default language. var lang: String { locale.language.languageCode?.identifier ?? LanguageList(bundle: .module).default } }