Finish most recent and featured section
This commit is contained in:
parent
f185191b7f
commit
3bd75a63ab
@ -140,6 +140,18 @@ extension Element {
|
|||||||
This property is mandatory at root level, and is propagated to child elements.
|
This property is mandatory at root level, and is propagated to child elements.
|
||||||
*/
|
*/
|
||||||
let navigationTextAsPreviousPage: String
|
let navigationTextAsPreviousPage: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
The text to display above a slideshow for most recent items.
|
||||||
|
Only used for elements that define `showMostRecentSection = true`
|
||||||
|
*/
|
||||||
|
let mostRecentTitle: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
The text to display above a slideshow for featured items.
|
||||||
|
Only used for elements that define `showFeaturedSection = true`
|
||||||
|
*/
|
||||||
|
let featuredTitle: String?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,6 +183,8 @@ extension Element.LocalizedMetadata {
|
|||||||
self.relatedContentText = log.required(data.relatedContentText, name: "relatedContentText", source: source, &isValid)
|
self.relatedContentText = log.required(data.relatedContentText, name: "relatedContentText", source: source, &isValid)
|
||||||
self.navigationTextAsNextPage = log.required(data.navigationTextAsNextPage, name: "navigationTextAsNextPage", source: source, &isValid)
|
self.navigationTextAsNextPage = log.required(data.navigationTextAsNextPage, name: "navigationTextAsNextPage", source: source, &isValid)
|
||||||
self.navigationTextAsPreviousPage = log.required(data.navigationTextAsPreviousPage, name: "navigationTextAsPreviousPage", source: source, &isValid)
|
self.navigationTextAsPreviousPage = log.required(data.navigationTextAsPreviousPage, name: "navigationTextAsPreviousPage", source: source, &isValid)
|
||||||
|
self.mostRecentTitle = data.mostRecentTitle
|
||||||
|
self.featuredTitle = data.featuredTitle
|
||||||
|
|
||||||
guard isValid else {
|
guard isValid else {
|
||||||
return nil
|
return nil
|
||||||
@ -202,6 +216,8 @@ extension Element.LocalizedMetadata {
|
|||||||
self.relatedContentText = data.relatedContentText ?? parent.relatedContentText
|
self.relatedContentText = data.relatedContentText ?? parent.relatedContentText
|
||||||
self.navigationTextAsPreviousPage = data.navigationTextAsPreviousPage ?? parent.navigationTextAsPreviousPage
|
self.navigationTextAsPreviousPage = data.navigationTextAsPreviousPage ?? parent.navigationTextAsPreviousPage
|
||||||
self.navigationTextAsNextPage = data.navigationTextAsNextPage ?? parent.navigationTextAsNextPage
|
self.navigationTextAsNextPage = data.navigationTextAsNextPage ?? parent.navigationTextAsNextPage
|
||||||
|
self.mostRecentTitle = data.mostRecentTitle ?? parent.mostRecentTitle
|
||||||
|
self.featuredTitle = data.featuredTitle ?? parent.featuredTitle
|
||||||
|
|
||||||
guard isValid else {
|
guard isValid else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -132,6 +132,13 @@ struct Element {
|
|||||||
*/
|
*/
|
||||||
let showMostRecentSection: Bool
|
let showMostRecentSection: Bool
|
||||||
|
|
||||||
|
/**
|
||||||
|
Indicate that the overview section should contain a `Featured Content` section before the other sections.
|
||||||
|
The elements are the page ids of the elements contained in the feature.
|
||||||
|
- Note: If not specified, this property defaults to `false`
|
||||||
|
*/
|
||||||
|
let featuredPages: [String]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The localized metadata for each language.
|
The localized metadata for each language.
|
||||||
*/
|
*/
|
||||||
@ -190,6 +197,7 @@ struct Element {
|
|||||||
self.overviewItemCount = metadata.overviewItemCount ?? Element.overviewItemCountDefault
|
self.overviewItemCount = metadata.overviewItemCount ?? Element.overviewItemCountDefault
|
||||||
self.headerType = log.cast(metadata.headerType, "headerType", source: source)
|
self.headerType = log.cast(metadata.headerType, "headerType", source: source)
|
||||||
self.showMostRecentSection = metadata.showMostRecentSection ?? false
|
self.showMostRecentSection = metadata.showMostRecentSection ?? false
|
||||||
|
self.featuredPages = metadata.featuredPages ?? []
|
||||||
self.languages = log.required(metadata.languages, name: "languages", source: source, &isValid)
|
self.languages = log.required(metadata.languages, name: "languages", source: source, &isValid)
|
||||||
.compactMap { language in
|
.compactMap { language in
|
||||||
.init(atRoot: folder, data: language, log: log)
|
.init(atRoot: folder, data: language, log: log)
|
||||||
@ -252,6 +260,7 @@ struct Element {
|
|||||||
self.overviewItemCount = metadata.overviewItemCount ?? parent.overviewItemCount
|
self.overviewItemCount = metadata.overviewItemCount ?? parent.overviewItemCount
|
||||||
self.headerType = log.cast(metadata.headerType, "headerType", source: source)
|
self.headerType = log.cast(metadata.headerType, "headerType", source: source)
|
||||||
self.showMostRecentSection = metadata.showMostRecentSection ?? false
|
self.showMostRecentSection = metadata.showMostRecentSection ?? false
|
||||||
|
self.featuredPages = metadata.featuredPages ?? []
|
||||||
self.languages = parent.languages.compactMap { parentData in
|
self.languages = parent.languages.compactMap { parentData in
|
||||||
guard let data = metadata.languages?.first(where: { $0.language == parentData.language }) else {
|
guard let data = metadata.languages?.first(where: { $0.language == parentData.language }) else {
|
||||||
log.warning("Language '\(parentData.language)' not found", source: source)
|
log.warning("Language '\(parentData.language)' not found", source: source)
|
||||||
@ -285,8 +294,25 @@ struct Element {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//files.add(page: path, id: id)
|
|
||||||
self.readElements(in: folder, source: path, log: log)
|
self.readElements(in: folder, source: path, log: log)
|
||||||
|
|
||||||
|
if showMostRecentSection {
|
||||||
|
if elements.isEmpty {
|
||||||
|
log.error("Page has no children", source: source)
|
||||||
|
}
|
||||||
|
languages.filter { $0.mostRecentTitle == nil }.forEach {
|
||||||
|
log.error("'showMostRecentSection' = true, but 'mostRecentTitle' not set for language '\($0.language)'", source: source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !featuredPages.isEmpty {
|
||||||
|
if elements.isEmpty {
|
||||||
|
log.error("'featuredPages' contains elements, but page has no children", source: source)
|
||||||
|
}
|
||||||
|
languages.filter { $0.featuredTitle == nil }.forEach {
|
||||||
|
log.error("'featuredPages' contains elements, but 'featuredTitle' not set for language '\($0.language)'", source: source)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExternalPageMap(language: String) -> [String : String] {
|
func getExternalPageMap(language: String) -> [String : String] {
|
||||||
@ -307,6 +333,10 @@ struct Element {
|
|||||||
private func getExternalLink(for language: String) -> String? {
|
private func getExternalLink(for language: String) -> String? {
|
||||||
languages.first { $0.language == language }?.externalUrl
|
languages.first { $0.language == language }?.externalUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var needsFirstSection: Bool {
|
||||||
|
showMostRecentSection || !featuredPages.isEmpty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Paths
|
// MARK: Paths
|
||||||
@ -349,6 +379,7 @@ extension Element {
|
|||||||
}
|
}
|
||||||
let all = shownItems
|
let all = shownItems
|
||||||
.reduce(into: [Element]()) { $0 += $1.mostRecentElements(count) }
|
.reduce(into: [Element]()) { $0 += $1.mostRecentElements(count) }
|
||||||
|
.filter { $0.thumbnailStyle == .large }
|
||||||
.filter { $0.date != nil }
|
.filter { $0.date != nil }
|
||||||
.sorted { $0.date! > $1.date! }
|
.sorted { $0.date! > $1.date! }
|
||||||
return Array(all.prefix(count))
|
return Array(all.prefix(count))
|
||||||
|
@ -133,6 +133,18 @@ extension GenericMetadata {
|
|||||||
This property is mandatory at root level, and is propagated to child elements.
|
This property is mandatory at root level, and is propagated to child elements.
|
||||||
*/
|
*/
|
||||||
let navigationTextAsNextPage: String?
|
let navigationTextAsNextPage: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
The text to display above a slideshow for most recent items.
|
||||||
|
Only used for elements that define `showMostRecentSection = true`
|
||||||
|
*/
|
||||||
|
let mostRecentTitle: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
The text to display above a slideshow for featured items.
|
||||||
|
Only used for elements that define `showFeaturedSection = true`
|
||||||
|
*/
|
||||||
|
let featuredTitle: String?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +170,8 @@ extension GenericMetadata.LocalizedMetadata: Codable {
|
|||||||
.relatedContentText,
|
.relatedContentText,
|
||||||
.navigationTextAsPreviousPage,
|
.navigationTextAsPreviousPage,
|
||||||
.navigationTextAsNextPage,
|
.navigationTextAsNextPage,
|
||||||
|
.mostRecentTitle,
|
||||||
|
.featuredTitle,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +204,9 @@ extension GenericMetadata.LocalizedMetadata {
|
|||||||
externalUrl: nil,
|
externalUrl: nil,
|
||||||
relatedContentText: nil,
|
relatedContentText: nil,
|
||||||
navigationTextAsPreviousPage: nil,
|
navigationTextAsPreviousPage: nil,
|
||||||
navigationTextAsNextPage: nil)
|
navigationTextAsNextPage: nil,
|
||||||
|
mostRecentTitle: nil,
|
||||||
|
featuredTitle: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -214,7 +230,9 @@ extension GenericMetadata.LocalizedMetadata {
|
|||||||
externalUrl: nil,
|
externalUrl: nil,
|
||||||
relatedContentText: "",
|
relatedContentText: "",
|
||||||
navigationTextAsPreviousPage: "",
|
navigationTextAsPreviousPage: "",
|
||||||
navigationTextAsNextPage: "")
|
navigationTextAsNextPage: "",
|
||||||
|
mostRecentTitle: nil,
|
||||||
|
featuredTitle: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
static var full: GenericMetadata.LocalizedMetadata {
|
static var full: GenericMetadata.LocalizedMetadata {
|
||||||
@ -235,6 +253,8 @@ extension GenericMetadata.LocalizedMetadata {
|
|||||||
externalUrl: "",
|
externalUrl: "",
|
||||||
relatedContentText: "",
|
relatedContentText: "",
|
||||||
navigationTextAsPreviousPage: "",
|
navigationTextAsPreviousPage: "",
|
||||||
navigationTextAsNextPage: "")
|
navigationTextAsNextPage: "",
|
||||||
|
mostRecentTitle: "",
|
||||||
|
featuredTitle: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,13 @@ struct GenericMetadata {
|
|||||||
*/
|
*/
|
||||||
let showMostRecentSection: Bool?
|
let showMostRecentSection: Bool?
|
||||||
|
|
||||||
|
/**
|
||||||
|
Indicate that the overview section should contain a `Featured Content` section before the other sections.
|
||||||
|
The elements are the page ids of the elements contained in the feature.
|
||||||
|
- Note: If not specified, this property defaults to `false`
|
||||||
|
*/
|
||||||
|
let featuredPages: [String]?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The localized metadata for each language.
|
The localized metadata for each language.
|
||||||
*/
|
*/
|
||||||
@ -157,6 +164,7 @@ extension GenericMetadata: Codable {
|
|||||||
.overviewItemCount,
|
.overviewItemCount,
|
||||||
.headerType,
|
.headerType,
|
||||||
.showMostRecentSection,
|
.showMostRecentSection,
|
||||||
|
.featuredPages,
|
||||||
.languages,
|
.languages,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -229,6 +237,7 @@ extension GenericMetadata {
|
|||||||
overviewItemCount: 6,
|
overviewItemCount: 6,
|
||||||
headerType: "left",
|
headerType: "left",
|
||||||
showMostRecentSection: false,
|
showMostRecentSection: false,
|
||||||
|
featuredPages: [],
|
||||||
languages: [.full])
|
languages: [.full])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,36 +2,80 @@ import Foundation
|
|||||||
|
|
||||||
struct OverviewSectionGenerator {
|
struct OverviewSectionGenerator {
|
||||||
|
|
||||||
private let multipleSectionsTemplate: OverviewSectionTemplate
|
private let factory: TemplateFactory
|
||||||
|
|
||||||
private let singleSectionsTemplate: OverviewSectionCleanTemplate
|
|
||||||
|
|
||||||
private let generator: ThumbnailListGenerator
|
private let generator: ThumbnailListGenerator
|
||||||
|
|
||||||
init(factory: TemplateFactory, results: GenerationResultsHandler) {
|
private let siteRoot: Element
|
||||||
self.multipleSectionsTemplate = factory.overviewSection
|
|
||||||
self.singleSectionsTemplate = factory.overviewSectionClean
|
private let results: GenerationResultsHandler
|
||||||
|
|
||||||
|
init(factory: TemplateFactory, siteRoot: Element, results: GenerationResultsHandler) {
|
||||||
|
self.factory = factory
|
||||||
|
self.siteRoot = siteRoot
|
||||||
|
self.results = results
|
||||||
self.generator = ThumbnailListGenerator(factory: factory, results: results)
|
self.generator = ThumbnailListGenerator(factory: factory, results: results)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generate(sections: [Element], in parent: Element, language: String, sectionItemCount: Int) -> String {
|
func generate(sections: [Element], in parent: Element, language: String, sectionItemCount: Int) -> String {
|
||||||
let content = sectionsContent(sections, in: parent, language: language, sectionItemCount: sectionItemCount)
|
let content = sectionsContent(sections, in: parent, language: language, sectionItemCount: sectionItemCount)
|
||||||
if parent.showMostRecentSection {
|
let firstSection = firstSectionContent(for: parent, language: language, sectionItemCount: sectionItemCount)
|
||||||
let news = newsSectionContent(for: parent, language: language, sectionItemCount: sectionItemCount)
|
return firstSection + content
|
||||||
return news + "\n" + content
|
|
||||||
} else {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func newsSectionContent(for element: Element, language: String, sectionItemCount: Int) -> String {
|
private func firstSectionContent(for element: Element, language: String, sectionItemCount: Int) -> String {
|
||||||
// let shownElements = element.mostRecentElements(sectionItemCount)
|
guard element.needsFirstSection else {
|
||||||
return ""
|
return ""
|
||||||
// return generator.generateContent(
|
}
|
||||||
// items: shownElements,
|
let metadata = element.localized(for: language)
|
||||||
// parent: element,
|
var result = ""
|
||||||
// language: language,
|
if element.showMostRecentSection {
|
||||||
// style: element.thumbnailStyle)
|
let shownElements = element.mostRecentElements(4).enumerated().map { (number, child) in
|
||||||
|
makeSlideshowItem(child, parent: element, language: language, number: number)
|
||||||
|
}
|
||||||
|
let title = metadata.mostRecentTitle ?? "Recent"
|
||||||
|
result = factory.slideshow.generate([.content: shownElements.joined(separator: "\n"), .title: title])
|
||||||
|
}
|
||||||
|
if !element.featuredPages.isEmpty {
|
||||||
|
let elements = element.featuredPages.compactMap { id -> Element? in
|
||||||
|
guard let linked = siteRoot.find(id) else {
|
||||||
|
results.warning("Unknown id '\(id)' in 'featuredPages'", source: element.path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return linked
|
||||||
|
}.prefix(4).enumerated().map { number, page in
|
||||||
|
makeSlideshowItem(page, parent: element, language: language, number: number)
|
||||||
|
}
|
||||||
|
let title = metadata.featuredTitle ?? "Featured"
|
||||||
|
result += factory.slideshow.generate([.content: elements.joined(separator: "\n"), .title: title])
|
||||||
|
}
|
||||||
|
return factory.slideshows.generate([.content : result])
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeSlideshowItem(_ item: Element, parent: Element, language: String, number: Int) -> String {
|
||||||
|
let metadata = item.localized(for: language)
|
||||||
|
var content = [SlideshowImageTemplate.Key : String]()
|
||||||
|
content[.number] = "\(number + 1)"
|
||||||
|
|
||||||
|
if item.state.hasThumbnailLink {
|
||||||
|
let fullPageUrl = item.fullPageUrl(for: language)
|
||||||
|
let relativePageUrl = parent.relativePathToFileWithPath(fullPageUrl)
|
||||||
|
content[.url] = "href=\"\(relativePageUrl)\""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image already assumed to be generated
|
||||||
|
let (_, thumbnailDestPath) = item.thumbnailFilePath(for: language)
|
||||||
|
let thumbnailDestNoExtension = thumbnailDestPath.dropAfterLast(".")
|
||||||
|
content[.image] = parent.relativePathToFileWithPath(thumbnailDestNoExtension)
|
||||||
|
|
||||||
|
if let suffix = metadata.thumbnailSuffix {
|
||||||
|
content[.title] = factory.html.make(title: metadata.title, suffix: suffix)
|
||||||
|
} else {
|
||||||
|
content[.title] = metadata.title
|
||||||
|
}
|
||||||
|
let path = item.makePath(language: language, from: siteRoot)
|
||||||
|
content[.subtitle] = factory.pageLink.makePath(components: path)
|
||||||
|
return factory.slideshowImage.generate(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sectionsContent(_ sections: [Element], in parent: Element, language: String, sectionItemCount: Int) -> String {
|
private func sectionsContent(_ sections: [Element], in parent: Element, language: String, sectionItemCount: Int) -> String {
|
||||||
@ -50,7 +94,7 @@ struct OverviewSectionGenerator {
|
|||||||
style: section.thumbnailStyle)
|
style: section.thumbnailStyle)
|
||||||
content[.more] = metadata.moreLinkText
|
content[.more] = metadata.moreLinkText
|
||||||
|
|
||||||
return multipleSectionsTemplate.generate(content)
|
return factory.overviewSection.generate(content)
|
||||||
}
|
}
|
||||||
.joined(separator: "\n")
|
.joined(separator: "\n")
|
||||||
}
|
}
|
||||||
@ -62,6 +106,6 @@ struct OverviewSectionGenerator {
|
|||||||
parent: section,
|
parent: section,
|
||||||
language: language,
|
language: language,
|
||||||
style: section.thumbnailStyle)
|
style: section.thumbnailStyle)
|
||||||
return singleSectionsTemplate.generate(content)
|
return factory.overviewSectionClean.generate(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SlideshowImageTemplate: Template {
|
||||||
|
|
||||||
|
enum Key: String, CaseIterable {
|
||||||
|
case url = "URL"
|
||||||
|
case image = "IMAGE"
|
||||||
|
case title = "TITLE"
|
||||||
|
case subtitle = "SUBTITLE"
|
||||||
|
case number = "NUMBER"
|
||||||
|
}
|
||||||
|
|
||||||
|
static let templateName = "slideshow-image.html"
|
||||||
|
|
||||||
|
let raw: String
|
||||||
|
|
||||||
|
let results: GenerationResultsHandler
|
||||||
|
|
||||||
|
func makePath(components: [String]) -> String {
|
||||||
|
components.joined(separator: " » ") //  » ")
|
||||||
|
}
|
||||||
|
}
|
15
Sources/Generator/Templates/Elements/SlideshowTemplate.swift
Normal file
15
Sources/Generator/Templates/Elements/SlideshowTemplate.swift
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SlideshowTemplate: Template {
|
||||||
|
|
||||||
|
enum Key: String, CaseIterable {
|
||||||
|
case title = "TITLE"
|
||||||
|
case content = "CONTENT"
|
||||||
|
}
|
||||||
|
|
||||||
|
static let templateName = "slideshow.html"
|
||||||
|
|
||||||
|
let raw: String
|
||||||
|
|
||||||
|
let results: GenerationResultsHandler
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SlideshowsTemplate: Template {
|
||||||
|
|
||||||
|
enum Key: String, CaseIterable {
|
||||||
|
case content = "CONTENT"
|
||||||
|
}
|
||||||
|
|
||||||
|
static let templateName = "slideshows.html"
|
||||||
|
|
||||||
|
let raw: String
|
||||||
|
|
||||||
|
let results: GenerationResultsHandler
|
||||||
|
}
|
@ -67,7 +67,7 @@ struct LocalizedSiteTemplate {
|
|||||||
sections: sections,
|
sections: sections,
|
||||||
topBarWebsiteTitle: site.topBarTitle)
|
topBarWebsiteTitle: site.topBarTitle)
|
||||||
self.pageHead = PageHeadGenerator(factory: factory, results: results)
|
self.pageHead = PageHeadGenerator(factory: factory, results: results)
|
||||||
self.overviewSection = OverviewSectionGenerator(factory: factory, results: results)
|
self.overviewSection = OverviewSectionGenerator(factory: factory, siteRoot: site, results: results)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Content
|
// MARK: Content
|
||||||
|
@ -53,6 +53,14 @@ final class TemplateFactory {
|
|||||||
|
|
||||||
let video: PageVideoTemplate
|
let video: PageVideoTemplate
|
||||||
|
|
||||||
|
// MARK: Slideshow
|
||||||
|
|
||||||
|
let slideshows: SlideshowsTemplate
|
||||||
|
|
||||||
|
let slideshow: SlideshowTemplate
|
||||||
|
|
||||||
|
let slideshowImage: SlideshowImageTemplate
|
||||||
|
|
||||||
// MARK: HTML
|
// MARK: HTML
|
||||||
|
|
||||||
let html: HTMLElementsGenerator
|
let html: HTMLElementsGenerator
|
||||||
@ -76,6 +84,9 @@ final class TemplateFactory {
|
|||||||
self.page = try .init(in: templateFolder, results: results)
|
self.page = try .init(in: templateFolder, results: results)
|
||||||
self.image = try .init(in: templateFolder, results: results)
|
self.image = try .init(in: templateFolder, results: results)
|
||||||
self.video = try .init(in: templateFolder, results: results)
|
self.video = try .init(in: templateFolder, results: results)
|
||||||
|
self.slideshow = try .init(in: templateFolder, results: results)
|
||||||
|
self.slideshows = try .init(in: templateFolder, results: results)
|
||||||
|
self.slideshowImage = try .init(in: templateFolder, results: results)
|
||||||
self.html = .init()
|
self.html = .init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user