Generate open graph meta tags
This commit is contained in:
parent
4b448f3415
commit
f6b868502d
@ -185,6 +185,8 @@
|
|||||||
E2FD1D282D2F2DAD00B48627 /* ErrorPrinter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D272D2F2D9100B48627 /* ErrorPrinter.swift */; };
|
E2FD1D282D2F2DAD00B48627 /* ErrorPrinter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D272D2F2D9100B48627 /* ErrorPrinter.swift */; };
|
||||||
E2FD1D2A2D35B74C00B48627 /* TextWithPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D292D35B74C00B48627 /* TextWithPopup.swift */; };
|
E2FD1D2A2D35B74C00B48627 /* TextWithPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D292D35B74C00B48627 /* TextWithPopup.swift */; };
|
||||||
E2FD1D2C2D35B76D00B48627 /* ListPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D2B2D35B76D00B48627 /* ListPopup.swift */; };
|
E2FD1D2C2D35B76D00B48627 /* ListPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D2B2D35B76D00B48627 /* ListPopup.swift */; };
|
||||||
|
E2FD1D2E2D37180900B48627 /* GeneralSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D2D2D37180600B48627 /* GeneralSettings.swift */; };
|
||||||
|
E2FD1D302D37196C00B48627 /* GeneralSettingsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D2F2D37196500B48627 /* GeneralSettingsDetailView.swift */; };
|
||||||
E2FE0EE62D15A0B5002963B7 /* GenerationResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EE52D15A0B1002963B7 /* GenerationResults.swift */; };
|
E2FE0EE62D15A0B5002963B7 /* GenerationResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EE52D15A0B1002963B7 /* GenerationResults.swift */; };
|
||||||
E2FE0EE82D16D4A3002963B7 /* ConvertThrowing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EE72D16D4A3002963B7 /* ConvertThrowing.swift */; };
|
E2FE0EE82D16D4A3002963B7 /* ConvertThrowing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EE72D16D4A3002963B7 /* ConvertThrowing.swift */; };
|
||||||
E2FE0EEC2D1C1253002963B7 /* MultiFileSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EEB2D1C124E002963B7 /* MultiFileSelectionView.swift */; };
|
E2FE0EEC2D1C1253002963B7 /* MultiFileSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EEB2D1C124E002963B7 /* MultiFileSelectionView.swift */; };
|
||||||
@ -416,6 +418,8 @@
|
|||||||
E2FD1D272D2F2D9100B48627 /* ErrorPrinter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPrinter.swift; sourceTree = "<group>"; };
|
E2FD1D272D2F2D9100B48627 /* ErrorPrinter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPrinter.swift; sourceTree = "<group>"; };
|
||||||
E2FD1D292D35B74C00B48627 /* TextWithPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextWithPopup.swift; sourceTree = "<group>"; };
|
E2FD1D292D35B74C00B48627 /* TextWithPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextWithPopup.swift; sourceTree = "<group>"; };
|
||||||
E2FD1D2B2D35B76D00B48627 /* ListPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListPopup.swift; sourceTree = "<group>"; };
|
E2FD1D2B2D35B76D00B48627 /* ListPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListPopup.swift; sourceTree = "<group>"; };
|
||||||
|
E2FD1D2D2D37180600B48627 /* GeneralSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettings.swift; sourceTree = "<group>"; };
|
||||||
|
E2FD1D2F2D37196500B48627 /* GeneralSettingsDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsDetailView.swift; sourceTree = "<group>"; };
|
||||||
E2FE0EE52D15A0B1002963B7 /* GenerationResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerationResults.swift; sourceTree = "<group>"; };
|
E2FE0EE52D15A0B1002963B7 /* GenerationResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerationResults.swift; sourceTree = "<group>"; };
|
||||||
E2FE0EE72D16D4A3002963B7 /* ConvertThrowing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConvertThrowing.swift; sourceTree = "<group>"; };
|
E2FE0EE72D16D4A3002963B7 /* ConvertThrowing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConvertThrowing.swift; sourceTree = "<group>"; };
|
||||||
E2FE0EEB2D1C124E002963B7 /* MultiFileSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiFileSelectionView.swift; sourceTree = "<group>"; };
|
E2FE0EEB2D1C124E002963B7 /* MultiFileSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiFileSelectionView.swift; sourceTree = "<group>"; };
|
||||||
@ -508,6 +512,7 @@
|
|||||||
E25DA53B2D0042EA00AEF16D /* Settings */ = {
|
E25DA53B2D0042EA00AEF16D /* Settings */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E2FD1D2D2D37180600B48627 /* GeneralSettings.swift */,
|
||||||
E2FE0F392D2B3E4E002963B7 /* AudioPlayerSettings.swift */,
|
E2FE0F392D2B3E4E002963B7 /* AudioPlayerSettings.swift */,
|
||||||
E2FE0F6D2D2D3685002963B7 /* LocalizedAudioPlayerSettings.swift */,
|
E2FE0F6D2D2D3685002963B7 /* LocalizedAudioPlayerSettings.swift */,
|
||||||
E2FE0F012D266FCB002963B7 /* LocalizedNavigationSettings.swift */,
|
E2FE0F012D266FCB002963B7 /* LocalizedNavigationSettings.swift */,
|
||||||
@ -639,6 +644,7 @@
|
|||||||
E25DA5702D01015400AEF16D /* GenerationContentView.swift */,
|
E25DA5702D01015400AEF16D /* GenerationContentView.swift */,
|
||||||
E29D31702D08234D0051B7F4 /* GenerationDetailView.swift */,
|
E29D31702D08234D0051B7F4 /* GenerationDetailView.swift */,
|
||||||
E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */,
|
E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */,
|
||||||
|
E2FD1D2F2D37196500B48627 /* GeneralSettingsDetailView.swift */,
|
||||||
E29D318C2D0B2E5E0051B7F4 /* Content */,
|
E29D318C2D0B2E5E0051B7F4 /* Content */,
|
||||||
E2FE0F032D2671FC002963B7 /* LocalizedNavigationBarSettingsView.swift */,
|
E2FE0F032D2671FC002963B7 /* LocalizedNavigationBarSettingsView.swift */,
|
||||||
E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */,
|
E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */,
|
||||||
@ -1130,6 +1136,7 @@
|
|||||||
E22990462D10B7A7009F8D77 /* SecurityScopeStatus.swift in Sources */,
|
E22990462D10B7A7009F8D77 /* SecurityScopeStatus.swift in Sources */,
|
||||||
E29D31512D06168E0051B7F4 /* PostListView.swift in Sources */,
|
E29D31512D06168E0051B7F4 /* PostListView.swift in Sources */,
|
||||||
E2FE0F4F2D2BCD80002963B7 /* TagLinkCommand.swift in Sources */,
|
E2FE0F4F2D2BCD80002963B7 /* TagLinkCommand.swift in Sources */,
|
||||||
|
E2FD1D302D37196C00B48627 /* GeneralSettingsDetailView.swift in Sources */,
|
||||||
E22990342D0F77E9009F8D77 /* PagePropertyView.swift in Sources */,
|
E22990342D0F77E9009F8D77 /* PagePropertyView.swift in Sources */,
|
||||||
E2A37D0C2CE4036B0000979F /* LocalizedPage.swift in Sources */,
|
E2A37D0C2CE4036B0000979F /* LocalizedPage.swift in Sources */,
|
||||||
E2A21C102CB18B3A0060935B /* FlowHStack.swift in Sources */,
|
E2A21C102CB18B3A0060935B /* FlowHStack.swift in Sources */,
|
||||||
@ -1163,6 +1170,7 @@
|
|||||||
E22990262D0F582B009F8D77 /* FilePropertyView.swift in Sources */,
|
E22990262D0F582B009F8D77 /* FilePropertyView.swift in Sources */,
|
||||||
E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */,
|
E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */,
|
||||||
E2FE0F172D2698D5002963B7 /* LocalizedPageId.swift in Sources */,
|
E2FE0F172D2698D5002963B7 /* LocalizedPageId.swift in Sources */,
|
||||||
|
E2FD1D2E2D37180900B48627 /* GeneralSettings.swift in Sources */,
|
||||||
E2FE0F0D2D268A09002963B7 /* PostListPageGeneratorSource.swift in Sources */,
|
E2FE0F0D2D268A09002963B7 /* PostListPageGeneratorSource.swift in Sources */,
|
||||||
E2FE0F402D2B45D3002963B7 /* SwiftBlock.swift in Sources */,
|
E2FE0F402D2B45D3002963B7 /* SwiftBlock.swift in Sources */,
|
||||||
E25DA58B2D020C9500AEF16D /* PageImage.swift in Sources */,
|
E25DA58B2D020C9500AEF16D /* PageImage.swift in Sources */,
|
||||||
|
@ -36,6 +36,14 @@ enum HeaderElement {
|
|||||||
|
|
||||||
case description(String)
|
case description(String)
|
||||||
|
|
||||||
|
case ogTitle(String)
|
||||||
|
|
||||||
|
case ogDescription(String)
|
||||||
|
|
||||||
|
case ogImage(String)
|
||||||
|
|
||||||
|
case ogUrl(String)
|
||||||
|
|
||||||
case charset
|
case charset
|
||||||
|
|
||||||
case viewport
|
case viewport
|
||||||
@ -44,30 +52,24 @@ enum HeaderElement {
|
|||||||
|
|
||||||
var order: Int {
|
var order: Int {
|
||||||
switch self {
|
switch self {
|
||||||
case .charset:
|
case .charset: 1
|
||||||
return 1
|
case .robots: 2
|
||||||
case .robots:
|
case .viewport: 3
|
||||||
return 2
|
case .icon: 10
|
||||||
case .viewport:
|
case .css(_, let order): order
|
||||||
return 3
|
case .js: 20
|
||||||
case .icon:
|
case .jsModule: 30
|
||||||
return 10
|
case .author: 100
|
||||||
case .css(_, let order):
|
case .title: 101
|
||||||
return order
|
case .description: 102
|
||||||
case .js:
|
case .ogTitle: 103
|
||||||
return 20
|
case .ogDescription: 104
|
||||||
case .jsModule:
|
case .ogImage: 105
|
||||||
return 30
|
case .ogUrl: 106
|
||||||
case .author:
|
|
||||||
return 100
|
|
||||||
case .title:
|
|
||||||
return 101
|
|
||||||
case .description:
|
|
||||||
return 102
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var file: FileResource? {
|
var requiredFile: FileResource? {
|
||||||
switch self {
|
switch self {
|
||||||
case .icon(let file, _, _):
|
case .icon(let file, _, _):
|
||||||
return file
|
return file
|
||||||
@ -113,6 +115,14 @@ extension HeaderElement {
|
|||||||
return "<title>\(title)</title>"
|
return "<title>\(title)</title>"
|
||||||
case .description(let description):
|
case .description(let description):
|
||||||
return "<meta name='description' content=\"\(description)\">"
|
return "<meta name='description' content=\"\(description)\">"
|
||||||
|
case .ogTitle(let title):
|
||||||
|
return "<meta property='og:title' content='\(title)'>"
|
||||||
|
case .ogDescription(let description):
|
||||||
|
return "<meta property='og:description' content='\(description)'>"
|
||||||
|
case .ogImage(let image):
|
||||||
|
return "<meta property='og:image' content='\(image)'>"
|
||||||
|
case .ogUrl(let url):
|
||||||
|
return "<meta property='og:url' content='\(url)'>"
|
||||||
case .charset:
|
case .charset:
|
||||||
return "<meta charset='utf-8' />"
|
return "<meta charset='utf-8' />"
|
||||||
case .viewport:
|
case .viewport:
|
||||||
@ -141,6 +151,14 @@ extension HeaderElement: CustomStringConvertible {
|
|||||||
return "title"
|
return "title"
|
||||||
case .description:
|
case .description:
|
||||||
return "description"
|
return "description"
|
||||||
|
case .ogTitle:
|
||||||
|
return "og:title"
|
||||||
|
case .ogDescription:
|
||||||
|
return "og:description"
|
||||||
|
case .ogImage:
|
||||||
|
return "og:image"
|
||||||
|
case .ogUrl:
|
||||||
|
return "og:url"
|
||||||
case .charset:
|
case .charset:
|
||||||
return "charset"
|
return "charset"
|
||||||
case .viewport:
|
case .viewport:
|
||||||
|
@ -24,9 +24,11 @@ final class FeedPageGenerator {
|
|||||||
|
|
||||||
func generatePage(language: ContentLanguage,
|
func generatePage(language: ContentLanguage,
|
||||||
posts: [FeedEntryData],
|
posts: [FeedEntryData],
|
||||||
title: String,
|
title: String?,
|
||||||
description: String?,
|
description: String?,
|
||||||
showTitle: Bool,
|
image: FileResource?,
|
||||||
|
pageUrl: String,
|
||||||
|
pageTitle: String?,
|
||||||
pageNumber: Int,
|
pageNumber: Int,
|
||||||
totalPages: Int,
|
totalPages: Int,
|
||||||
languageButtonUrl: String,
|
languageButtonUrl: String,
|
||||||
@ -44,10 +46,14 @@ final class FeedPageGenerator {
|
|||||||
text: language.next.rawValue,
|
text: language.next.rawValue,
|
||||||
url: languageButtonUrl)
|
url: languageButtonUrl)
|
||||||
|
|
||||||
|
let imageUrl = image?.linkPreviewImage(results: results)
|
||||||
|
|
||||||
let pageHeader = PageHeader(
|
let pageHeader = PageHeader(
|
||||||
language: language,
|
language: language,
|
||||||
title: title,
|
title: title ?? pageTitle,
|
||||||
description: description,
|
description: description,
|
||||||
|
image: imageUrl,
|
||||||
|
pageUrl: pageUrl,
|
||||||
iconUrl: iconUrl,
|
iconUrl: iconUrl,
|
||||||
languageButton: languageButton,
|
languageButton: languageButton,
|
||||||
links: content.navigationBar(in: language),
|
links: content.navigationBar(in: language),
|
||||||
@ -57,8 +63,8 @@ final class FeedPageGenerator {
|
|||||||
let page = GenericPage(
|
let page = GenericPage(
|
||||||
header: pageHeader,
|
header: pageHeader,
|
||||||
additionalFooter: footer) { content in
|
additionalFooter: footer) { content in
|
||||||
if showTitle {
|
if let pageTitle {
|
||||||
content += "<h1 class='separated-headline'>\(title)</h1>"
|
content += "<h1 class='separated-headline'>\(pageTitle)</h1>"
|
||||||
}
|
}
|
||||||
for post in posts {
|
for post in posts {
|
||||||
content += FeedEntry(data: post).content
|
content += FeedEntry(data: post).content
|
||||||
|
@ -25,7 +25,7 @@ final class PageGenerator {
|
|||||||
text: settings.emptyPageText).content
|
text: settings.emptyPageText).content
|
||||||
}
|
}
|
||||||
|
|
||||||
func generate(page: Page, language: ContentLanguage, results: PageGenerationResults) -> String? {
|
func generate(page: Page, language: ContentLanguage, results: PageGenerationResults, pageUrl: String) -> String? {
|
||||||
let contentGenerator = PageContentParser(
|
let contentGenerator = PageContentParser(
|
||||||
content: content,
|
content: content,
|
||||||
language: language, results: results)
|
language: language, results: results)
|
||||||
@ -51,7 +51,7 @@ final class PageGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let headers = makeHeaders(requiredItems: results.requiredHeaders, results: results)
|
let headers = makeHeaders(requiredItems: results.requiredHeaders, results: results)
|
||||||
results.require(files: headers.compactMap { $0.file })
|
results.require(files: headers.compactMap { $0.requiredFile })
|
||||||
|
|
||||||
let iconUrl = content.settings.navigation.localized(in: language).rootUrl
|
let iconUrl = content.settings.navigation.localized(in: language).rootUrl
|
||||||
let languageUrl = page.absoluteUrl(in: language.next)
|
let languageUrl = page.absoluteUrl(in: language.next)
|
||||||
@ -59,10 +59,14 @@ final class PageGenerator {
|
|||||||
text: language.next.rawValue,
|
text: language.next.rawValue,
|
||||||
url: languageUrl)
|
url: languageUrl)
|
||||||
|
|
||||||
|
let imageUrl = localized.linkPreview.image?.linkPreviewImage(results: results)
|
||||||
|
|
||||||
let pageHeader = PageHeader(
|
let pageHeader = PageHeader(
|
||||||
language: language,
|
language: language,
|
||||||
title: localized.linkPreview.title ?? localized.title,
|
title: localized.linkPreview.title ?? localized.title,
|
||||||
description: localized.linkPreview.description,
|
description: localized.linkPreview.description,
|
||||||
|
image: imageUrl,
|
||||||
|
pageUrl: pageUrl,
|
||||||
iconUrl: iconUrl,
|
iconUrl: iconUrl,
|
||||||
languageButton: languageButton,
|
languageButton: languageButton,
|
||||||
links: content.navigationBar(in: language),
|
links: content.navigationBar(in: language),
|
||||||
|
@ -34,22 +34,45 @@ private struct TagHeaderContent {
|
|||||||
|
|
||||||
let description: String?
|
let description: String?
|
||||||
|
|
||||||
|
let image: String?
|
||||||
|
|
||||||
let iconUrl: String
|
let iconUrl: String
|
||||||
|
|
||||||
let links: [NavigationBar.Link]
|
let links: [NavigationBar.Link]
|
||||||
|
|
||||||
let headers: Set<HeaderElement>
|
let headers: Set<HeaderElement>
|
||||||
|
|
||||||
let baseUrl: String
|
let basePath: String
|
||||||
|
|
||||||
|
let websiteUrl: String
|
||||||
|
|
||||||
let localizedBaseUrl: String
|
let localizedBaseUrl: String
|
||||||
|
|
||||||
private func url(pageNumber: Int) -> String {
|
/**
|
||||||
baseUrl + "/\(pageNumber)"
|
The path (relative to the output folder) for the html file
|
||||||
|
*/
|
||||||
|
func filePath(pageNumber: Int) -> String {
|
||||||
|
basePath + "/\(pageNumber).html"
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileUrl(pageNumber: Int) -> String {
|
/**
|
||||||
url(pageNumber: pageNumber) + ".html"
|
The absolute url (without domain) for the language button of the page
|
||||||
|
*/
|
||||||
|
private func languageButtonUrl(page: Int) -> String {
|
||||||
|
guard page > 1 else {
|
||||||
|
return localizedBaseUrl
|
||||||
|
}
|
||||||
|
return localizedBaseUrl + "/\(page)"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The full url (including domain) to the page.
|
||||||
|
*/
|
||||||
|
private func pageUrl(page: Int) -> String {
|
||||||
|
guard page > 1 else {
|
||||||
|
return websiteUrl + basePath
|
||||||
|
}
|
||||||
|
return websiteUrl + basePath + "/\(page)"
|
||||||
}
|
}
|
||||||
|
|
||||||
func pageHeader(pageNumber: Int) -> PageHeader {
|
func pageHeader(pageNumber: Int) -> PageHeader {
|
||||||
@ -57,10 +80,12 @@ private struct TagHeaderContent {
|
|||||||
language: language,
|
language: language,
|
||||||
title: title,
|
title: title,
|
||||||
description: description,
|
description: description,
|
||||||
|
image: image,
|
||||||
|
pageUrl: pageUrl(page: pageNumber),
|
||||||
iconUrl: iconUrl,
|
iconUrl: iconUrl,
|
||||||
languageButton: .init(
|
languageButton: .init(
|
||||||
text: language.next.rawValue,
|
text: language.next.rawValue,
|
||||||
url: localizedBaseUrl + "/\(pageNumber)"),
|
url: languageButtonUrl(page: pageNumber)),
|
||||||
links: links,
|
links: links,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
icons: [])
|
icons: [])
|
||||||
@ -83,14 +108,19 @@ final class TagOverviewGenerator {
|
|||||||
|
|
||||||
func generatePages(tags: [Tag], overview: Tag) {
|
func generatePages(tags: [Tag], overview: Tag) {
|
||||||
let localized = overview.localized(in: language)
|
let localized = overview.localized(in: language)
|
||||||
|
|
||||||
|
let imageUrl = localized.linkPreview.image?.linkPreviewImage(results: results)
|
||||||
|
|
||||||
let header = TagHeaderContent(
|
let header = TagHeaderContent(
|
||||||
language: language,
|
language: language,
|
||||||
title: localized.linkPreview.title ?? localized.title,
|
title: localized.linkPreview.title ?? localized.title,
|
||||||
description: localized.linkPreview.description,
|
description: localized.linkPreview.description,
|
||||||
|
image: imageUrl,
|
||||||
iconUrl: content.settings.navigation.localized(in: language).rootUrl,
|
iconUrl: content.settings.navigation.localized(in: language).rootUrl,
|
||||||
links: content.navigationBar(in: language),
|
links: content.navigationBar(in: language),
|
||||||
headers: content.postPageHeaders,
|
headers: content.postPageHeaders,
|
||||||
baseUrl: overview.absoluteUrl(in: language),
|
basePath: overview.absoluteUrl(in: language),
|
||||||
|
websiteUrl: content.settings.general.url,
|
||||||
localizedBaseUrl: overview.absoluteUrl(in: language.next))
|
localizedBaseUrl: overview.absoluteUrl(in: language.next))
|
||||||
|
|
||||||
// Sort tags by title
|
// Sort tags by title
|
||||||
@ -140,16 +170,16 @@ final class TagOverviewGenerator {
|
|||||||
}
|
}
|
||||||
if totalPages > 1 {
|
if totalPages > 1 {
|
||||||
content += PostFeedPageNavigation(
|
content += PostFeedPageNavigation(
|
||||||
linkPrefix: header.baseUrl,
|
linkPrefix: header.basePath,
|
||||||
currentPage: pageNumber,
|
currentPage: pageNumber,
|
||||||
numberOfPages: totalPages).content
|
numberOfPages: totalPages).content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let fileContent = page.content
|
let fileContent = page.content
|
||||||
let url = header.fileUrl(pageNumber: pageNumber)
|
let filePath = header.filePath(pageNumber: pageNumber)
|
||||||
|
|
||||||
guard content.storage.write(fileContent, to: url) else {
|
guard content.storage.write(fileContent, to: filePath) else {
|
||||||
results.unsavedOutput(url, source: .tagOverview)
|
results.unsavedOutput(filePath, source: .tagOverview)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,20 +7,28 @@ struct FeedGeneratorSource: PostListPageGeneratorSource {
|
|||||||
|
|
||||||
let results: PageGenerationResults
|
let results: PageGenerationResults
|
||||||
|
|
||||||
var showTitle: Bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
var postsPerPage: Int {
|
var postsPerPage: Int {
|
||||||
content.settings.posts.postsPerPage
|
content.settings.posts.postsPerPage
|
||||||
}
|
}
|
||||||
|
|
||||||
var pageTitle: String {
|
var pageTitle: String? {
|
||||||
content.settings.posts.localized(in: language).title
|
nil // Don't show title in page
|
||||||
}
|
}
|
||||||
|
|
||||||
var pageDescription: String {
|
private var linkPreview: LinkPreview {
|
||||||
content.settings.posts.localized(in: language).description
|
content.settings.posts.localized(in: language).linkPreview
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkTitle: String? {
|
||||||
|
linkPreview.title
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkDescription: String? {
|
||||||
|
linkPreview.description
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkImage: FileResource? {
|
||||||
|
linkPreview.image
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,7 +17,15 @@ final class PostListPageGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func pageUrl(in language: ContentLanguage, pageNumber: Int) -> String {
|
private func pageUrl(in language: ContentLanguage, pageNumber: Int) -> String {
|
||||||
"\(source.pageUrlPrefix(for: language))/\(pageNumber)"
|
let base = source.content.settings.general.url + source.pageUrlPrefix(for: language)
|
||||||
|
guard pageNumber > 1 else {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
return base + "/\(pageNumber)"
|
||||||
|
}
|
||||||
|
|
||||||
|
private func filePath(in language: ContentLanguage, pageNumber: Int) -> String {
|
||||||
|
"\(source.pageUrlPrefix(for: language))/\(pageNumber).html"
|
||||||
}
|
}
|
||||||
|
|
||||||
func createPages(for posts: [Post]) {
|
func createPages(for posts: [Post]) {
|
||||||
@ -82,18 +90,23 @@ final class PostListPageGenerator {
|
|||||||
// Includes leading slash
|
// Includes leading slash
|
||||||
let languageButtonUrl = pageUrl(in: language.next, pageNumber: pageIndex)
|
let languageButtonUrl = pageUrl(in: language.next, pageNumber: pageIndex)
|
||||||
|
|
||||||
|
// Includes leading slash
|
||||||
|
let pageUrl = pageUrl(in: language, pageNumber: pageIndex)
|
||||||
|
|
||||||
let fileContent = feedPageGenerator.generatePage(
|
let fileContent = feedPageGenerator.generatePage(
|
||||||
language: language,
|
language: language,
|
||||||
posts: posts,
|
posts: posts,
|
||||||
title: source.pageTitle,
|
title: source.linkTitle,
|
||||||
description: source.pageDescription,
|
description: source.linkDescription,
|
||||||
showTitle: source.showTitle,
|
image: source.linkImage,
|
||||||
|
pageUrl: pageUrl,
|
||||||
|
pageTitle: source.pageTitle,
|
||||||
pageNumber: pageIndex,
|
pageNumber: pageIndex,
|
||||||
totalPages: pageCount,
|
totalPages: pageCount,
|
||||||
languageButtonUrl: languageButtonUrl,
|
languageButtonUrl: languageButtonUrl,
|
||||||
linkPrefix: source.pageUrlPrefix(for: language))
|
linkPrefix: source.pageUrlPrefix(for: language))
|
||||||
// Includes leading slash
|
|
||||||
let filePath = pageUrl(in: language, pageNumber: pageIndex) + ".html"
|
let filePath = self.filePath(in: language, pageNumber: pageIndex)
|
||||||
guard save(fileContent, to: filePath) else {
|
guard save(fileContent, to: filePath) else {
|
||||||
source.results.unsavedOutput(filePath, source: .feed)
|
source.results.unsavedOutput(filePath, source: .feed)
|
||||||
return
|
return
|
||||||
|
@ -7,11 +7,13 @@ protocol PostListPageGeneratorSource {
|
|||||||
|
|
||||||
var results: PageGenerationResults { get }
|
var results: PageGenerationResults { get }
|
||||||
|
|
||||||
var showTitle: Bool { get }
|
var pageTitle: String? { get }
|
||||||
|
|
||||||
var pageTitle: String { get }
|
var linkTitle: String? { get }
|
||||||
|
|
||||||
var pageDescription: String { get }
|
var linkDescription: String? { get }
|
||||||
|
|
||||||
|
var linkImage: FileResource? { get }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The url to the page, including a leading slash
|
The url to the page, including a leading slash
|
||||||
|
@ -9,20 +9,28 @@ struct TagPageGeneratorSource: PostListPageGeneratorSource {
|
|||||||
|
|
||||||
let tag: Tag
|
let tag: Tag
|
||||||
|
|
||||||
var showTitle: Bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
var postsPerPage: Int {
|
var postsPerPage: Int {
|
||||||
content.settings.posts.postsPerPage
|
content.settings.posts.postsPerPage
|
||||||
}
|
}
|
||||||
|
|
||||||
var pageTitle: String {
|
var pageTitle: String? {
|
||||||
tag.localized(in: language).name
|
linkPreview.title ?? tag.localized(in: language).name
|
||||||
}
|
}
|
||||||
|
|
||||||
var pageDescription: String {
|
private var linkPreview: LinkPreview {
|
||||||
tag.localized(in: language).linkPreview.description ?? ""
|
tag.localized(in: language).linkPreview
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkTitle: String? {
|
||||||
|
linkPreview.title
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkDescription: String? {
|
||||||
|
linkPreview.description
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkImage: FileResource? {
|
||||||
|
linkPreview.image
|
||||||
}
|
}
|
||||||
|
|
||||||
func pageUrlPrefix(for language: ContentLanguage) -> String {
|
func pageUrlPrefix(for language: ContentLanguage) -> String {
|
||||||
|
@ -281,13 +281,15 @@ extension Content {
|
|||||||
|
|
||||||
results.require(files: page.requiredFiles)
|
results.require(files: page.requiredFiles)
|
||||||
|
|
||||||
guard let content = pageGenerator.generate(page: page, language: language, results: results) else {
|
let relativePageUrl = page.absoluteUrl(in: language)
|
||||||
|
let filePath = relativePageUrl + ".html"
|
||||||
|
let pageUrl = settings.general.url + relativePageUrl
|
||||||
|
|
||||||
|
guard let content = pageGenerator.generate(page: page, language: language, results: results, pageUrl: pageUrl) else {
|
||||||
print("Failed to generate page \(page.id) in language \(language)")
|
print("Failed to generate page \(page.id) in language \(language)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let pageUrl = page.absoluteUrl(in: language)
|
|
||||||
let filePath = pageUrl + ".html"
|
|
||||||
guard storage.write(content, to: filePath) else {
|
guard storage.write(content, to: filePath) else {
|
||||||
print("Failed to save page \(page.id)")
|
print("Failed to save page \(page.id)")
|
||||||
return
|
return
|
||||||
|
@ -246,6 +246,20 @@ final class FileResource: Item, LocalizedItem {
|
|||||||
.init(image: self, type: type, maximumWidth: width, maximumHeight: height)
|
.init(image: self, type: type, maximumWidth: width, maximumHeight: height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func linkPreviewImage(results: PageGenerationResults) -> String {
|
||||||
|
let type: FileType
|
||||||
|
switch self.type {
|
||||||
|
case .jpg, .png, .gif: type = self.type
|
||||||
|
default: type = .jpg
|
||||||
|
}
|
||||||
|
let version = imageVersion(
|
||||||
|
width: content.settings.general.linkPreviewImageWidth,
|
||||||
|
height: content.settings.general.linkPreviewImageHeight,
|
||||||
|
type: type)
|
||||||
|
results.require(image: version)
|
||||||
|
return content.settings.general.url + version.outputPath
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Paths
|
// MARK: Paths
|
||||||
|
|
||||||
func removeFileFromOutputFolder() {
|
func removeFileFromOutputFolder() {
|
||||||
|
42
CHDataManagement/Model/Settings/GeneralSettings.swift
Normal file
42
CHDataManagement/Model/Settings/GeneralSettings.swift
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class GeneralSettings: ObservableObject {
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var url: String
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var linkPreviewImageWidth: Int
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var linkPreviewImageHeight: Int
|
||||||
|
|
||||||
|
init(url: String, linkPreviewImageWidth: Int, linkPreviewImageHeight: Int) {
|
||||||
|
self.url = url
|
||||||
|
self.linkPreviewImageWidth = linkPreviewImageWidth
|
||||||
|
self.linkPreviewImageHeight = linkPreviewImageHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension GeneralSettings {
|
||||||
|
|
||||||
|
convenience init(data: Data) {
|
||||||
|
self.init(
|
||||||
|
url: data.url,
|
||||||
|
linkPreviewImageWidth: data.linkPreviewImageWidth,
|
||||||
|
linkPreviewImageHeight: data.linkPreviewImageHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
var data: Data {
|
||||||
|
.init(
|
||||||
|
url: url,
|
||||||
|
linkPreviewImageWidth: linkPreviewImageWidth,
|
||||||
|
linkPreviewImageHeight: linkPreviewImageHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Data: Codable {
|
||||||
|
let url: String
|
||||||
|
let linkPreviewImageWidth: Int
|
||||||
|
let linkPreviewImageHeight: Int
|
||||||
|
}
|
||||||
|
}
|
@ -2,13 +2,7 @@ import Foundation
|
|||||||
|
|
||||||
final class LocalizedPostSettings: ObservableObject {
|
final class LocalizedPostSettings: ObservableObject {
|
||||||
|
|
||||||
/// The page title for the post feed
|
var linkPreview: LinkPreview
|
||||||
@Published
|
|
||||||
var title: String
|
|
||||||
|
|
||||||
/// The page description for the post feed
|
|
||||||
@Published
|
|
||||||
var description: String
|
|
||||||
|
|
||||||
/// The path to the feed in the final website, appended with the page number
|
/// The path to the feed in the final website, appended with the page number
|
||||||
@Published
|
@Published
|
||||||
@ -22,11 +16,10 @@ final class LocalizedPostSettings: ObservableObject {
|
|||||||
@Published
|
@Published
|
||||||
var defaultPageLinkText: String
|
var defaultPageLinkText: String
|
||||||
|
|
||||||
init(title: String, description: String, feedUrlPrefix: String, defaultPageLinkText: String) {
|
init(feedUrlPrefix: String, defaultPageLinkText: String, linkPreview: LinkPreview) {
|
||||||
self.title = title
|
|
||||||
self.description = description
|
|
||||||
self.feedUrlPrefix = feedUrlPrefix
|
self.feedUrlPrefix = feedUrlPrefix
|
||||||
self.defaultPageLinkText = defaultPageLinkText
|
self.defaultPageLinkText = defaultPageLinkText
|
||||||
|
self.linkPreview = linkPreview
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,26 +27,23 @@ final class LocalizedPostSettings: ObservableObject {
|
|||||||
|
|
||||||
extension LocalizedPostSettings {
|
extension LocalizedPostSettings {
|
||||||
|
|
||||||
convenience init(data: Data) {
|
convenience init(context: LoadingContext, data: Data) {
|
||||||
self.init(
|
self.init(
|
||||||
title: data.feedTitle,
|
|
||||||
description: data.feedDescription,
|
|
||||||
feedUrlPrefix: data.feedUrlPrefix,
|
feedUrlPrefix: data.feedUrlPrefix,
|
||||||
defaultPageLinkText: data.defaultPageLinkText)
|
defaultPageLinkText: data.defaultPageLinkText,
|
||||||
|
linkPreview: .init(context: context, data: data.linkPreview))
|
||||||
}
|
}
|
||||||
|
|
||||||
var data: Data {
|
var data: Data {
|
||||||
.init(
|
.init(
|
||||||
feedTitle: title,
|
|
||||||
feedDescription: description,
|
|
||||||
feedUrlPrefix: feedUrlPrefix,
|
feedUrlPrefix: feedUrlPrefix,
|
||||||
defaultPageLinkText: defaultPageLinkText)
|
defaultPageLinkText: defaultPageLinkText,
|
||||||
|
linkPreview: linkPreview.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Data: Codable {
|
struct Data: Codable {
|
||||||
let feedTitle: String
|
|
||||||
let feedDescription: String
|
|
||||||
let feedUrlPrefix: String
|
let feedUrlPrefix: String
|
||||||
let defaultPageLinkText: String
|
let defaultPageLinkText: String
|
||||||
|
let linkPreview: LinkPreview.Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,8 +65,8 @@ extension PostSettings {
|
|||||||
swiperCssFile: data.swiperCssFile.map(context.file),
|
swiperCssFile: data.swiperCssFile.map(context.file),
|
||||||
swiperJsFile: data.swiperJsFile.map(context.file),
|
swiperJsFile: data.swiperJsFile.map(context.file),
|
||||||
defaultCssFile: data.defaultCssFile.map(context.file),
|
defaultCssFile: data.defaultCssFile.map(context.file),
|
||||||
german: .init(data: data.german),
|
german: .init(context: context, data: data.german),
|
||||||
english: .init(data: data.english))
|
english: .init(context: context, data: data.english))
|
||||||
}
|
}
|
||||||
|
|
||||||
var data: PostSettings.Data {
|
var data: PostSettings.Data {
|
||||||
|
@ -2,6 +2,9 @@ import Foundation
|
|||||||
|
|
||||||
final class Settings: ObservableObject {
|
final class Settings: ObservableObject {
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var general: GeneralSettings
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var paths: PathSettings
|
var paths: PathSettings
|
||||||
|
|
||||||
@ -18,11 +21,13 @@ final class Settings: ObservableObject {
|
|||||||
@Published
|
@Published
|
||||||
var audioPlayer: AudioPlayerSettings
|
var audioPlayer: AudioPlayerSettings
|
||||||
|
|
||||||
init(paths: PathSettings,
|
init(general: GeneralSettings,
|
||||||
|
paths: PathSettings,
|
||||||
navigation: NavigationSettings,
|
navigation: NavigationSettings,
|
||||||
posts: PostSettings,
|
posts: PostSettings,
|
||||||
pages: PageSettings,
|
pages: PageSettings,
|
||||||
audioPlayer: AudioPlayerSettings) {
|
audioPlayer: AudioPlayerSettings) {
|
||||||
|
self.general = general
|
||||||
self.paths = paths
|
self.paths = paths
|
||||||
self.navigation = navigation
|
self.navigation = navigation
|
||||||
self.posts = posts
|
self.posts = posts
|
||||||
@ -43,6 +48,7 @@ extension Settings {
|
|||||||
|
|
||||||
convenience init(context: LoadingContext, data: Settings.Data) {
|
convenience init(context: LoadingContext, data: Settings.Data) {
|
||||||
self.init(
|
self.init(
|
||||||
|
general: .init(data: data.general),
|
||||||
paths: .init(data: data.paths),
|
paths: .init(data: data.paths),
|
||||||
navigation: .init(context: context, data: data.navigation),
|
navigation: .init(context: context, data: data.navigation),
|
||||||
posts: .init(context: context, data: data.posts),
|
posts: .init(context: context, data: data.posts),
|
||||||
@ -52,6 +58,7 @@ extension Settings {
|
|||||||
|
|
||||||
func data(tagOverview: Tag?) -> Data {
|
func data(tagOverview: Tag?) -> Data {
|
||||||
.init(
|
.init(
|
||||||
|
general: general.data,
|
||||||
paths: paths.data,
|
paths: paths.data,
|
||||||
navigation: navigation.data,
|
navigation: navigation.data,
|
||||||
posts: posts.data,
|
posts: posts.data,
|
||||||
@ -61,6 +68,7 @@ extension Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Data: Codable {
|
struct Data: Codable {
|
||||||
|
let general: GeneralSettings.Data
|
||||||
let paths: PathSettings.Data
|
let paths: PathSettings.Data
|
||||||
let navigation: NavigationSettings.Data
|
let navigation: NavigationSettings.Data
|
||||||
let posts: PostSettings.Data
|
let posts: PostSettings.Data
|
||||||
@ -73,6 +81,7 @@ extension Settings {
|
|||||||
extension Settings {
|
extension Settings {
|
||||||
|
|
||||||
static let `default`: Settings = .init(
|
static let `default`: Settings = .init(
|
||||||
|
general: .default,
|
||||||
paths: .default,
|
paths: .default,
|
||||||
navigation: .default,
|
navigation: .default,
|
||||||
posts: .default,
|
posts: .default,
|
||||||
@ -80,6 +89,14 @@ extension Settings {
|
|||||||
audioPlayer: .default)
|
audioPlayer: .default)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension GeneralSettings {
|
||||||
|
|
||||||
|
static let `default`: GeneralSettings = .init(
|
||||||
|
url: "https://example.com",
|
||||||
|
linkPreviewImageWidth: 1200,
|
||||||
|
linkPreviewImageHeight: 630)
|
||||||
|
}
|
||||||
|
|
||||||
extension AudioPlayerSettings {
|
extension AudioPlayerSettings {
|
||||||
|
|
||||||
static let `default`: AudioPlayerSettings = .init(
|
static let `default`: AudioPlayerSettings = .init(
|
||||||
@ -100,15 +117,18 @@ extension PostSettings {
|
|||||||
swiperJsFile: nil,
|
swiperJsFile: nil,
|
||||||
defaultCssFile: nil,
|
defaultCssFile: nil,
|
||||||
german: .init(
|
german: .init(
|
||||||
|
feedUrlPrefix: "blog",
|
||||||
|
defaultPageLinkText: "Anzeigen",
|
||||||
|
linkPreview: .init(
|
||||||
title: "Beiträge",
|
title: "Beiträge",
|
||||||
description: "Alle Beiträge",
|
description: "Alle Beiträge")),
|
||||||
feedUrlPrefix: "blog",
|
|
||||||
defaultPageLinkText: "Anzeigen"),
|
|
||||||
english: .init(
|
english: .init(
|
||||||
title: "Blog posts",
|
|
||||||
description: "All blog posts",
|
|
||||||
feedUrlPrefix: "blog",
|
feedUrlPrefix: "blog",
|
||||||
defaultPageLinkText: "View"))
|
defaultPageLinkText: "View",
|
||||||
|
linkPreview: .init(
|
||||||
|
title: "Blog posts",
|
||||||
|
description: "All blog posts"))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +15,10 @@ struct PageHeader: HtmlProducer {
|
|||||||
|
|
||||||
init(
|
init(
|
||||||
language: ContentLanguage,
|
language: ContentLanguage,
|
||||||
title: String,
|
title: String?,
|
||||||
description: String?,
|
description: String?,
|
||||||
|
image: String?,
|
||||||
|
pageUrl: String,
|
||||||
iconUrl: String,
|
iconUrl: String,
|
||||||
languageButton: NavigationBar.Link,
|
languageButton: NavigationBar.Link,
|
||||||
links: [NavigationBar.Link],
|
links: [NavigationBar.Link],
|
||||||
@ -29,10 +31,18 @@ struct PageHeader: HtmlProducer {
|
|||||||
self.icons = icons
|
self.icons = icons
|
||||||
|
|
||||||
var headers = headers
|
var headers = headers
|
||||||
|
if let title {
|
||||||
headers.insert(.title(title))
|
headers.insert(.title(title))
|
||||||
|
headers.insert(.ogTitle(title))
|
||||||
|
}
|
||||||
if let description {
|
if let description {
|
||||||
headers.insert(.description(description))
|
headers.insert(.description(description))
|
||||||
|
headers.insert(.ogDescription(description))
|
||||||
}
|
}
|
||||||
|
if let image {
|
||||||
|
headers.insert(.ogImage(image))
|
||||||
|
}
|
||||||
|
headers.insert(.ogUrl(pageUrl))
|
||||||
self.headers = headers.sorted()
|
self.headers = headers.sorted()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct GeneralSettingsDetailView: View {
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var generalSettings: GeneralSettings
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
DetailTitle(title: "General",
|
||||||
|
text: "General settings for the webpage")
|
||||||
|
|
||||||
|
StringPropertyView(
|
||||||
|
title: "Website URL",
|
||||||
|
text: $generalSettings.url,
|
||||||
|
footer: "The base path where the website is deployed, to generate absolute links")
|
||||||
|
|
||||||
|
IntegerPropertyView(
|
||||||
|
title: "Link Preview Image Width",
|
||||||
|
value: $generalSettings.linkPreviewImageWidth,
|
||||||
|
footer: "The maximum width of a link preview image")
|
||||||
|
|
||||||
|
IntegerPropertyView(
|
||||||
|
title: "Link Preview Image Height",
|
||||||
|
value: $generalSettings.linkPreviewImageHeight,
|
||||||
|
footer: "The maximum height of a link preview image")
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,8 @@ struct GenerationDetailView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
switch section {
|
switch section {
|
||||||
|
case .general:
|
||||||
|
GeneralSettingsDetailView(generalSettings: content.settings.general)
|
||||||
case .folders:
|
case .folders:
|
||||||
PathSettingsView()
|
PathSettingsView()
|
||||||
case .navigationBar:
|
case .navigationBar:
|
||||||
|
@ -5,6 +5,8 @@ struct LocalizedPostFeedSettingsView: View {
|
|||||||
@ObservedObject
|
@ObservedObject
|
||||||
var settings: LocalizedPostSettings
|
var settings: LocalizedPostSettings
|
||||||
|
|
||||||
|
let transferImage: (language: ContentLanguage, image: FileResource)?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
StringPropertyView(
|
StringPropertyView(
|
||||||
@ -12,25 +14,19 @@ struct LocalizedPostFeedSettingsView: View {
|
|||||||
text: $settings.defaultPageLinkText,
|
text: $settings.defaultPageLinkText,
|
||||||
footer: "The text to display when linking from a post to a page. Each post can provide a custom text.")
|
footer: "The text to display when linking from a post to a page. Each post can provide a custom text.")
|
||||||
|
|
||||||
StringPropertyView(
|
|
||||||
title: "Title",
|
|
||||||
text: $settings.title,
|
|
||||||
footer: "The title of all post feed pages.")
|
|
||||||
|
|
||||||
StringPropertyView(
|
StringPropertyView(
|
||||||
title: "URL prefix",
|
title: "URL prefix",
|
||||||
text: $settings.feedUrlPrefix,
|
text: $settings.feedUrlPrefix,
|
||||||
footer: "The prefix to generate the urls for all post feed pages.")
|
footer: "The prefix to generate the urls for all post feed pages.")
|
||||||
|
|
||||||
TextFieldPropertyView(
|
LinkPreviewDetailView(linkPreview: settings.linkPreview, fallbackTitle: nil, transferImage: transferImage)
|
||||||
title: "Description",
|
|
||||||
text: $settings.description,
|
|
||||||
footer: "The description of all post feed pages.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
LocalizedPostFeedSettingsView(settings: PostSettings.default.english)
|
LocalizedPostFeedSettingsView(
|
||||||
|
settings: PostSettings.default.english,
|
||||||
|
transferImage: nil)
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,12 @@ struct PostFeedSettingsView: View {
|
|||||||
@ObservedObject
|
@ObservedObject
|
||||||
var postSettings: PostSettings
|
var postSettings: PostSettings
|
||||||
|
|
||||||
|
private var transferImage: (language: ContentLanguage, image: FileResource)? {
|
||||||
|
postSettings.localized(in: language.next).linkPreview.image.map {
|
||||||
|
(language.next, $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
@ -40,7 +46,8 @@ struct PostFeedSettingsView: View {
|
|||||||
selectedFile: $postSettings.swiperJsFile)
|
selectedFile: $postSettings.swiperJsFile)
|
||||||
|
|
||||||
LocalizedPostFeedSettingsView(
|
LocalizedPostFeedSettingsView(
|
||||||
settings: postSettings.localized(in: language))
|
settings: postSettings.localized(in: language),
|
||||||
|
transferImage: transferImage)
|
||||||
.id(language)
|
.id(language)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
@ -2,6 +2,8 @@ import SFSafeSymbols
|
|||||||
|
|
||||||
enum SettingsSection: String {
|
enum SettingsSection: String {
|
||||||
|
|
||||||
|
case general = "General"
|
||||||
|
|
||||||
case folders = "Folders"
|
case folders = "Folders"
|
||||||
|
|
||||||
case navigationBar = "Navigation Bar"
|
case navigationBar = "Navigation Bar"
|
||||||
@ -20,6 +22,7 @@ extension SettingsSection {
|
|||||||
|
|
||||||
var icon: SFSymbol {
|
var icon: SFSymbol {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .general: return .noteText
|
||||||
case .folders: return .folder
|
case .folders: return .folder
|
||||||
case .navigationBar: return .menubarArrowUpRectangle
|
case .navigationBar: return .menubarArrowUpRectangle
|
||||||
case .postFeed: return .rectangleGrid1x2
|
case .postFeed: return .rectangleGrid1x2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user