Improve asset handling

This commit is contained in:
Christoph Hagen
2024-12-16 15:36:58 +01:00
parent 31d1ecb8bd
commit b22b76fd32
21 changed files with 264 additions and 85 deletions

View File

@ -8,27 +8,12 @@ final class FeedPageGenerator {
self.content = content
}
var swiperIncludes: [HeaderElement] {
var result = [HeaderElement]()
private func includeSwiper(in headers: inout Set<HeaderElement>) {
if let swiperCss = content.settings.posts.swiperCssFile {
result.append(.css(swiperCss))
} else {
#warning("Add warning message")
headers.insert(.css(file: swiperCss, order: HeaderElement.swiperCssFileOrder))
}
if let swiperJs = content.settings.posts.swiperJsFile {
result.append(.js(file: swiperJs, defer: true))
} else {
#warning("Add warning message")
}
return result
}
var defaultHeaders: [HeaderElement] {
if let header = content.settings.posts.defaultCssFile {
return [.css(header)]
} else {
#warning("Add warning message")
return []
headers.insert(.js(file: swiperJs, defer: true))
}
}
@ -39,10 +24,11 @@ final class FeedPageGenerator {
showTitle: Bool,
pageNumber: Int,
totalPages: Int) -> String {
var headers = defaultHeaders
var headers = content.defaultPageHeaders
var footer = ""
if posts.contains(where: { $0.images.count > 1 }) {
headers += swiperIncludes
// Sort swiper style sheet before default style sheet
includeSwiper(in: &headers)
footer = swiperInitScript(posts: posts)
}
@ -68,15 +54,14 @@ final class FeedPageGenerator {
}
func swiperInitScript(posts: [FeedEntryData]) -> String {
var result = "<script>"
var result = "<script> window.onload = () => { "
for post in posts {
guard post.images.count > 1 else {
continue
}
result += ImageGallery.swiperInit(id: post.entryId)
}
result += "</script>"
result += "}; </script>"
return result
}
}

View File

@ -1,33 +1,135 @@
#warning("Add remaining header elements")
// <meta name="msapplication-TileColor" content="#da532c">
// <meta name="theme-color" content="#ffffff">
// <meta name="msapplication-config" content="/assets/icons/browserconfig.xml?v=1">
// TODO: Move to settings?
extension HeaderElement {
static let swiperCssFileOrder = 41
static let defaultCssFileOrder = 42
static let audioPlayerCssOrder = 43
}
enum HeaderElement {
case css(FileResource)
/// Order: 10-19
case icon(file: FileResource, size: Int, rel: String)
/// Order: From 40-99, lower numbers appear first
case css(file: FileResource, order: Int)
/// Order: 20-29
case js(file: FileResource, defer: Bool)
/// Order: 30-39
case jsModule(FileResource)
case author(String)
case title(String)
case description(String)
case charset
case viewport
case robots
var order: Int {
switch self {
case .charset:
return 1
case .robots:
return 2
case .viewport:
return 3
case .icon:
return 10
case .css(_, let order):
return order
case .js:
return 20
case .jsModule:
return 30
case .author:
return 100
case .title:
return 101
case .description:
return 102
}
}
}
extension HeaderElement: Hashable {
}
extension HeaderElement: Comparable {
static func < (lhs: HeaderElement, rhs: HeaderElement) -> Bool {
lhs.order < rhs.order
}
}
extension HeaderElement {
var content: String {
switch self {
case .css(let file):
case .icon(let file, let size, let rel):
return "<link rel='\(rel)' sizes='\(size)x\(size)' href='\(file.assetUrl)'>"
case .css(let file, _):
return "<link rel='stylesheet' href='\(file.assetUrl)' />"
case .js(let file, let deferred):
let deferText = deferred ? " defer" : ""
return "<script src='\(file.assetUrl)'\(deferText)></script>"
case .jsModule(let file):
return "<script type='module' src='\(file.assetUrl)'></script>"
case .author(let author):
return "<meta name='author' content='\(author)'>"
case .title(let title):
return "<title>\(title)</title>"
case .description(let description):
return "<meta name='description' content='\(description)'>"
return "<meta name='description' content=\"\(description)\">"
case .charset:
return "<meta charset='utf-8' />"
case .viewport:
return "<meta name='viewport' content='width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1' />"
case .robots:
return "<meta name='robots' content='noindex'>"
}
}
}
extension HeaderElement: CustomStringConvertible {
var description: String {
switch self {
case .icon(let file, _, _):
return file.description
case .css(let file, _):
return file.description
case .js(let file, let deferred):
return file.description + (deferred ? " (deferred)" : "")
case .jsModule(let file):
return file.description
case .author:
return "author"
case .title:
return "title"
case .description:
return "description"
case .charset:
return "charset"
case .viewport:
return "viewport"
case .robots:
return "robots"
}
}
}

View File

@ -1,5 +1,5 @@
enum HeaderFile: Int {
enum KnownHeaderElement: Int {
case codeHightlighting = 4
@ -23,7 +23,7 @@ enum HeaderFile: Int {
}
case .audioPlayerCss:
if let file = content.settings.pages.audioPlayerCssFile {
return .css(file)
return .css(file: file, order: HeaderElement.audioPlayerCssOrder)
}
case .audioPlayerJs:
if let file = content.settings.pages.audioPlayerJsFile {
@ -34,13 +34,27 @@ enum HeaderFile: Int {
}
}
extension HeaderFile: Comparable {
extension KnownHeaderElement: Comparable {
static func < (lhs: HeaderFile, rhs: HeaderFile) -> Bool {
static func < (lhs: KnownHeaderElement, rhs: KnownHeaderElement) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
typealias RequiredHeaders = Set<HeaderFile>
extension KnownHeaderElement: CustomStringConvertible {
var description: String {
switch self {
case .codeHightlighting:
return "code-highlighting"
case .modelViewer:
return "model-viewer"
case .audioPlayerCss:
return "audio-player-css"
case .audioPlayerJs:
return "audio-player-js"
}
}
}

View File

@ -2,10 +2,23 @@ import Foundation
final class LocalizedWebsiteGenerator {
private let content: Content
let language: ContentLanguage
private let imageGenerator: ImageGenerator
private let localizedPostSettings: LocalizedPostSettings
init(content: Content, language: ContentLanguage) {
self.language = language
self.content = content
self.localizedPostSettings = content.settings.localized(in: language)
self.imageGenerator = ImageGenerator(
storage: content.storage,
relativeImageOutputPath: content.settings.paths.imagesOutputFolderPath)
}
private var outputDirectory: URL {
content.settings.outputDirectory
}
@ -18,19 +31,6 @@ final class LocalizedWebsiteGenerator {
CGFloat(content.settings.posts.contentWidth)
}
private let content: Content
private let imageGenerator: ImageGenerator
init(content: Content, language: ContentLanguage) {
self.language = language
self.content = content
self.localizedPostSettings = content.settings.localized(in: language)
self.imageGenerator = ImageGenerator(
storage: content.storage,
relativeImageOutputPath: content.settings.paths.imagesOutputFolderPath)
}
func generateWebsite(callback: (String) -> Void) -> Bool {
guard imageGenerator.prepareForGeneration() else {
return false

View File

@ -45,7 +45,7 @@ final class PageGenerationResults: ObservableObject {
var invalidCommands: [(command: ShorthandMarkdownKey?, markdown: String)] = []
@Published
var requiredHeaders: RequiredHeaders = []
var requiredHeaders: Set<KnownHeaderElement> = []
@Published
var requiredFooters: Set<String> = []

View File

@ -9,14 +9,15 @@ final class PageGenerator {
self.imageGenerator = imageGenerator
}
func makeHeaders(requiredItems: [HeaderFile]) -> [HeaderElement] {
var result = [HeaderElement]()
private func makeHeaders(requiredItems: Set<KnownHeaderElement>) -> Set<HeaderElement> {
var result = content.defaultPageHeaders
for item in requiredItems {
guard let header = item.header(content: content) else {
print("Missing header \(item)")
#warning("Add warning on missing file assignment")
continue
}
result.append(header)
result.insert(header)
}
return result
}
@ -39,7 +40,8 @@ final class PageGenerator {
url: content.absoluteUrlToTag(tag, language: language))
}
let headers = makeHeaders(requiredItems: contentGenerator.results.requiredHeaders.sorted())
let headers = makeHeaders(requiredItems: contentGenerator.results.requiredHeaders)
print("Headers for page: \(headers)")
let fullPage = ContentPage(
language: language,