Generate tag overview, add file action

This commit is contained in:
Christoph Hagen
2025-01-05 09:21:21 +01:00
parent 0dca633805
commit 01baf560ee
27 changed files with 501 additions and 137 deletions

View File

@ -48,9 +48,8 @@ final class GenerationResults: ObservableObject {
private(set) var general: PageGenerationResults!
var resultCount: Int {
cache.count
}
@Published
var resultCount: Int = 0
// MARK: Life cycle
@ -59,12 +58,14 @@ final class GenerationResults: ObservableObject {
let general = PageGenerationResults(itemId: id, delegate: self)
self.general = general
cache[id] = general
self.resultCount = 1
}
func makeResults(_ itemId: ItemId) -> PageGenerationResults {
guard let result = cache[itemId] else {
let result = PageGenerationResults(itemId: itemId, delegate: self)
cache[itemId] = result
update { self.resultCount += 1 }
return result
}
return result
@ -116,7 +117,6 @@ final class GenerationResults: ObservableObject {
}
}
// MARK: Adding entries
func inaccessibleContent(file: FileResource) {

View File

@ -49,6 +49,7 @@ final class ImageGenerator {
*/
func removeVersions(of image: String) {
generatedImages[image] = nil
save()
}
func recalculateGeneratedImages(by images: Set<String>) {
@ -119,6 +120,10 @@ final class ImageGenerator {
}
if version.type == .avif {
if version.image.type == .gif {
// Skip GIFs, since they can't be converted by avifenc
return true
}
// AVIF conversion is very slow, so we save bash commands
// for the conversion instead
let baseVersion = ImageVersion(

View File

@ -44,7 +44,7 @@ struct ImageSet {
var result = "<picture>"
result += "<source type='image/avif' srcset='\(prefix1x).avif 1x, \(prefix2x).avif 2x'/>"
result += "<source type='image/webp' srcset='\(prefix1x).webm 1x, \(prefix1x).webm 2x'/>"
result += "<source type='image/webp' srcset='\(prefix1x).webp 1x, \(prefix1x).webp 2x'/>"
result += "<img srcset='\(prefix2x)\(fileExtension) 2x' src='\(prefix1x)\(fileExtension)' loading='lazy' alt='\(description.htmlEscaped())'/>"
result += "</picture>"
return result

View File

@ -1,10 +1,78 @@
private struct TagData {
let url: String
let title: String
let localized: LocalizedTag
init(tag: Tag, language: ContentLanguage) {
let localized = tag.localized(in: language)
self.url = tag.absoluteUrl(in: language)
self.title = localized.linkPreviewTitle ?? localized.name
self.localized = localized
}
}
extension TagData: Comparable {
static func < (lhs: TagData, rhs: TagData) -> Bool {
lhs.title < rhs.title
}
static func == (lhs: TagData, rhs: TagData) -> Bool {
lhs.title == rhs.title
}
}
private struct TagHeaderContent {
let language: ContentLanguage
let title: String
let description: String?
let iconUrl: String
let links: [NavigationBar.Link]
let headers: Set<HeaderElement>
let baseUrl: String
let localizedBaseUrl: String
private func url(pageNumber: Int) -> String {
baseUrl + "/\(pageNumber)"
}
func fileUrl(pageNumber: Int) -> String {
url(pageNumber: pageNumber) + ".html"
}
func pageHeader(pageNumber: Int) -> PageHeader {
.init(
language: language,
title: title,
description: description,
iconUrl: iconUrl,
languageButton: .init(
text: language.next.rawValue,
url: localizedBaseUrl + "/\(pageNumber)"),
links: links,
headers: headers,
icons: [])
}
}
final class TagOverviewGenerator {
let content: Content
let language: ContentLanguage
let results: PageGenerationResults
init(content: Content, language: ContentLanguage, results: PageGenerationResults) {
@ -13,49 +81,67 @@ final class TagOverviewGenerator {
self.results = results
}
func generatePage(tags: [Tag], overview: TagOverviewPage) {
let iconUrl = content.settings.navigation.localized(in: language).rootUrl
let languageUrl = overview.absoluteUrl(in: language.next)
let languageButton = NavigationBar.Link(
text: language.next.rawValue,
url: languageUrl)
func generatePages(tags: [Tag], overview: TagOverviewPage) {
let localized = overview.localized(in: language)
let pageHeader = PageHeader(
let header = TagHeaderContent(
language: language,
title: localized.linkPreviewTitle ?? localized.title,
description: localized.linkPreviewDescription,
iconUrl: iconUrl,
languageButton: languageButton,
iconUrl: content.settings.navigation.localized(in: language).rootUrl,
links: content.navigationBar(in: language),
headers: content.defaultPageHeaders,
icons: [])
baseUrl: overview.absoluteUrl(in: language),
localizedBaseUrl: overview.absoluteUrl(in: language.next))
// Sort tags by title
let tagData = tags.map {
TagData(tag: $0, language: language)
}.sorted { $0.title }
let totalCount = tagData.count
guard totalCount > 0 else {
// Create one empty page
generatePage(tags: [], header: header, page: 1, totalPages: 1)
return
}
let tagsPerPage = content.settings.posts.postsPerPage
let numberOfPages = (totalCount + tagsPerPage - 1) / tagsPerPage // Round up
for pageIndex in 1...numberOfPages {
let startIndex = (pageIndex - 1) * tagsPerPage
let endIndex = min(pageIndex * tagsPerPage, totalCount)
let tagsOnPage = tagData[startIndex..<endIndex]
generatePage(tags: tagsOnPage, header: header, page: pageIndex, totalPages: numberOfPages)
}
}
private func generatePage(tags: ArraySlice<TagData>, header: TagHeaderContent, page pageNumber: Int, totalPages: Int) {
let pageHeader = header.pageHeader(pageNumber: pageNumber)
let page = GenericPage(
header: pageHeader,
additionalFooter: "") { content in
content += "<h1>\(localized.title)</h1>"
content += "<h1>\(header.title)</h1>"
for tag in tags {
let localized = tag.localized(in: self.language)
let url = tag.absoluteUrl(in: self.language)
let title = localized.name
let description = localized.description ?? ""
let image = self.makePageImage(item: localized)
let description = tag.localized.description ?? ""
let image = self.makePageImage(item: tag.localized)
content += RelatedPageLink(
title: title,
title: tag.title,
description: description,
url: url,
url: tag.url,
image: image)
.content
.content
}
if totalPages > 1 {
content += PostFeedPageNavigation(
linkPrefix: header.baseUrl,
currentPage: pageNumber,
numberOfPages: totalPages).content
}
// if totalPages > 1 {
// content += PostFeedPageNavigation(currentPage: pageNumber, numberOfPages: totalPages, language: language).content
// }
}
let fileContent = page.content
let url = overview.absoluteUrl(in: language) + ".html"
let url = header.fileUrl(pageNumber: pageNumber)
guard content.storage.write(fileContent, to: url) else {
results.unsavedOutput(url, source: .tagOverview)

View File

@ -23,6 +23,8 @@ final class PostListPageGenerator {
func createPages(for posts: [Post]) {
let totalCount = posts.count
guard totalCount > 0 else {
// Create one empty page
createPostFeedPage(1, pageCount: 1, posts: [])
return
}
let postsPerPage = source.postsPerPage
@ -80,7 +82,7 @@ final class PostListPageGenerator {
pageNumber: pageIndex,
totalPages: pageCount,
languageButtonUrl: languageButtonUrl,
linkPrefix: "/" + source.pageUrlPrefix(for: language) + "/")
linkPrefix: source.pageUrlPrefix(for: language))
let filePath = pageUrl(in: language, pageNumber: pageIndex) + ".html"
guard save(fileContent, to: filePath) else {
source.results.unsavedOutput(filePath, source: .feed)