Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Examples/APIDocsSite/Sources/APIDocsSite/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let site = KilnSite(
theme: .default(
palette: .autoLightDark(primary: .black, accent: .blue),
logo: "assets/logo.svg",
favicon: "assets/logo.svg"
favicons: [.init(type: .svg, path: "assets/logo.svg")]
),
docc: DocCSite(
packages: [
Expand Down
5 changes: 4 additions & 1 deletion Examples/ExampleSite/Content/latest/guides/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ translations, fallback, and UI strings.
theme: .default(
palette: .autoLightDark(primary: .black, accent: .blue),
logo: "assets/logo.svg",
favicon: "assets/logo.svg"
favicons: [
.init(type: .svg, path: "assets/logo.svg"),
.init(type: .png(.x16)),
]
)
```

Expand Down
19 changes: 11 additions & 8 deletions Examples/ExampleSite/Content/latest/guides/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,22 @@ tweak it with options, or replace any part with your own templates.
theme: .default(
palette: .autoLightDark(primary: .black, accent: .blue),
logo: "assets/logo.svg",
favicon: "assets/logo.svg",
favicons: [
.init(type: .svg, path: "assets/logo.svg"),
.init(type: .png(.x16)),
],
fonts: .init(text: "Inter", code: "JetBrains Mono"),
features: [.backToTop, .searchHighlight]
)
```

| Option | Purpose |
| ---------- | ------- |
| `palette` | `Palette` with `primary`/`accent` `Color`s and a default mode (`.auto`/`.light`/`.dark`). |
| `logo` | Header logo (content-relative path). |
| `favicon` | Site favicon. |
| `fonts` | `Fonts(text:code:)` for body and code text. |
| `features` | Opt-in extras: `.searchSuggest`, `.searchHighlight`, `.navigationTabs`, `.backToTop`. |
| Option | Purpose | Default values |
|------------|-------------------------------------------------------------------------------------------------------------|-----------------------------------------|
| `palette` | `Palette` with `primary`/`accent` `Color`s and a default mode. | `.auto`/`.light`/`.dark` |
| `logo` | Header logo (content-relative path). | none |
| `favicons` | `.svg` for modern browsers, `.ico\|.png(.x16\|.x32)` for most browsers, `.png(.x180)` for apple-touch-icon. | path : assets/favicon\[-\(size)\].(ext) |
| `fonts` | `Fonts(text:code:)` for body and code text. | |
| `features` | Opt-in extras: `.searchSuggest`, `.searchHighlight`, `.navigationTabs`, `.backToTop`. | `.searchSuggest`, `.searchHighlight` |

`Color` has presets (`.black`, `.blue`, `.indigo`, …) or accepts any CSS string
via `Color("#2f6feb")`.
Expand Down
2 changes: 1 addition & 1 deletion Examples/ExampleSite/Sources/ExampleSite/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ let site = KilnSite(
directory: "Theme",
palette: .autoLightDark(primary: .black, accent: .blue),
logo: "assets/logo.svg",
favicon: "assets/logo.svg"
favicons: [.init(type: .svg, path: "assets/logo.svg")]
),
social: [
.init(icon: .github, link: "https://github.com/brokenhandsio/kiln"),
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ A few more things to know:
preview at `http://127.0.0.1:8080/docs/` mirrors production.

**Theme** options: `palette` (`Palette` with `primary`/`accent` `Color`s and a
`.auto`/`.light`/`.dark` default mode), `logo`, `favicon`, `fonts`
`.auto`/`.light`/`.dark` default mode), `logo`, `favicons`, `fonts`
(`Fonts(text:code:)`), and `features` (`.searchSuggest`, `.searchHighlight`,
`.navigationTabs`, `.backToTop`). `Color` has presets (`.black`, `.blue`,
`.indigo`, …) or accepts any CSS string via `Color("#2f6feb")`.
Expand Down
14 changes: 14 additions & 0 deletions Sources/Kiln/Configuration/FavIcon.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// Paths (relative to the content directory's assets) to a logo image.
public struct FavIcon: Sendable
{
public var type: FavIconType
public var path: String

public init(
type: FavIconType = .svg,
path: String? = nil
) {
self.type = type
self.path = path ?? type.defaultPath()
}
}
50 changes: 50 additions & 0 deletions Sources/Kiln/Configuration/FavIconType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
public enum FavIconType: Sendable, Equatable {
case svg
case png(PNGSize)
case ico

public enum PNGSize: Int, Sendable {
case x16 = 16
case x32 = 32
case x180 = 180
}

public func ext() -> String {
switch self {
case .svg: return "svg"
case .png: return "png"
case .ico: return "ico"
}
}

public func defaultPath() -> String {
switch self {
case .svg: return "assets/favicon.svg"
case .png(let size): return "assets/favicon-\(size.rawValue).png"
case .ico: return "assets/favicon.ico"
}
}

public func rel() -> String {
switch self {
case .png(.x180): return "apple-touch-icon"
case .svg, .ico, .png: return "icon"
}
}

public func sizes() -> String? {
switch self {
case .svg, .ico: return "any"
case .png(let size): return "\(size.rawValue)x\(size.rawValue)"
}
}

public func mimeType() -> String? {
switch self {
case .svg: return "image/svg+xml"
case .png(.x180), .ico: return nil
case .png: return "image/png"
}
}
}

15 changes: 7 additions & 8 deletions Sources/Kiln/Configuration/Theme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ public struct Theme: Sendable {
public var palette: Palette
/// Path (relative to the content directory's assets) to a logo image.
public var logo: String?
/// Path to a favicon image.
public var favicon: String?
public var favicons: [FavIcon]
public var fonts: Fonts?
public var features: Set<ThemeFeature>

Expand All @@ -70,15 +69,15 @@ public struct Theme: Sendable {
sharedLayers: [URL] = [],
palette: Palette = Palette(),
logo: String? = nil,
favicon: String? = nil,
favicons: [FavIcon] = [],
fonts: Fonts? = nil,
features: Set<ThemeFeature> = [.searchSuggest, .searchHighlight]
) {
self.source = source
self.sharedLayers = sharedLayers
self.palette = palette
self.logo = logo
self.favicon = favicon
self.favicons = favicons
self.fonts = fonts
self.features = features
}
Expand All @@ -88,11 +87,11 @@ public struct Theme: Sendable {
sharedLayers: [URL] = [],
palette: Palette = Palette(),
logo: String? = nil,
favicon: String? = nil,
favicons: [FavIcon] = [],
fonts: Fonts? = nil,
features: Set<ThemeFeature> = [.searchSuggest, .searchHighlight]
) -> Theme {
Theme(source: .default, sharedLayers: sharedLayers, palette: palette, logo: logo, favicon: favicon, fonts: fonts, features: features)
Theme(source: .default, sharedLayers: sharedLayers, palette: palette, logo: logo, favicons: favicons, fonts: fonts, features: features)
}

/// A theme that overrides the bundled default with your own templates/assets.
Expand All @@ -101,10 +100,10 @@ public struct Theme: Sendable {
sharedLayers: [URL] = [],
palette: Palette = Palette(),
logo: String? = nil,
favicon: String? = nil,
favicons: [FavIcon] = [],
fonts: Fonts? = nil,
features: Set<ThemeFeature> = [.searchSuggest, .searchHighlight]
) -> Theme {
Theme(source: .custom(directory: directory), sharedLayers: sharedLayers, palette: palette, logo: logo, favicon: favicon, fonts: fonts, features: features)
Theme(source: .custom(directory: directory), sharedLayers: sharedLayers, palette: palette, logo: logo, favicons: favicons, fonts: fonts, features: features)
}
}
13 changes: 11 additions & 2 deletions Sources/Kiln/Rendering/RenderContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ struct RenderContext {
var baseURL: String
/// The site's mount path (e.g. `/docs`), or `""` when served from the domain
/// root. Prefixed onto hard-coded root-relative links in templates (theme
/// assets, logo, favicon, home links).
/// assets, logo, favicons, home links).
var basePath: String = ""

var pageTitle: String
Expand Down Expand Up @@ -219,7 +219,7 @@ struct RenderContext {
"author": .string(site.author),
"copyright": .string(site.copyright),
"logo": .string(site.theme.logo),
"favicon": .string(site.theme.favicon),
"favicons": .array(site.theme.favicons.map(Self.favIconData)),
"social": .array(site.social.map(Self.socialData)),
"extraCSS": .array(site.extraCSS.map { .string($0) }),
"extraJS": .array(site.extraJavaScript.map { .string($0) }),
Expand Down Expand Up @@ -279,6 +279,15 @@ struct RenderContext {
])
}

private static func favIconData(_ favicon: FavIcon) -> LeafData {
.dictionary([
"path": .string(favicon.path),
"rel": .string(favicon.type.rel()),
"sizes": .string(favicon.type.sizes()),
"mimeType": .string(favicon.type.mimeType()),
])
}

// MARK: Language

// MARK: Localised strings
Expand Down
3 changes: 2 additions & 1 deletion Sources/Kiln/Resources/DefaultTheme/templates/base.leaf
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
#if(page.imageURL):<meta name="twitter:image" content="#(page.imageURL)">
<meta name="twitter:image:alt" content="#if(page.isHome):#(site.name)#else:#(page.title)#endif">#endif

#if(site.favicon):<link rel="icon" href="#(basePath)/#(site.favicon)">#endif
#for(favicon in site.favicons):<link rel="#(favicon.rel)" sizes="#(favicon.sizes)"#if(favicon.mimeType): type="#(favicon.mimeType)"#endif href="#(basePath)/#(favicon.path)">
#endfor

#for(alt in languages):<link rel="alternate" hreflang="#(alt.locale)" href="#(alt.absoluteURL)">
#if(alt.isDefault):<link rel="alternate" hreflang="x-default" href="#(alt.absoluteURL)">
Expand Down
35 changes: 35 additions & 0 deletions Tests/KilnTests/BuildTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ struct BuildTests {
description: "Fixture site description.",
image: "assets/card.png",
twitterSite: "@fixture",
theme: .default(favicons: [
.init(type: .ico, path: "assets/customFaviconName.ico"),
.init(type: .ico),
.init(type: .svg, path: "assets/customFaviconName.svg"),
.init(type: .svg),
.init(type: .png(.x16), path: "assets/customFaviconName16.png"),
.init(type: .png(.x16)),
.init(type: .png(.x32), path: "assets/customFaviconName32.png"),
.init(type: .png(.x32)),
.init(type: .png(.x180), path: "assets/customFaviconName180.png"),
.init(type: .png(.x180)),
]),
carbonAds: .init(serve: "TESTSERVE", placement: "fixture"),
languages: languages ?? [
.init(.english, isDefault: true),
Expand Down Expand Up @@ -341,4 +353,27 @@ struct BuildTests {
#expect(index.docs.count == 3)
#expect(index.docs.contains { $0.title == "Home" })
}

@Test("Pages include favicons")
func favicons() async throws {
let output = try await buildFixture()
defer { try? FileManager.default.removeItem(at: output) }

let home = try read(output.appendingPathComponent("index.html"))
// SVG.
#expect(home.contains("<link rel=\"icon\" sizes=\"any\" type=\"image/svg+xml\" href=\"/assets/favicon.svg\">"))
#expect(home.contains("<link rel=\"icon\" sizes=\"any\" type=\"image/svg+xml\" href=\"/assets/customFaviconName.svg\">"))
// ICO.
#expect(home.contains("<link rel=\"icon\" sizes=\"any\" href=\"/assets/favicon.ico\">"))
#expect(home.contains("<link rel=\"icon\" sizes=\"any\" href=\"/assets/customFaviconName.ico\">"))
// PNG 16
#expect(home.contains("<link rel=\"icon\" sizes=\"16x16\" type=\"image/png\" href=\"/assets/favicon-16.png\">"))
#expect(home.contains("<link rel=\"icon\" sizes=\"16x16\" type=\"image/png\" href=\"/assets/customFaviconName16.png\">"))
// PNG 32
#expect(home.contains("<link rel=\"icon\" sizes=\"32x32\" type=\"image/png\" href=\"/assets/favicon-32.png\">"))
#expect(home.contains("<link rel=\"icon\" sizes=\"32x32\" type=\"image/png\" href=\"/assets/customFaviconName32.png\">"))
// PNG 180
#expect(home.contains("<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/assets/favicon-180.png\">"))
#expect(home.contains("<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/assets/customFaviconName180.png\">"))
}
}
Loading