From 0db6e411c3a60a6924a1f28031cf9ff34f8f0875 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Thu, 9 Jan 2025 13:27:38 +0100 Subject: [PATCH] Improve page indicators, adding items --- CHDataManagement.xcodeproj/project.pbxproj | 32 +++--- .../Model/Content+Validation.swift | 2 + CHDataManagement/Model/ContentLanguage.swift | 7 ++ .../Model/Item/TagOverviewPage.swift | 108 ------------------ .../Model/Loading/ModelLoader.swift | 4 +- CHDataManagement/Model/LocalizedPage.swift | 3 + CHDataManagement/Model/Page.swift | 22 +++- CHDataManagement/Model/Post.swift | 1 + CHDataManagement/Storage/Storage.swift | 2 +- .../Views/Files/FileToAddView.swift | 29 ++++- .../Views/Generic/DraftIndicator.swift | 15 --- .../Views/Generic/TextIndicator.swift | 33 ++++++ .../Views/Pages/AddPageView.swift | 16 ++- .../Pages/LocalizedPageContentView.swift | 25 +--- .../Views/Pages/PageContentView.swift | 4 +- .../Views/Pages/PageListView.swift | 44 +++++-- .../Views/Posts/AddPostView.swift | 12 +- .../Views/Posts/PostListView.swift | 2 +- .../Views/Posts/TagSelectionView.swift | 3 - .../Content/Pages/PageIssueView.swift | 2 +- .../{Content => }/GenerationContentView.swift | 0 CHDataManagement/Views/Tags/AddTagView.swift | 59 ++++++++-- CHDataManagement/Views/Tags/TagListView.swift | 19 ++- 23 files changed, 238 insertions(+), 206 deletions(-) delete mode 100644 CHDataManagement/Model/Item/TagOverviewPage.swift delete mode 100644 CHDataManagement/Views/Generic/DraftIndicator.swift create mode 100644 CHDataManagement/Views/Generic/TextIndicator.swift rename CHDataManagement/Views/Settings/{Content => }/GenerationContentView.swift (100%) diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index d7037ca..357cfa7 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ E21850372CFCA55F0090B18B /* LocalizedPostSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850362CFCA5580090B18B /* LocalizedPostSettings.swift */; }; E218503D2CFCFD910090B18B /* LocalizedPostFeedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */; }; E22990152D0E2B7F009F8D77 /* ItemSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990142D0E2B74009F8D77 /* ItemSelectionView.swift */; }; - E22990172D0E330F009F8D77 /* TagOverviewPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990162D0E32F5009F8D77 /* TagOverviewPage.swift */; }; E22990192D0E3546009F8D77 /* ItemReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990182D0E3546009F8D77 /* ItemReference.swift */; }; E229901E2D0E4364009F8D77 /* LocalizedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E229901D2D0E4362009F8D77 /* LocalizedItem.swift */; }; E22990202D0ECBE5009F8D77 /* TagOverviewDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E229901F2D0ECBD4009F8D77 /* TagOverviewDetailView.swift */; }; @@ -238,7 +237,7 @@ E2FE0F682D2D2CF6002963B7 /* LocalizedPageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F672D2D2CF0002963B7 /* LocalizedPageSettings.swift */; }; E2FE0F6C2D2D335E002963B7 /* LocalizedPageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */; }; E2FE0F6E2D2D3689002963B7 /* LocalizedAudioPlayerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F6D2D2D3685002963B7 /* LocalizedAudioPlayerSettings.swift */; }; - E2FE0F702D2D5235002963B7 /* DraftIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F6F2D2D5231002963B7 /* DraftIndicator.swift */; }; + E2FE0F702D2D5235002963B7 /* TextIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F6F2D2D5231002963B7 /* TextIndicator.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -254,7 +253,6 @@ E21850362CFCA5580090B18B /* LocalizedPostSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPostSettings.swift; sourceTree = ""; }; E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPostFeedSettingsView.swift; sourceTree = ""; }; E22990142D0E2B74009F8D77 /* ItemSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemSelectionView.swift; sourceTree = ""; }; - E22990162D0E32F5009F8D77 /* TagOverviewPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagOverviewPage.swift; sourceTree = ""; }; E22990182D0E3546009F8D77 /* ItemReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemReference.swift; sourceTree = ""; }; E229901D2D0E4362009F8D77 /* LocalizedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedItem.swift; sourceTree = ""; }; E229901F2D0ECBD4009F8D77 /* TagOverviewDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagOverviewDetailView.swift; sourceTree = ""; }; @@ -468,7 +466,7 @@ E2FE0F672D2D2CF0002963B7 /* LocalizedPageSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageSettings.swift; sourceTree = ""; }; E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageSettingsView.swift; sourceTree = ""; }; E2FE0F6D2D2D3685002963B7 /* LocalizedAudioPlayerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedAudioPlayerSettings.swift; sourceTree = ""; }; - E2FE0F6F2D2D5231002963B7 /* DraftIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftIndicator.swift; sourceTree = ""; }; + E2FE0F6F2D2D5231002963B7 /* TextIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextIndicator.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -499,7 +497,6 @@ E229901D2D0E4362009F8D77 /* LocalizedItem.swift */, E29D31A22D0CC98B0051B7F4 /* Item.swift */, E22990182D0E3546009F8D77 /* ItemReference.swift */, - E22990162D0E32F5009F8D77 /* TagOverviewPage.swift */, ); path = Item; sourceTree = ""; @@ -576,7 +573,6 @@ isa = PBXGroup; children = ( E29D31992D0C451B0051B7F4 /* Pages */, - E25DA5702D01015400AEF16D /* GenerationContentView.swift */, E29D318D2D0B2E640051B7F4 /* PageSettingsContentView.swift */, ); path = Content; @@ -634,18 +630,19 @@ E2A21C342CB9A3CA0060935B /* Settings */ = { isa = PBXGroup; children = ( - E29D318C2D0B2E5E0051B7F4 /* Content */, - E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */, - E29D316E2D0822720051B7F4 /* SettingsListView.swift */, - E29D31702D08234D0051B7F4 /* GenerationDetailView.swift */, - E25DA5942D023BCC00AEF16D /* PageSettingsDetailView.swift */, - E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */, E25DA5442D00952D00AEF16D /* SettingsSection.swift */, - E2A21C352CB9A3D70060935B /* PathSettingsView.swift */, - E25DA56C2D00EBC900AEF16D /* NavigationBarSettingsView.swift */, + E29D316E2D0822720051B7F4 /* SettingsListView.swift */, + E25DA5702D01015400AEF16D /* GenerationContentView.swift */, + E29D31702D08234D0051B7F4 /* GenerationDetailView.swift */, + E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */, + E29D318C2D0B2E5E0051B7F4 /* Content */, E2FE0F032D2671FC002963B7 /* LocalizedNavigationBarSettingsView.swift */, - E25DA56E2D00F99900AEF16D /* PostFeedSettingsView.swift */, + E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */, E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */, + E25DA56C2D00EBC900AEF16D /* NavigationBarSettingsView.swift */, + E25DA5942D023BCC00AEF16D /* PageSettingsDetailView.swift */, + E2A21C352CB9A3D70060935B /* PathSettingsView.swift */, + E25DA56E2D00F99900AEF16D /* PostFeedSettingsView.swift */, E229901F2D0ECBD4009F8D77 /* TagOverviewDetailView.swift */, ); path = Settings; @@ -654,7 +651,7 @@ E2A21C372CB9A4F10060935B /* Generic */ = { isa = PBXGroup; children = ( - E2FE0F6F2D2D5231002963B7 /* DraftIndicator.swift */, + E2FE0F6F2D2D5231002963B7 /* TextIndicator.swift */, E2FE0F232D2A8C1A002963B7 /* TagDisplayView.swift */, E229902F2D0F75CF009F8D77 /* BoolPropertyView.swift */, E22990312D0F7678009F8D77 /* DatePropertyView.swift */, @@ -1117,7 +1114,7 @@ E29D313D2D047C1B0051B7F4 /* LocalizedPageContentView.swift in Sources */, E2FE0F242D2A8C21002963B7 /* TagDisplayView.swift in Sources */, E2A37D2D2CED2EF10000979F /* OptionalTextField.swift in Sources */, - E2FE0F702D2D5235002963B7 /* DraftIndicator.swift in Sources */, + E2FE0F702D2D5235002963B7 /* TextIndicator.swift in Sources */, E2A37D2B2CED2CC30000979F /* TagDetailView.swift in Sources */, E2A21C282CB29B2A0060935B /* FeedEntryData.swift in Sources */, E29D31942D0B7D280051B7F4 /* SimpleImage.swift in Sources */, @@ -1181,7 +1178,6 @@ E25DA5752D018B6100AEF16D /* FileDetailView.swift in Sources */, E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */, E29D31A12D0C75CA0051B7F4 /* Content+Validation.swift in Sources */, - E22990172D0E330F009F8D77 /* TagOverviewPage.swift in Sources */, E29D316D2D07A5050051B7F4 /* PageGenerationResults.swift in Sources */, E229903E2D0F8F02009F8D77 /* StringPropertyView.swift in Sources */, E25DA5192CFF035900AEF16D /* Array+Split.swift in Sources */, diff --git a/CHDataManagement/Model/Content+Validation.swift b/CHDataManagement/Model/Content+Validation.swift index e5d51da..2835d7c 100644 --- a/CHDataManagement/Model/Content+Validation.swift +++ b/CHDataManagement/Model/Content+Validation.swift @@ -23,10 +23,12 @@ extension Content { } func isValidIdForTagOrPageOrPost(_ id: String) -> Bool { + !id.isEmpty && id.rangeOfCharacter(from: Content.disallowedCharactersInIds) == nil } func isValidIdForFile(_ id: String) -> Bool { + !id.isEmpty && id.rangeOfCharacter(from: Content.disallowedCharactersInFileIds) == nil } diff --git a/CHDataManagement/Model/ContentLanguage.swift b/CHDataManagement/Model/ContentLanguage.swift index e0e35f4..8478bff 100644 --- a/CHDataManagement/Model/ContentLanguage.swift +++ b/CHDataManagement/Model/ContentLanguage.swift @@ -48,4 +48,11 @@ extension ContentLanguage { case .german: return "German" } } + + var shortText: String { + switch self { + case .english: "EN" + case .german: "DE" + } + } } diff --git a/CHDataManagement/Model/Item/TagOverviewPage.swift b/CHDataManagement/Model/Item/TagOverviewPage.swift deleted file mode 100644 index 1975d42..0000000 --- a/CHDataManagement/Model/Item/TagOverviewPage.swift +++ /dev/null @@ -1,108 +0,0 @@ -import Foundation -/* -final class TagOverviewPage: Item { - - static let id = "all-tags" - - @Published - var german: LocalizedTagOverviewPage - - @Published - var english: LocalizedTagOverviewPage - - - init(content: Content, german: LocalizedTagOverviewPage, english: LocalizedTagOverviewPage) { - self.german = german - self.english = english - super.init(content: content, id: TagOverviewPage.id) - } - - override var itemType: ItemType { - .tagOverview - } - - override func title(in language: ContentLanguage) -> String { - localized(in: language).title - } - - override func absoluteUrl(in language: ContentLanguage) -> String { - makeCleanAbsolutePath(internalPath(for: language)) - } - - func filePathRelativeToOutputFolder(for language: ContentLanguage) -> String { - makeCleanRelativePath(internalPath(for: language)) - } - - private func internalPath(for language: ContentLanguage) -> String { - content.settings.paths.tagsOutputFolderPath + "/" + localized(in: language).urlComponent - } - - func contains(urlComponent: String) -> Bool { - english.urlComponent == urlComponent || german.urlComponent == urlComponent - } - - var file: TagOverviewFile { - .init(german: german.file, - english: english.file) - } -} - -extension TagOverviewPage: LocalizedItem { - -} - -final class LocalizedTagOverviewPage: ObservableObject { - - unowned let content: Content - - @Published - var title: String - - /** - The string to use when creating the url for the page. - */ - @Published - var urlComponent: String - - @Published - var linkPreviewImage: FileResource? - - @Published - var linkPreviewTitle: String? - - @Published - var linkPreviewDescription: String? - - init(content: Content, title: String, urlString: String, linkPreviewImage: FileResource? = nil, linkPreviewTitle: String? = nil, linkPreviewDescription: String? = nil) { - self.content = content - self.title = title - self.urlComponent = urlString - self.linkPreviewImage = linkPreviewImage - self.linkPreviewTitle = linkPreviewTitle - self.linkPreviewDescription = linkPreviewDescription - } - - init(content: Content, file: LocalizedTagOverviewFile, image: FileResource?) { - self.content = content - self.title = file.title - self.urlComponent = file.url - self.linkPreviewImage = image - self.linkPreviewTitle = file.linkPreviewTitle - self.linkPreviewDescription = file.linkPreviewDescription - } - - var file: LocalizedTagOverviewFile { - .init(url: urlComponent, - title: title, - linkPreviewImage: linkPreviewImage?.id, - linkPreviewTitle: linkPreviewTitle, - linkPreviewDescription: linkPreviewDescription) - } - - func isValid(urlComponent: String) -> Bool { - !urlComponent.isEmpty && - content.isValidIdForTagOrPageOrPost(urlComponent) && - !content.containsTag(withUrlComponent: urlComponent) - } -} -*/ diff --git a/CHDataManagement/Model/Loading/ModelLoader.swift b/CHDataManagement/Model/Loading/ModelLoader.swift index 7345114..9df2a85 100644 --- a/CHDataManagement/Model/Loading/ModelLoader.swift +++ b/CHDataManagement/Model/Loading/ModelLoader.swift @@ -82,7 +82,9 @@ final class ModelLoader { if pages.isEmpty { print("No pages loaded") } pages.forEach { pageId, data in - context.pages[pageId] = Page(context: context, id: pageId, data: data) + let page = Page(context: context, id: pageId, data: data) + page.updateContentExistence() + context.pages[pageId] = page } } diff --git a/CHDataManagement/Model/LocalizedPage.swift b/CHDataManagement/Model/LocalizedPage.swift index 1e60161..5027b53 100644 --- a/CHDataManagement/Model/LocalizedPage.swift +++ b/CHDataManagement/Model/LocalizedPage.swift @@ -40,6 +40,9 @@ final class LocalizedPage: ObservableObject { @Published var hideTitle: Bool + @Published + var hasContent: Bool = false + init(content: Content, urlString: String, title: String, diff --git a/CHDataManagement/Model/Page.swift b/CHDataManagement/Model/Page.swift index 955e540..03bb7c8 100644 --- a/CHDataManagement/Model/Page.swift +++ b/CHDataManagement/Model/Page.swift @@ -153,8 +153,26 @@ final class Page: Item, DateItem, LocalizedItem { content.storage.pageContent(for: id, language: language) } - func hasContent(in language: ContentLanguage) -> Bool { - content.storage.hasPageContent(for: id, language: language) + func save(pageContent: String, in language: ContentLanguage) -> Bool { + guard content.storage.save(pageContent: pageContent, for: id, in: language) else { + return false + } + localized(in: language).hasContent = true + return true + } + + /** + Update the `hasContent` property of all localized pages. + */ + func updateContentExistence() { + for language in ContentLanguage.allCases { + localized(in: language).hasContent = content.storage.hasPageContent(for: id, language: language) + } + } + + /// All languages for which the page has no content + var missingContentLanguages: [ContentLanguage] { + ContentLanguage.allCases.filter { !localized(in: $0).hasContent } } func remove(_ file: FileResource) { diff --git a/CHDataManagement/Model/Post.swift b/CHDataManagement/Model/Post.swift index 5bc407b..452a1e6 100644 --- a/CHDataManagement/Model/Post.swift +++ b/CHDataManagement/Model/Post.swift @@ -81,6 +81,7 @@ final class Post: Item, DateItem, LocalizedItem { func toggle(_ tag: Tag) { if let linkedPage { linkedPage.toggle(tag) + didChange() // Otherwise tags will not be updated return } guard let index = tags.firstIndex(of: tag) else { diff --git a/CHDataManagement/Storage/Storage.swift b/CHDataManagement/Storage/Storage.swift index 92dd7ad..045c506 100644 --- a/CHDataManagement/Storage/Storage.swift +++ b/CHDataManagement/Storage/Storage.swift @@ -67,7 +67,7 @@ final class Storage: ObservableObject { id + ".json" } - func save(pageContent: String, for pageId: String, language: ContentLanguage) -> Bool { + func save(pageContent: String, for pageId: String, in language: ContentLanguage) -> Bool { guard let contentScope else { return false } let path = pageContentPath(page: pageId, language: language) return contentScope.write(pageContent, to: path) diff --git a/CHDataManagement/Views/Files/FileToAddView.swift b/CHDataManagement/Views/Files/FileToAddView.swift index 6e75f4f..43d8f7d 100644 --- a/CHDataManagement/Views/Files/FileToAddView.swift +++ b/CHDataManagement/Views/Files/FileToAddView.swift @@ -8,15 +8,38 @@ struct FileToAddView: View { let delete: (FileToAdd) -> Void + var symbol: SFSymbol { + if file.idAlreadyExists { + return .docOnDoc + } + if file.isSelected { + return .checkmarkCircleFill + } + return .circle + } + + var color: Color { + if file.idAlreadyExists { + return .red + } + if file.isSelected { + return .blue + } + return .gray + } + var body: some View { VStack(alignment: .leading) { HStack { - Image(systemSymbol: file.isSelected ? .checkmarkCircleFill : .circle) + Image(systemSymbol: symbol) .resizable() + .aspectRatio(contentMode: .fit) .frame(width: 20, height: 20) - .foregroundStyle(.blue) + .foregroundStyle(color) .onTapGesture { - file.isSelected.toggle() + if !file.idAlreadyExists { + file.isSelected.toggle() + } } Image(systemSymbol: .trashCircleFill) .resizable() diff --git a/CHDataManagement/Views/Generic/DraftIndicator.swift b/CHDataManagement/Views/Generic/DraftIndicator.swift deleted file mode 100644 index 30ca1c4..0000000 --- a/CHDataManagement/Views/Generic/DraftIndicator.swift +++ /dev/null @@ -1,15 +0,0 @@ -import SwiftUI - -struct DraftIndicator: View { - - var body: some View { - Text("Draft") - .foregroundStyle(.white) - .padding(.vertical, 2) - .padding(.horizontal, 5) - .background( - RoundedRectangle(cornerRadius: 5, style: .circular) - .foregroundStyle(Color.gray) - ) - } -} diff --git a/CHDataManagement/Views/Generic/TextIndicator.swift b/CHDataManagement/Views/Generic/TextIndicator.swift new file mode 100644 index 0000000..1190c6b --- /dev/null +++ b/CHDataManagement/Views/Generic/TextIndicator.swift @@ -0,0 +1,33 @@ +import SwiftUI + +struct TextIndicator: View { + + let text: LocalizedStringKey + + let color: Color + + let background: Color + + init(text: String, color: Color = .white, background: Color = Color.gray) { + self.text = .init(stringLiteral: text) + self.background = background + self.color = color + } + + init(text: LocalizedStringKey, color: Color = .white, background: Color = Color.gray) { + self.text = text + self.background = background + self.color = color + } + + var body: some View { + Text(text) + .foregroundStyle(color) + .padding(.vertical, 2) + .padding(.horizontal, 5) + .background( + RoundedRectangle(cornerRadius: 5, style: .circular) + .foregroundStyle(background) + ) + } +} diff --git a/CHDataManagement/Views/Pages/AddPageView.swift b/CHDataManagement/Views/Pages/AddPageView.swift index a291c81..d285ccd 100644 --- a/CHDataManagement/Views/Pages/AddPageView.swift +++ b/CHDataManagement/Views/Pages/AddPageView.swift @@ -17,18 +17,16 @@ struct AddPageView: View { @State private var newPageId = "" - private let allowedCharactersInPageId = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")).inverted - init(selected: Binding) { self._selectedPage = selected } private var idExists: Bool { - content.pages.contains { $0.id == newPageId } + !content.isNewIdForPage(newPageId) } - private var containsInvalidCharacters: Bool { - newPageId.rangeOfCharacter(from: allowedCharactersInPageId) != nil + private var isInvalidId: Bool { + !content.isValidIdForTagOrPageOrPost(newPageId) } var body: some View { @@ -42,12 +40,12 @@ struct AddPageView: View { if newPageId.isEmpty { Text("Enter the id of the new page to create") .foregroundStyle(.secondary) + } else if isInvalidId { + Text("The id contains invalid characters") + .foregroundStyle(Color.red) } else if idExists { Text("A page with the same id already exists") .foregroundStyle(Color.red) - } else if containsInvalidCharacters { - Text("The id contains invalid characters") - .foregroundStyle(Color.red) } else { Text("Create a new page with the id") .foregroundStyle(.secondary) @@ -59,7 +57,7 @@ struct AddPageView: View { Button(action: addNewPage) { Text("Create") } - .disabled(newPageId.isEmpty || containsInvalidCharacters || idExists) + .disabled(isInvalidId || idExists) } } .padding() diff --git a/CHDataManagement/Views/Pages/LocalizedPageContentView.swift b/CHDataManagement/Views/Pages/LocalizedPageContentView.swift index 79696c2..afb76b3 100644 --- a/CHDataManagement/Views/Pages/LocalizedPageContentView.swift +++ b/CHDataManagement/Views/Pages/LocalizedPageContentView.swift @@ -7,13 +7,11 @@ struct LocalizedPageContentView: View { @EnvironmentObject var content: Content - let pageId: String + @ObservedObject + var page: Page let language: ContentLanguage - @ObservedObject - var page: LocalizedPage - @State private var pageContent: String = "" @@ -26,18 +24,8 @@ struct LocalizedPageContentView: View { @State private var didChangeContent = false - init(pageId: String, page: LocalizedPage, language: ContentLanguage) { - self.pageId = pageId - self.page = page - self.language = language - } - var body: some View { VStack(alignment: .leading) { - TextField("", text: $page.title) - .font(.title) - .textFieldStyle(.plain) - HStack(alignment: .firstTextBaseline) { Button(action: loadContent) { Text("Load") @@ -70,14 +58,14 @@ struct LocalizedPageContentView: View { private func loadContent() { let language = language - guard page.content.storage.hasPageContent(for: pageId, language: language) else { + guard page.localized(in: language).hasContent else { pageContent = "New file" DispatchQueue.main.async { didChangeContent = false } return } - guard let content = page.content.storage.pageContent(for: pageId, language: language) else { + guard let content = page.pageContent(in: language) else { print("Failed to load page content") pageContent = "Failed to load" DispatchQueue.main.async { @@ -108,7 +96,7 @@ struct LocalizedPageContentView: View { guard didChangeContent else { return } - guard page.content.storage.save(pageContent: pageContent, for: pageId, language: language) else { + guard page.save(pageContent: pageContent, in: language) else { print("Failed to save content") return } @@ -120,9 +108,6 @@ struct LocalizedPageContentView: View { guard content != pageContentUsedForGeneration else { return } - guard let page = self.content.page(pageId) else { - return - } guard !self.content.isGeneratingWebsite else { return } diff --git a/CHDataManagement/Views/Pages/PageContentView.swift b/CHDataManagement/Views/Pages/PageContentView.swift index 069827f..1c47fae 100644 --- a/CHDataManagement/Views/Pages/PageContentView.swift +++ b/CHDataManagement/Views/Pages/PageContentView.swift @@ -41,8 +41,10 @@ struct PageContentView: View { }.padding() } else { VStack(alignment: .leading) { + PageTitleView(page: page.localized(in: language)) + .id(page.id + language.rawValue) TagDisplayView(tags: $page.tags) - LocalizedPageContentView(pageId: page.id, page: page.localized(in: language), language: language) + LocalizedPageContentView(page: page, language: language) .id(page.id + language.rawValue) } .padding() diff --git a/CHDataManagement/Views/Pages/PageListView.swift b/CHDataManagement/Views/Pages/PageListView.swift index 074afa1..8b2602f 100644 --- a/CHDataManagement/Views/Pages/PageListView.swift +++ b/CHDataManagement/Views/Pages/PageListView.swift @@ -1,5 +1,40 @@ import SwiftUI +private struct PageListItem: View { + + @Environment(\.language) + private var language + + @ObservedObject + var page: Page + + var body: some View { + HStack { + LocalizedPageListItem(page: page.localized(in: language)) + Spacer() + if page.isExternalUrl { + TextIndicator(text: "External") + } else if page.isDraft { + TextIndicator(text: "Draft", background: .yellow) + } else { + ForEach(page.missingContentLanguages, id: \.self) { language in + TextIndicator(text: language.shortText, background: Color.red) + } + } + } + } +} + +private struct LocalizedPageListItem: View { + + @ObservedObject + var page: LocalizedPage + + var body: some View { + Text(page.title) + } +} + struct PageListView: View { @Environment(\.language) @@ -31,13 +66,8 @@ struct PageListView: View { .textFieldStyle(.roundedBorder) .padding(.horizontal, 8) List(filteredPages, selection: $selectedPage) { page in - HStack { - Text(page.title(in: language)) - Spacer() - if page.isDraft { - DraftIndicator() - } - }.tag(page) + PageListItem(page: page) + .tag(page) } } .onAppear { diff --git a/CHDataManagement/Views/Posts/AddPostView.swift b/CHDataManagement/Views/Posts/AddPostView.swift index 1a243c5..6915189 100644 --- a/CHDataManagement/Views/Posts/AddPostView.swift +++ b/CHDataManagement/Views/Posts/AddPostView.swift @@ -17,18 +17,16 @@ struct AddPostView: View { @State private var newPostId = "" - private let allowedCharactersInPostId = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")).inverted - init(selected: Binding) { self._selectedPost = selected } private var idExists: Bool { - content.posts.contains { $0.id == newPostId } + !content.isNewIdForPost(newPostId) } - private var containsInvalidCharacters: Bool { - newPostId.rangeOfCharacter(from: allowedCharactersInPostId) != nil + private var isInvalidId: Bool { + !content.isValidIdForTagOrPageOrPost(newPostId) } var body: some View { @@ -45,7 +43,7 @@ struct AddPostView: View { } else if idExists { Text("A post with the same id already exists") .foregroundStyle(Color.red) - } else if containsInvalidCharacters { + } else if isInvalidId { Text("The id contains invalid characters") .foregroundStyle(Color.red) } else { @@ -59,7 +57,7 @@ struct AddPostView: View { Button(action: addNewPost) { Text("Create") } - .disabled(newPostId.isEmpty || containsInvalidCharacters || idExists) + .disabled(isInvalidId || idExists) } } .padding() diff --git a/CHDataManagement/Views/Posts/PostListView.swift b/CHDataManagement/Views/Posts/PostListView.swift index ecea9ef..720840c 100644 --- a/CHDataManagement/Views/Posts/PostListView.swift +++ b/CHDataManagement/Views/Posts/PostListView.swift @@ -35,7 +35,7 @@ struct PostListView: View { Text(post.title(in: language)) Spacer() if post.isDraft { - DraftIndicator() + TextIndicator(text: "Draft") } }.tag(post) } diff --git a/CHDataManagement/Views/Posts/TagSelectionView.swift b/CHDataManagement/Views/Posts/TagSelectionView.swift index 5ab3e83..4023912 100644 --- a/CHDataManagement/Views/Posts/TagSelectionView.swift +++ b/CHDataManagement/Views/Posts/TagSelectionView.swift @@ -69,9 +69,6 @@ struct TagSelectionView: View { return } selected.remove(at: index) - - let insertIndex = tags.firstIndex(where: { $0 > tag }) ?? tags.endIndex - tags.insert(tag, at: insertIndex) } private func select(tag: Tag) { diff --git a/CHDataManagement/Views/Settings/Content/Pages/PageIssueView.swift b/CHDataManagement/Views/Settings/Content/Pages/PageIssueView.swift index 41750de..be1e445 100644 --- a/CHDataManagement/Views/Settings/Content/Pages/PageIssueView.swift +++ b/CHDataManagement/Views/Settings/Content/Pages/PageIssueView.swift @@ -294,7 +294,7 @@ struct PageIssueView: View { } let modified = pageContent.replacingOccurrences(of: oldString, with: newString) - guard content.storage.save(pageContent: modified, for: page.id, language: language) else { + guard content.storage.save(pageContent: modified, for: page.id, in: language) else { print("Replaced \(oldString) with \(newString) in page \(page.id) (\(language))") return } diff --git a/CHDataManagement/Views/Settings/Content/GenerationContentView.swift b/CHDataManagement/Views/Settings/GenerationContentView.swift similarity index 100% rename from CHDataManagement/Views/Settings/Content/GenerationContentView.swift rename to CHDataManagement/Views/Settings/GenerationContentView.swift diff --git a/CHDataManagement/Views/Tags/AddTagView.swift b/CHDataManagement/Views/Tags/AddTagView.swift index faf1aae..0140bd9 100644 --- a/CHDataManagement/Views/Tags/AddTagView.swift +++ b/CHDataManagement/Views/Tags/AddTagView.swift @@ -14,24 +14,69 @@ struct AddTagView: View { @Binding var selectedTag: Tag? + @State + private var newId = "" + init(selected: Binding) { self._selectedTag = selected } + private var idExists: Bool { + !content.isNewIdForTag(newId) + } + + private var isInvalidId: Bool { + !content.isValidIdForTagOrPageOrPost(newId) + } + var body: some View { - Text("Creating tag...") - .onAppear(perform: addNewTag) + VStack { + Text("New tag") + .font(.headline) + + TextField("", text: $newId) + .textFieldStyle(.roundedBorder) + .frame(maxWidth: 350) + if newId.isEmpty { + Text("Enter the id of the new tag to create") + .foregroundStyle(.secondary) + } else if isInvalidId { + Text("The id contains invalid characters") + .foregroundStyle(Color.red) + } else if idExists { + Text("A tag with the same id already exists") + .foregroundStyle(Color.red) + } else { + Text("Create a new tag with the id") + .foregroundStyle(.secondary) + } + HStack { + Button(role: .cancel, action: dismissSheet) { + Text("Cancel") + } + Button(action: addNewTag) { + Text("Create") + } + .disabled(isInvalidId || idExists) + } + } + .padding() } private func addNewTag() { - let newTag = Tag( + let tag = Tag( content: content, - id: "tag", + id: newId, isVisible: true, - german: .init(content: content, urlComponent: "tag", name: "Neuer Tag"), - english: .init(content: content, urlComponent: "tag-en", name: "New Tag")) + german: .init(content: content, urlComponent: newId, name: newId), + english: .init(content: content, urlComponent: "\(newId)-en", name: "\(newId)-en")) // Add to top of the list, and resort when changing the name - content.tags.insert(newTag, at: 0) + content.tags.insert(tag, at: 0) + selectedTag = tag + dismiss() + } + + private func dismissSheet() { dismiss() } } diff --git a/CHDataManagement/Views/Tags/TagListView.swift b/CHDataManagement/Views/Tags/TagListView.swift index b26ff34..032e23e 100644 --- a/CHDataManagement/Views/Tags/TagListView.swift +++ b/CHDataManagement/Views/Tags/TagListView.swift @@ -25,13 +25,18 @@ struct TagListView: View { return content.tags.filter { $0.localized(in: language).name.contains(searchString) } } + private var filteredAndSortedTags: [Tag] { + filteredTags.sorted { $0.title(in: language) } + } + var body: some View { VStack { TextField("", text: $searchString, prompt: Text("Search")) .textFieldStyle(.roundedBorder) .padding(.horizontal, 8) - List(filteredTags, selection: $selectedTag) { tag in - Text(tag.localized(in: language).title).tag(tag) + List(filteredAndSortedTags, selection: $selectedTag) { tag in + TagListItem(tag: tag.localized(in: language)) + .tag(tag) } }.onAppear { if selectedTag == nil { @@ -40,3 +45,13 @@ struct TagListView: View { } } } + +private struct TagListItem: View { + + @ObservedObject + var tag: LocalizedTag + + var body: some View { + Text(tag.title) + } +}