184 lines
6.3 KiB
Swift
184 lines
6.3 KiB
Swift
import Foundation
|
|
|
|
struct WebsiteGeneratorConfiguration {
|
|
|
|
let language: ContentLanguage
|
|
|
|
let outputDirectory: URL
|
|
|
|
let postsPerPage: Int
|
|
|
|
let postFeedTitle: String
|
|
|
|
let postFeedDescription: String
|
|
|
|
let postFeedUrlPrefix: String
|
|
|
|
let navigationIconPath: String
|
|
|
|
let mainContentMaximumWidth: CGFloat
|
|
}
|
|
|
|
final class WebsiteGenerator {
|
|
|
|
let language: ContentLanguage
|
|
|
|
let outputDirectory: URL
|
|
|
|
let postsPerPage: Int
|
|
|
|
let postFeedTitle: String
|
|
|
|
let postFeedDescription: String
|
|
|
|
let postFeedUrlPrefix: String
|
|
|
|
let navigationIconPath: String
|
|
|
|
let mainContentMaximumWidth: CGFloat
|
|
|
|
private let content: Content
|
|
|
|
private let imageGenerator: ImageGenerator
|
|
|
|
init(content: Content, configuration: WebsiteGeneratorConfiguration) {
|
|
self.language = configuration.language
|
|
self.outputDirectory = configuration.outputDirectory
|
|
self.postsPerPage = configuration.postsPerPage
|
|
self.postFeedTitle = configuration.postFeedTitle
|
|
self.postFeedDescription = configuration.postFeedDescription
|
|
self.postFeedUrlPrefix = configuration.postFeedUrlPrefix
|
|
self.navigationIconPath = configuration.navigationIconPath
|
|
self.mainContentMaximumWidth = configuration.mainContentMaximumWidth
|
|
|
|
self.content = content
|
|
self.imageGenerator = ImageGenerator(
|
|
storage: content.storage,
|
|
inputImageFolder: content.storage.filesFolder,
|
|
relativeImageOutputPath: "images")
|
|
}
|
|
|
|
func generateWebsite() -> Bool {
|
|
guard imageGenerator.prepareForGeneration() else {
|
|
return false
|
|
}
|
|
guard createPostFeedPages() else {
|
|
return false
|
|
}
|
|
guard imageGenerator.runJobs() else {
|
|
return false
|
|
}
|
|
return imageGenerator.save()
|
|
}
|
|
|
|
private func createPostFeedPages() -> Bool {
|
|
let totalCount = content.posts.count
|
|
guard totalCount > 0 else {
|
|
return true
|
|
}
|
|
|
|
let navBarData = createNavigationBarData()
|
|
|
|
let numberOfPages = (totalCount + postsPerPage - 1) / postsPerPage // Round up
|
|
for pageIndex in 1...numberOfPages {
|
|
let startIndex = (pageIndex - 1) * postsPerPage
|
|
let endIndex = min(pageIndex * postsPerPage, totalCount)
|
|
let postsOnPage = content.posts[startIndex..<endIndex]
|
|
guard createPostFeedPage(pageIndex, pageCount: numberOfPages, posts: postsOnPage, bar: navBarData) else {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
private func createNavigationBarData() -> NavigationBarData {
|
|
let data = content.websiteData.localized(in: language)
|
|
let navigationItems: [NavigationBarLink] = content.websiteData.navigationTags.map {
|
|
let localized = $0.localized(in: language)
|
|
return .init(text: localized.name, url: localized.urlComponent)
|
|
}
|
|
return NavigationBarData(
|
|
navigationIconPath: navigationIconPath,
|
|
iconDescription: data.iconDescription,
|
|
navigationItems: navigationItems)
|
|
}
|
|
|
|
private func createImageSet(for image: ImageResource) -> FeedEntryData.Image {
|
|
let size1x = mainContentMaximumWidth
|
|
let size2x = mainContentMaximumWidth * 2
|
|
|
|
let avif1x = imageGenerator.generateVersion(for: image.id, type: .avif, maximumWidth: size1x, maximumHeight: size1x)
|
|
let avif2x = imageGenerator.generateVersion(for: image.id, type: .avif, maximumWidth: size2x, maximumHeight: size2x)
|
|
|
|
let webp1x = imageGenerator.generateVersion(for: image.id, type: .webp, maximumWidth: size1x, maximumHeight: size1x)
|
|
let webp2x = imageGenerator.generateVersion(for: image.id, type: .webp, maximumWidth: size2x, maximumHeight: size2x)
|
|
|
|
let jpg1x = imageGenerator.generateVersion(for: image.id, type: .jpg, maximumWidth: size1x, maximumHeight: size1x)
|
|
let jpg2x = imageGenerator.generateVersion(for: image.id, type: .jpg, maximumWidth: size2x, maximumHeight: size2x)
|
|
|
|
return FeedEntryData.Image(
|
|
altText: image.altText.getText(for: language),
|
|
avif1x: avif1x,
|
|
avif2x: avif2x,
|
|
webp1x: webp1x,
|
|
webp2x: webp2x,
|
|
jpg1x: jpg1x,
|
|
jpg2x: jpg2x)
|
|
}
|
|
|
|
private func createPostFeedPage(_ pageIndex: Int, pageCount: Int, posts: ArraySlice<Post>, bar: NavigationBarData) -> Bool {
|
|
let posts: [FeedEntryData] = posts.map { post in
|
|
let localized: LocalizedPost = post.localized(in: language)
|
|
|
|
let linkUrl = post.linkedPage.map {
|
|
FeedEntryData.Link(url: $0.localized(in: language).relativeUrl, text: "View")
|
|
}
|
|
|
|
return FeedEntryData(
|
|
entryId: "\(post.id)",
|
|
title: localized.title,
|
|
textAboveTitle: post.dateText(in: language),
|
|
link: linkUrl,
|
|
tags: post.tags.map { $0.data(in: language) },
|
|
text: [localized.content], // TODO: Convert from markdown to html
|
|
images: localized.images.map(createImageSet))
|
|
}
|
|
|
|
let feed = PageInFeed(
|
|
language: language,
|
|
title: postFeedTitle,
|
|
description: postFeedDescription,
|
|
navigationBarData: bar,
|
|
pageNumber: pageIndex,
|
|
totalPages: pageCount,
|
|
posts: posts)
|
|
let fileContent = feed.content
|
|
if pageIndex == 1 {
|
|
return save(fileContent, to: "\(postFeedUrlPrefix).html")
|
|
} else {
|
|
return save(fileContent, to: "\(postFeedUrlPrefix)-\(pageIndex).html")
|
|
}
|
|
}
|
|
|
|
private func save(_ content: String, to relativePath: String) -> Bool {
|
|
guard let data = content.data(using: .utf8) else {
|
|
print("Failed to create data for \(relativePath)")
|
|
return false
|
|
}
|
|
return save(data, to: relativePath)
|
|
}
|
|
|
|
private func save(_ data: Data, to relativePath: String) -> Bool {
|
|
self.content.storage.write(in: .outputPath) { folder in
|
|
let outputFile = folder.appendingPathComponent(relativePath, isDirectory: false)
|
|
do {
|
|
try data.write(to: outputFile)
|
|
return true
|
|
} catch {
|
|
print("Failed to save \(outputFile.path()): \(error)")
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|