Add tag overview, improve assets

This commit is contained in:
Christoph Hagen
2024-12-15 21:20:12 +01:00
parent 8a3a0f1797
commit 1e67a99866
59 changed files with 1301 additions and 480 deletions

View File

@ -0,0 +1,89 @@
import Foundation
final class FeedPageGenerator {
let content: Content
init(content: Content) {
self.content = content
}
func navigationBar(in language: ContentLanguage) -> [NavigationBar.Link] {
content.settings.navigationItems.map {
.init(text: $0.title(in: language),
url: $0.absoluteUrl(in: language))
}
}
var swiperIncludes: [HeaderElement] {
var result = [HeaderElement]()
if let swiperCss = content.settings.posts.swiperCssFile {
result.append(.css(swiperCss))
} else {
#warning("Add warning message")
}
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 []
}
}
func generatePage(language: ContentLanguage,
posts: [FeedEntryData],
title: String,
description: String,
showTitle: Bool,
pageNumber: Int,
totalPages: Int) -> String {
var headers = defaultHeaders
var footer = ""
if posts.contains(where: { $0.images.count > 1 }) {
headers += swiperIncludes
footer = swiperInitScript(posts: posts)
}
let page = GenericPage(
language: language,
title: title,
description: description,
links: navigationBar(in: language),
headers: headers,
additionalFooter: footer) { content in
if showTitle {
content += "<h1>\(title)</h1>"
}
for post in posts {
content += FeedEntry(data: post).content
}
if totalPages > 1 {
content += PostFeedPageNavigation(currentPage: pageNumber, numberOfPages: totalPages, language: language).content
}
}
return page.content
}
func swiperInitScript(posts: [FeedEntryData]) -> String {
var result = "<script>"
for post in posts {
guard post.images.count > 1 else {
continue
}
result += ImageGallery.swiperInit(id: post.entryId)
}
result += "</script>"
return result
}
}

View File

@ -0,0 +1,33 @@
enum HeaderElement {
case css(FileResource)
case js(file: FileResource, defer: Bool)
case jsModule(FileResource)
case title(String)
case description(String)
case charset
case viewport
}
extension HeaderElement {
var content: String {
switch self {
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 .title(let title):
return "<title>\(title)</title>"
case .description(let 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' />"
}
}
}

View File

@ -23,9 +23,9 @@ final class LocalizedWebsiteGenerator {
private let imageGenerator: ImageGenerator
private var navigationBarLinks: [NavigationBar.Link] {
content.settings.navigationTags.map {
let localized = $0.localized(in: language)
return .init(text: localized.name, url: content.absoluteUrlToTag($0, language: language))
content.settings.navigationItems.map {
.init(text: $0.title(in: language),
url: $0.absoluteUrl(in: language))
}
}
@ -46,6 +46,7 @@ final class LocalizedWebsiteGenerator {
return false
}
#warning("Generate content pages")
#warning("Generate tag overview page")
guard generateTagPages() else {
return false
}
@ -94,7 +95,7 @@ final class LocalizedWebsiteGenerator {
}
private func generatePagesFolderIfNeeded() -> Bool {
let relativePath = content.settings.pages.pageUrlPrefix
let relativePath = content.settings.paths.pagesOutputFolderPath
return content.storage.write(in: .outputPath) { folder in
let outputFile = folder.appendingPathComponent(relativePath, isDirectory: true)
@ -130,7 +131,7 @@ final class LocalizedWebsiteGenerator {
return true
}
let path = page.absoluteUrl(for: language) + ".html"
let path = page.absoluteUrl(in: language) + ".html"
guard save(content, to: path) else {
print("Failed to save page")
return false

View File

@ -78,7 +78,7 @@ struct AudioPlayerCommandProcessor: CommandProcessor {
let footerScript = AudioPlayerScript(items: amplitude).content
results.requiredFooters.insert(footerScript)
results.requiredHeaders.insert(.audioPlayerCss)
results.requiredHeaders.insert(.amplitude)
results.requiredHeaders.insert(.audioPlayerJs)
results.requiredIcons.formUnion([
.audioPlayerClose,

View File

@ -101,7 +101,7 @@ final class PageContentParser {
return markdown.between("[", and: "]")
}
results.linkedPages.insert(page)
let pagePath = page.absoluteUrl(for: language)
let pagePath = page.absoluteUrl(in: language)
return html.replacingOccurrences(of: textToChange, with: pagePath)
}
@ -119,12 +119,15 @@ final class PageContentParser {
return html.replacingOccurrences(of: textToChange, with: tagPath)
}
private func handleHTML(_: String, markdown: Substring) -> String {
let result = String(markdown)
findImages(in: result)
findLinks(in: result)
findSourceSets(in: result)
return result
private func handleHTML(html: String, _: Substring) -> String {
findResourcesInHtml(html: html)
return html
}
private func findResourcesInHtml(html: String) {
findImages(in: html)
findLinks(in: html)
findSourceSets(in: html)
}
private func findImages(in markdown: String) {
@ -298,7 +301,7 @@ final class PageContentParser {
results.files.insert(image)
let caption = arguments.count == 2 ? arguments[1] : nil
let altText = image.getDescription(for: language)
let altText = image.localized(in: language)
let path = image.absoluteUrl
@ -403,7 +406,9 @@ final class PageContentParser {
results.missing(file: fileId, markdown: markdown)
return ""
}
return file.textContent()
let content = file.textContent()
findResourcesInHtml(html: content)
return content
}
/**
@ -439,7 +444,7 @@ final class PageContentParser {
}
let localized = page.localized(in: language)
let url = page.absoluteUrl(for: language)
let url = page.absoluteUrl(in: language)
let title = localized.linkPreviewTitle ?? localized.title
let description = localized.linkPreviewDescription ?? ""
@ -450,7 +455,7 @@ final class PageContentParser {
return RelatedPageLink.Image(
url: image.absoluteUrl,
description: image.getDescription(for: language),
description: image.localized(in: language),
size: size)
}
@ -478,7 +483,7 @@ final class PageContentParser {
}
let localized = tag.localized(in: language)
let url = tag.absoluteUrl(for: language)
let url = tag.absoluteUrl(in: language)
let title = localized.name
let description = localized.description ?? ""
@ -489,7 +494,7 @@ final class PageContentParser {
return RelatedPageLink.Image(
url: image.absoluteUrl,
description: image.getDescription(for: language),
description: image.localized(in: language),
size: size)
}
@ -522,7 +527,7 @@ final class PageContentParser {
results.files.insert(file)
results.requiredHeaders.insert(.modelViewer)
let description = file.getDescription(for: language)
let description = file.localized(in: language)
return ModelViewer(file: file.absoluteUrl, description: description).content
}
@ -554,7 +559,7 @@ final class PageContentParser {
return PartialSvgImage(
imagePath: image.absoluteUrl,
altText: image.getDescription(for: language),
altText: image.localized(in: language),
x: x,
y: y,
width: partWidth,

View File

@ -12,6 +12,18 @@ final class PageGenerator {
self.navigationBarLinks = navigationBarLinks
}
func makeHeaders(requiredItems: [HeaderFile]) -> [HeaderElement] {
var result = [HeaderElement]()
for item in requiredItems {
guard let header = item.header(content: content) else {
#warning("Add warning on missing file assignment")
continue
}
result.append(header)
}
return result
}
func generate(page: Page, language: ContentLanguage) throws -> (page: String, results: PageGenerationResults) {
let contentGenerator = PageContentParser(
content: content,
@ -30,9 +42,8 @@ final class PageGenerator {
url: content.absoluteUrlToTag(tag, language: language))
}
let headers = AdditionalPageHeaders(
headers: contentGenerator.results.requiredHeaders,
assetPath: content.settings.pages.javascriptFilesPath)
let headers = makeHeaders(requiredItems: contentGenerator.results.requiredHeaders.sorted())
let fullPage = ContentPage(
language: language,
dateString: page.dateText(in: language),
@ -42,7 +53,7 @@ final class PageGenerator {
description: localized.linkPreviewDescription ?? "",
navigationBarLinks: navigationBarLinks,
pageContent: pageContent,
headers: headers.content,
headers: headers,
footers: contentGenerator.results.requiredFooters.sorted(),
icons: contentGenerator.results.requiredIcons)
.content

View File

@ -8,6 +8,7 @@ final class PostListPageGenerator {
private let imageGenerator: ImageGenerator
#warning("Get from settings")
private let navigationBarLinks: [NavigationBar.Link]
private let showTitle: Bool
@ -62,7 +63,7 @@ final class PostListPageGenerator {
let linkUrl = post.linkedPage.map {
FeedEntryData.Link(
url: $0.absoluteUrl(for: language),
url: $0.absoluteUrl(in: language),
text: language == .english ? "View" : "Anzeigen") // TODO: Add to settings
}
@ -81,16 +82,17 @@ final class PostListPageGenerator {
images: localized.images.map(createImageSet))
}
let feed = PageInFeed(
let feedPageGenerator = FeedPageGenerator(content: content)
let fileContent = feedPageGenerator.generatePage(
language: language,
posts: posts,
title: pageTitle,
showTitle: showTitle,
description: pageDescription,
navigationBarLinks: bar,
showTitle: showTitle,
pageNumber: pageIndex,
totalPages: pageCount,
posts: posts)
let fileContent = feed.content
totalPages: pageCount)
if pageIndex == 1 {
return save(fileContent, to: "\(pageUrlPrefix).html")
} else {
@ -107,7 +109,7 @@ final class PostListPageGenerator {
rawImagePath: image.absoluteUrl,
width: Int(mainContentMaximumWidth),
height: Int(mainContentMaximumWidth),
altText: image.getDescription(for: language))
altText: image.localized(in: language))
}
private func save(_ content: String, to relativePath: String) -> Bool {

View File

@ -1,22 +1,46 @@
enum HeaderFile: String {
enum HeaderFile: Int {
case codeHightlighting = "highlight.min.js"
case modelViewer = "model-viewer.min.js"
case codeHightlighting = 4
case audioPlayerCss = "audio-player.css"
case modelViewer = 3
case amplitude = "amplitude.min.js"
/// CSS File to style the audio player
case audioPlayerCss = 1
var asModule: Bool {
/// JavaScript file for the audio player
case audioPlayerJs = 2
func header(content: Content) -> HeaderElement? {
switch self {
case .codeHightlighting: return false
case .modelViewer: return true
case .amplitude: return false
case .audioPlayerCss: return false
case .codeHightlighting:
if let file = content.settings.pages.codeHighlightingJsFile {
return HeaderElement.js(file: file, defer: true)
}
case .modelViewer:
if let file = content.settings.pages.modelViewerJsFile {
return HeaderElement.jsModule(file)
}
case .audioPlayerCss:
if let file = content.settings.pages.audioPlayerCssFile {
return .css(file)
}
case .audioPlayerJs:
if let file = content.settings.pages.audioPlayerJsFile {
return .js(file: file, defer: true)
}
}
return nil
}
}
extension HeaderFile: Comparable {
static func < (lhs: HeaderFile, rhs: HeaderFile) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
typealias RequiredHeaders = Set<HeaderFile>