Add tag overview, improve assets
This commit is contained in:
89
CHDataManagement/Generator/FeedPageGenerator.swift
Normal file
89
CHDataManagement/Generator/FeedPageGenerator.swift
Normal 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
|
||||
|
||||
}
|
||||
}
|
33
CHDataManagement/Generator/HeaderElement.swift
Normal file
33
CHDataManagement/Generator/HeaderElement.swift
Normal 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' />"
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user