diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index 158cff3..4e829eb 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -185,6 +185,8 @@ E2FD1D282D2F2DAD00B48627 /* ErrorPrinter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D272D2F2D9100B48627 /* ErrorPrinter.swift */; }; E2FD1D2A2D35B74C00B48627 /* TextWithPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D292D35B74C00B48627 /* TextWithPopup.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 */; }; E2FE0EE82D16D4A3002963B7 /* ConvertThrowing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EE72D16D4A3002963B7 /* ConvertThrowing.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 = ""; }; E2FD1D292D35B74C00B48627 /* TextWithPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextWithPopup.swift; sourceTree = ""; }; E2FD1D2B2D35B76D00B48627 /* ListPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListPopup.swift; sourceTree = ""; }; + E2FD1D2D2D37180600B48627 /* GeneralSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettings.swift; sourceTree = ""; }; + E2FD1D2F2D37196500B48627 /* GeneralSettingsDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsDetailView.swift; sourceTree = ""; }; E2FE0EE52D15A0B1002963B7 /* GenerationResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerationResults.swift; sourceTree = ""; }; E2FE0EE72D16D4A3002963B7 /* ConvertThrowing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConvertThrowing.swift; sourceTree = ""; }; E2FE0EEB2D1C124E002963B7 /* MultiFileSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiFileSelectionView.swift; sourceTree = ""; }; @@ -508,6 +512,7 @@ E25DA53B2D0042EA00AEF16D /* Settings */ = { isa = PBXGroup; children = ( + E2FD1D2D2D37180600B48627 /* GeneralSettings.swift */, E2FE0F392D2B3E4E002963B7 /* AudioPlayerSettings.swift */, E2FE0F6D2D2D3685002963B7 /* LocalizedAudioPlayerSettings.swift */, E2FE0F012D266FCB002963B7 /* LocalizedNavigationSettings.swift */, @@ -639,6 +644,7 @@ E25DA5702D01015400AEF16D /* GenerationContentView.swift */, E29D31702D08234D0051B7F4 /* GenerationDetailView.swift */, E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */, + E2FD1D2F2D37196500B48627 /* GeneralSettingsDetailView.swift */, E29D318C2D0B2E5E0051B7F4 /* Content */, E2FE0F032D2671FC002963B7 /* LocalizedNavigationBarSettingsView.swift */, E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */, @@ -1130,6 +1136,7 @@ E22990462D10B7A7009F8D77 /* SecurityScopeStatus.swift in Sources */, E29D31512D06168E0051B7F4 /* PostListView.swift in Sources */, E2FE0F4F2D2BCD80002963B7 /* TagLinkCommand.swift in Sources */, + E2FD1D302D37196C00B48627 /* GeneralSettingsDetailView.swift in Sources */, E22990342D0F77E9009F8D77 /* PagePropertyView.swift in Sources */, E2A37D0C2CE4036B0000979F /* LocalizedPage.swift in Sources */, E2A21C102CB18B3A0060935B /* FlowHStack.swift in Sources */, @@ -1163,6 +1170,7 @@ E22990262D0F582B009F8D77 /* FilePropertyView.swift in Sources */, E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */, E2FE0F172D2698D5002963B7 /* LocalizedPageId.swift in Sources */, + E2FD1D2E2D37180900B48627 /* GeneralSettings.swift in Sources */, E2FE0F0D2D268A09002963B7 /* PostListPageGeneratorSource.swift in Sources */, E2FE0F402D2B45D3002963B7 /* SwiftBlock.swift in Sources */, E25DA58B2D020C9500AEF16D /* PageImage.swift in Sources */, diff --git a/CHDataManagement/Generator/HeaderElement.swift b/CHDataManagement/Generator/HeaderElement.swift index 1216de0..e3b133b 100644 --- a/CHDataManagement/Generator/HeaderElement.swift +++ b/CHDataManagement/Generator/HeaderElement.swift @@ -36,6 +36,14 @@ enum HeaderElement { case description(String) + case ogTitle(String) + + case ogDescription(String) + + case ogImage(String) + + case ogUrl(String) + case charset case viewport @@ -44,30 +52,24 @@ enum HeaderElement { var order: Int { switch self { - case .charset: - return 1 - case .robots: - return 2 - case .viewport: - return 3 - case .icon: - return 10 - case .css(_, let order): - return order - case .js: - return 20 - case .jsModule: - return 30 - case .author: - return 100 - case .title: - return 101 - case .description: - return 102 + case .charset: 1 + case .robots: 2 + case .viewport: 3 + case .icon: 10 + case .css(_, let order): order + case .js: 20 + case .jsModule: 30 + case .author: 100 + case .title: 101 + case .description: 102 + case .ogTitle: 103 + case .ogDescription: 104 + case .ogImage: 105 + case .ogUrl: 106 } } - var file: FileResource? { + var requiredFile: FileResource? { switch self { case .icon(let file, _, _): return file @@ -113,6 +115,14 @@ extension HeaderElement { return "\(title)" case .description(let description): return "" + case .ogTitle(let title): + return "" + case .ogDescription(let description): + return "" + case .ogImage(let image): + return "" + case .ogUrl(let url): + return "" case .charset: return "" case .viewport: @@ -141,6 +151,14 @@ extension HeaderElement: CustomStringConvertible { return "title" case .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: return "charset" case .viewport: diff --git a/CHDataManagement/Generator/Page Generators/FeedPageGenerator.swift b/CHDataManagement/Generator/Page Generators/FeedPageGenerator.swift index bf8a18c..3bf0f4f 100644 --- a/CHDataManagement/Generator/Page Generators/FeedPageGenerator.swift +++ b/CHDataManagement/Generator/Page Generators/FeedPageGenerator.swift @@ -24,9 +24,11 @@ final class FeedPageGenerator { func generatePage(language: ContentLanguage, posts: [FeedEntryData], - title: String, + title: String?, description: String?, - showTitle: Bool, + image: FileResource?, + pageUrl: String, + pageTitle: String?, pageNumber: Int, totalPages: Int, languageButtonUrl: String, @@ -44,10 +46,14 @@ final class FeedPageGenerator { text: language.next.rawValue, url: languageButtonUrl) + let imageUrl = image?.linkPreviewImage(results: results) + let pageHeader = PageHeader( language: language, - title: title, + title: title ?? pageTitle, description: description, + image: imageUrl, + pageUrl: pageUrl, iconUrl: iconUrl, languageButton: languageButton, links: content.navigationBar(in: language), @@ -57,8 +63,8 @@ final class FeedPageGenerator { let page = GenericPage( header: pageHeader, additionalFooter: footer) { content in - if showTitle { - content += "

\(title)

" + if let pageTitle { + content += "

\(pageTitle)

" } for post in posts { content += FeedEntry(data: post).content diff --git a/CHDataManagement/Generator/Page Generators/PageGenerator.swift b/CHDataManagement/Generator/Page Generators/PageGenerator.swift index b9dac44..ea13948 100644 --- a/CHDataManagement/Generator/Page Generators/PageGenerator.swift +++ b/CHDataManagement/Generator/Page Generators/PageGenerator.swift @@ -25,7 +25,7 @@ final class PageGenerator { 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( content: content, language: language, results: results) @@ -51,7 +51,7 @@ final class PageGenerator { } 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 languageUrl = page.absoluteUrl(in: language.next) @@ -59,10 +59,14 @@ final class PageGenerator { text: language.next.rawValue, url: languageUrl) + let imageUrl = localized.linkPreview.image?.linkPreviewImage(results: results) + let pageHeader = PageHeader( language: language, title: localized.linkPreview.title ?? localized.title, description: localized.linkPreview.description, + image: imageUrl, + pageUrl: pageUrl, iconUrl: iconUrl, languageButton: languageButton, links: content.navigationBar(in: language), diff --git a/CHDataManagement/Generator/Page Generators/TagOverviewGenerator.swift b/CHDataManagement/Generator/Page Generators/TagOverviewGenerator.swift index a914b78..084d80f 100644 --- a/CHDataManagement/Generator/Page Generators/TagOverviewGenerator.swift +++ b/CHDataManagement/Generator/Page Generators/TagOverviewGenerator.swift @@ -34,22 +34,45 @@ private struct TagHeaderContent { let description: String? + let image: String? + let iconUrl: String let links: [NavigationBar.Link] let headers: Set - let baseUrl: String + let basePath: String + + let websiteUrl: 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 { @@ -57,10 +80,12 @@ private struct TagHeaderContent { language: language, title: title, description: description, + image: image, + pageUrl: pageUrl(page: pageNumber), iconUrl: iconUrl, languageButton: .init( text: language.next.rawValue, - url: localizedBaseUrl + "/\(pageNumber)"), + url: languageButtonUrl(page: pageNumber)), links: links, headers: headers, icons: []) @@ -83,14 +108,19 @@ final class TagOverviewGenerator { func generatePages(tags: [Tag], overview: Tag) { let localized = overview.localized(in: language) + + let imageUrl = localized.linkPreview.image?.linkPreviewImage(results: results) + let header = TagHeaderContent( language: language, title: localized.linkPreview.title ?? localized.title, description: localized.linkPreview.description, + image: imageUrl, iconUrl: content.settings.navigation.localized(in: language).rootUrl, links: content.navigationBar(in: language), headers: content.postPageHeaders, - baseUrl: overview.absoluteUrl(in: language), + basePath: overview.absoluteUrl(in: language), + websiteUrl: content.settings.general.url, localizedBaseUrl: overview.absoluteUrl(in: language.next)) // Sort tags by title @@ -140,16 +170,16 @@ final class TagOverviewGenerator { } if totalPages > 1 { content += PostFeedPageNavigation( - linkPrefix: header.baseUrl, + linkPrefix: header.basePath, currentPage: pageNumber, numberOfPages: totalPages).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 { - results.unsavedOutput(url, source: .tagOverview) + guard content.storage.write(fileContent, to: filePath) else { + results.unsavedOutput(filePath, source: .tagOverview) return } } diff --git a/CHDataManagement/Generator/Post Lists/FeedGeneratorSource.swift b/CHDataManagement/Generator/Post Lists/FeedGeneratorSource.swift index 23d8d01..2e150c0 100644 --- a/CHDataManagement/Generator/Post Lists/FeedGeneratorSource.swift +++ b/CHDataManagement/Generator/Post Lists/FeedGeneratorSource.swift @@ -7,20 +7,28 @@ struct FeedGeneratorSource: PostListPageGeneratorSource { let results: PageGenerationResults - var showTitle: Bool { - false - } - var postsPerPage: Int { content.settings.posts.postsPerPage } - var pageTitle: String { - content.settings.posts.localized(in: language).title + var pageTitle: String? { + nil // Don't show title in page } - var pageDescription: String { - content.settings.posts.localized(in: language).description + private var linkPreview: LinkPreview { + content.settings.posts.localized(in: language).linkPreview + } + + var linkTitle: String? { + linkPreview.title + } + + var linkDescription: String? { + linkPreview.description + } + + var linkImage: FileResource? { + linkPreview.image } /** diff --git a/CHDataManagement/Generator/Post Lists/PostListPageGenerator.swift b/CHDataManagement/Generator/Post Lists/PostListPageGenerator.swift index dd4bc32..6a95935 100644 --- a/CHDataManagement/Generator/Post Lists/PostListPageGenerator.swift +++ b/CHDataManagement/Generator/Post Lists/PostListPageGenerator.swift @@ -17,7 +17,15 @@ final class PostListPageGenerator { } 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]) { @@ -82,18 +90,23 @@ final class PostListPageGenerator { // Includes leading slash let languageButtonUrl = pageUrl(in: language.next, pageNumber: pageIndex) + // Includes leading slash + let pageUrl = pageUrl(in: language, pageNumber: pageIndex) + let fileContent = feedPageGenerator.generatePage( language: language, posts: posts, - title: source.pageTitle, - description: source.pageDescription, - showTitle: source.showTitle, + title: source.linkTitle, + description: source.linkDescription, + image: source.linkImage, + pageUrl: pageUrl, + pageTitle: source.pageTitle, pageNumber: pageIndex, totalPages: pageCount, languageButtonUrl: languageButtonUrl, 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 { source.results.unsavedOutput(filePath, source: .feed) return diff --git a/CHDataManagement/Generator/Post Lists/PostListPageGeneratorSource.swift b/CHDataManagement/Generator/Post Lists/PostListPageGeneratorSource.swift index f44396e..6cf17fb 100644 --- a/CHDataManagement/Generator/Post Lists/PostListPageGeneratorSource.swift +++ b/CHDataManagement/Generator/Post Lists/PostListPageGeneratorSource.swift @@ -7,11 +7,13 @@ protocol PostListPageGeneratorSource { 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 diff --git a/CHDataManagement/Generator/Post Lists/TagPageGeneratorSource.swift b/CHDataManagement/Generator/Post Lists/TagPageGeneratorSource.swift index 4e953b3..38ae61e 100644 --- a/CHDataManagement/Generator/Post Lists/TagPageGeneratorSource.swift +++ b/CHDataManagement/Generator/Post Lists/TagPageGeneratorSource.swift @@ -9,20 +9,28 @@ struct TagPageGeneratorSource: PostListPageGeneratorSource { let tag: Tag - var showTitle: Bool { - true - } - var postsPerPage: Int { content.settings.posts.postsPerPage } - var pageTitle: String { - tag.localized(in: language).name + var pageTitle: String? { + linkPreview.title ?? tag.localized(in: language).name } - var pageDescription: String { - tag.localized(in: language).linkPreview.description ?? "" + private var linkPreview: LinkPreview { + 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 { diff --git a/CHDataManagement/Model/Content+Generation.swift b/CHDataManagement/Model/Content+Generation.swift index 83ddeb1..aaf4497 100644 --- a/CHDataManagement/Model/Content+Generation.swift +++ b/CHDataManagement/Model/Content+Generation.swift @@ -281,13 +281,15 @@ extension Content { 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)") return } - let pageUrl = page.absoluteUrl(in: language) - let filePath = pageUrl + ".html" guard storage.write(content, to: filePath) else { print("Failed to save page \(page.id)") return diff --git a/CHDataManagement/Model/FileResource.swift b/CHDataManagement/Model/FileResource.swift index d656888..526143f 100644 --- a/CHDataManagement/Model/FileResource.swift +++ b/CHDataManagement/Model/FileResource.swift @@ -246,6 +246,20 @@ final class FileResource: Item, LocalizedItem { .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 func removeFileFromOutputFolder() { diff --git a/CHDataManagement/Model/Settings/GeneralSettings.swift b/CHDataManagement/Model/Settings/GeneralSettings.swift new file mode 100644 index 0000000..21ed926 --- /dev/null +++ b/CHDataManagement/Model/Settings/GeneralSettings.swift @@ -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 + } +} diff --git a/CHDataManagement/Model/Settings/LocalizedPostSettings.swift b/CHDataManagement/Model/Settings/LocalizedPostSettings.swift index 71bbf2e..73c8e6d 100644 --- a/CHDataManagement/Model/Settings/LocalizedPostSettings.swift +++ b/CHDataManagement/Model/Settings/LocalizedPostSettings.swift @@ -2,13 +2,7 @@ import Foundation final class LocalizedPostSettings: ObservableObject { - /// The page title for the post feed - @Published - var title: String - - /// The page description for the post feed - @Published - var description: String + var linkPreview: LinkPreview /// The path to the feed in the final website, appended with the page number @Published @@ -22,11 +16,10 @@ final class LocalizedPostSettings: ObservableObject { @Published var defaultPageLinkText: String - init(title: String, description: String, feedUrlPrefix: String, defaultPageLinkText: String) { - self.title = title - self.description = description + init(feedUrlPrefix: String, defaultPageLinkText: String, linkPreview: LinkPreview) { self.feedUrlPrefix = feedUrlPrefix self.defaultPageLinkText = defaultPageLinkText + self.linkPreview = linkPreview } } @@ -34,26 +27,23 @@ final class LocalizedPostSettings: ObservableObject { extension LocalizedPostSettings { - convenience init(data: Data) { + convenience init(context: LoadingContext, data: Data) { self.init( - title: data.feedTitle, - description: data.feedDescription, feedUrlPrefix: data.feedUrlPrefix, - defaultPageLinkText: data.defaultPageLinkText) + defaultPageLinkText: data.defaultPageLinkText, + linkPreview: .init(context: context, data: data.linkPreview)) } var data: Data { .init( - feedTitle: title, - feedDescription: description, feedUrlPrefix: feedUrlPrefix, - defaultPageLinkText: defaultPageLinkText) + defaultPageLinkText: defaultPageLinkText, + linkPreview: linkPreview.data) } struct Data: Codable { - let feedTitle: String - let feedDescription: String let feedUrlPrefix: String let defaultPageLinkText: String + let linkPreview: LinkPreview.Data } } diff --git a/CHDataManagement/Model/Settings/PostSettings.swift b/CHDataManagement/Model/Settings/PostSettings.swift index 20b3cfb..5680622 100644 --- a/CHDataManagement/Model/Settings/PostSettings.swift +++ b/CHDataManagement/Model/Settings/PostSettings.swift @@ -65,8 +65,8 @@ extension PostSettings { swiperCssFile: data.swiperCssFile.map(context.file), swiperJsFile: data.swiperJsFile.map(context.file), defaultCssFile: data.defaultCssFile.map(context.file), - german: .init(data: data.german), - english: .init(data: data.english)) + german: .init(context: context, data: data.german), + english: .init(context: context, data: data.english)) } var data: PostSettings.Data { diff --git a/CHDataManagement/Model/Settings/Settings.swift b/CHDataManagement/Model/Settings/Settings.swift index d909f2d..b8ec491 100644 --- a/CHDataManagement/Model/Settings/Settings.swift +++ b/CHDataManagement/Model/Settings/Settings.swift @@ -2,6 +2,9 @@ import Foundation final class Settings: ObservableObject { + @Published + var general: GeneralSettings + @Published var paths: PathSettings @@ -18,11 +21,13 @@ final class Settings: ObservableObject { @Published var audioPlayer: AudioPlayerSettings - init(paths: PathSettings, + init(general: GeneralSettings, + paths: PathSettings, navigation: NavigationSettings, posts: PostSettings, pages: PageSettings, audioPlayer: AudioPlayerSettings) { + self.general = general self.paths = paths self.navigation = navigation self.posts = posts @@ -43,6 +48,7 @@ extension Settings { convenience init(context: LoadingContext, data: Settings.Data) { self.init( + general: .init(data: data.general), paths: .init(data: data.paths), navigation: .init(context: context, data: data.navigation), posts: .init(context: context, data: data.posts), @@ -52,6 +58,7 @@ extension Settings { func data(tagOverview: Tag?) -> Data { .init( + general: general.data, paths: paths.data, navigation: navigation.data, posts: posts.data, @@ -61,6 +68,7 @@ extension Settings { } struct Data: Codable { + let general: GeneralSettings.Data let paths: PathSettings.Data let navigation: NavigationSettings.Data let posts: PostSettings.Data @@ -73,6 +81,7 @@ extension Settings { extension Settings { static let `default`: Settings = .init( + general: .default, paths: .default, navigation: .default, posts: .default, @@ -80,6 +89,14 @@ extension Settings { audioPlayer: .default) } +extension GeneralSettings { + + static let `default`: GeneralSettings = .init( + url: "https://example.com", + linkPreviewImageWidth: 1200, + linkPreviewImageHeight: 630) +} + extension AudioPlayerSettings { static let `default`: AudioPlayerSettings = .init( @@ -100,15 +117,18 @@ extension PostSettings { swiperJsFile: nil, defaultCssFile: nil, german: .init( - title: "Beiträge", - description: "Alle Beiträge", feedUrlPrefix: "blog", - defaultPageLinkText: "Anzeigen"), + defaultPageLinkText: "Anzeigen", + linkPreview: .init( + title: "Beiträge", + description: "Alle Beiträge")), english: .init( - title: "Blog posts", - description: "All blog posts", feedUrlPrefix: "blog", - defaultPageLinkText: "View")) + defaultPageLinkText: "View", + linkPreview: .init( + title: "Blog posts", + description: "All blog posts")) + ) } } diff --git a/CHDataManagement/Page Elements/PageHeader.swift b/CHDataManagement/Page Elements/PageHeader.swift index 8ccd7bb..e973add 100644 --- a/CHDataManagement/Page Elements/PageHeader.swift +++ b/CHDataManagement/Page Elements/PageHeader.swift @@ -15,8 +15,10 @@ struct PageHeader: HtmlProducer { init( language: ContentLanguage, - title: String, + title: String?, description: String?, + image: String?, + pageUrl: String, iconUrl: String, languageButton: NavigationBar.Link, links: [NavigationBar.Link], @@ -29,10 +31,18 @@ struct PageHeader: HtmlProducer { self.icons = icons var headers = headers - headers.insert(.title(title)) + if let title { + headers.insert(.title(title)) + headers.insert(.ogTitle(title)) + } if let description { headers.insert(.description(description)) + headers.insert(.ogDescription(description)) } + if let image { + headers.insert(.ogImage(image)) + } + headers.insert(.ogUrl(pageUrl)) self.headers = headers.sorted() } diff --git a/CHDataManagement/Views/Settings/GeneralSettingsDetailView.swift b/CHDataManagement/Views/Settings/GeneralSettingsDetailView.swift new file mode 100644 index 0000000..c53bd60 --- /dev/null +++ b/CHDataManagement/Views/Settings/GeneralSettingsDetailView.swift @@ -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() + } + } +} diff --git a/CHDataManagement/Views/Settings/GenerationDetailView.swift b/CHDataManagement/Views/Settings/GenerationDetailView.swift index 54206ea..8fa8a3d 100644 --- a/CHDataManagement/Views/Settings/GenerationDetailView.swift +++ b/CHDataManagement/Views/Settings/GenerationDetailView.swift @@ -9,6 +9,8 @@ struct GenerationDetailView: View { var body: some View { switch section { + case .general: + GeneralSettingsDetailView(generalSettings: content.settings.general) case .folders: PathSettingsView() case .navigationBar: diff --git a/CHDataManagement/Views/Settings/LocalizedPostFeedSettingsView.swift b/CHDataManagement/Views/Settings/LocalizedPostFeedSettingsView.swift index 1cfd77b..af8c737 100644 --- a/CHDataManagement/Views/Settings/LocalizedPostFeedSettingsView.swift +++ b/CHDataManagement/Views/Settings/LocalizedPostFeedSettingsView.swift @@ -5,6 +5,8 @@ struct LocalizedPostFeedSettingsView: View { @ObservedObject var settings: LocalizedPostSettings + let transferImage: (language: ContentLanguage, image: FileResource)? + var body: some View { VStack(alignment: .leading) { StringPropertyView( @@ -12,25 +14,19 @@ struct LocalizedPostFeedSettingsView: View { text: $settings.defaultPageLinkText, 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( title: "URL prefix", text: $settings.feedUrlPrefix, footer: "The prefix to generate the urls for all post feed pages.") - TextFieldPropertyView( - title: "Description", - text: $settings.description, - footer: "The description of all post feed pages.") + LinkPreviewDetailView(linkPreview: settings.linkPreview, fallbackTitle: nil, transferImage: transferImage) } } } #Preview { - LocalizedPostFeedSettingsView(settings: PostSettings.default.english) - .padding() + LocalizedPostFeedSettingsView( + settings: PostSettings.default.english, + transferImage: nil) + .padding() } diff --git a/CHDataManagement/Views/Settings/PostFeedSettingsView.swift b/CHDataManagement/Views/Settings/PostFeedSettingsView.swift index 112d5bd..ed9c7e4 100644 --- a/CHDataManagement/Views/Settings/PostFeedSettingsView.swift +++ b/CHDataManagement/Views/Settings/PostFeedSettingsView.swift @@ -8,6 +8,12 @@ struct PostFeedSettingsView: View { @ObservedObject 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 { ScrollView { VStack(alignment: .leading) { @@ -40,7 +46,8 @@ struct PostFeedSettingsView: View { selectedFile: $postSettings.swiperJsFile) LocalizedPostFeedSettingsView( - settings: postSettings.localized(in: language)) + settings: postSettings.localized(in: language), + transferImage: transferImage) .id(language) } .padding() diff --git a/CHDataManagement/Views/Settings/SettingsSection.swift b/CHDataManagement/Views/Settings/SettingsSection.swift index 4198ef7..f77f583 100644 --- a/CHDataManagement/Views/Settings/SettingsSection.swift +++ b/CHDataManagement/Views/Settings/SettingsSection.swift @@ -2,6 +2,8 @@ import SFSafeSymbols enum SettingsSection: String { + case general = "General" + case folders = "Folders" case navigationBar = "Navigation Bar" @@ -20,6 +22,7 @@ extension SettingsSection { var icon: SFSymbol { switch self { + case .general: return .noteText case .folders: return .folder case .navigationBar: return .menubarArrowUpRectangle case .postFeed: return .rectangleGrid1x2