From 1d0eba9d780c8bda2446c345e3cb7d5ab0ad6726 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Wed, 11 Jun 2025 08:19:44 +0200 Subject: [PATCH] Fix id of Items, saving --- CHDataManagement.xcodeproj/project.pbxproj | 4 +- .../Generator/Blocks/GalleryBlock.swift | 2 +- .../Generator/Blocks/PhoneScreensBlock.swift | 4 +- .../Generator/Commands/HtmlCommand.swift | 2 +- .../Generator/ImageGenerator.swift | 8 ++-- CHDataManagement/Generator/ImageVersion.swift | 6 +-- .../Page Generators/PageGenerator.swift | 2 +- .../Post Lists/PostContentGenerator.swift | 2 +- .../Post Lists/PostListPageGenerator.swift | 2 +- .../Generator/Results/GenerationResults.swift | 2 +- .../Results/PageGenerationResults.swift | 4 +- CHDataManagement/Main/MainContentView.swift | 2 +- .../Main/SelectedDetailView.swift | 2 +- CHDataManagement/Mock/File+Mock.swift | 2 +- CHDataManagement/Mock/Page+Mock.swift | 4 +- CHDataManagement/Mock/Post+Mock.swift | 36 +++++++-------- CHDataManagement/Mock/Tag+Mock.swift | 14 +++--- .../Model/Content+Generation.swift | 16 +++---- CHDataManagement/Model/Content+Save.swift | 8 ++-- .../Model/Content+Validation.swift | 8 ++-- CHDataManagement/Model/Content.swift | 14 ++++-- CHDataManagement/Model/DateItem.swift | 4 +- CHDataManagement/Model/FileResource.swift | 44 +++++++++---------- CHDataManagement/Model/Item/Item.swift | 17 ++++--- .../Model/Item/ItemReference.swift | 12 ++--- CHDataManagement/Model/LinkPreview.swift | 2 +- .../Model/Loading/LoadingContext.swift | 2 +- CHDataManagement/Model/LocalizedPost.swift | 2 +- CHDataManagement/Model/Page.swift | 18 ++++---- CHDataManagement/Model/Post.swift | 20 ++++----- .../Model/Settings/AudioPlayerSettings.swift | 4 +- .../Model/Settings/GeneralSettings.swift | 2 +- .../Model/Settings/PageSettings.swift | 14 +++--- .../Model/Settings/PostSettings.swift | 6 +-- CHDataManagement/Model/Tag.swift | 8 ++-- CHDataManagement/Model/TagOverview.swift | 2 +- .../Views/Files/AddFileView.swift | 4 ++ .../Views/Files/FileContentView.swift | 2 +- .../Views/Files/FileDetailView.swift | 21 ++++----- .../Views/Files/FileListView.swift | 6 +-- CHDataManagement/Views/Files/FileToAdd.swift | 2 +- .../Views/Files/MultiFileSelectionView.swift | 6 +-- .../Views/Files/TextFileContentView.swift | 14 +++--- .../Generation/GenerationContentView.swift | 12 ++--- .../Views/Generic/FilePropertyView.swift | 2 +- .../Generic/OptionalImagePropertyView.swift | 2 +- .../Views/Generic/PagePropertyView.swift | 2 +- .../Views/Generic/TagDisplayView.swift | 2 +- .../Views/Generic/TagPickerView.swift | 2 +- .../Views/Generic/TagPropertyView.swift | 2 +- .../Views/Pages/Commands/Insert+Buttons.swift | 6 +-- .../Views/Pages/Commands/Insert+Gallery.swift | 2 +- .../Views/Pages/Commands/Insert+Image.swift | 4 +- .../Views/Pages/Commands/Insert+Link.swift | 6 +-- .../Views/Pages/Commands/Insert+Route.swift | 4 +- .../Views/Pages/Commands/Insert+Video.swift | 8 ++-- .../Views/Pages/PageContentResultsView.swift | 2 +- .../Views/Pages/PageContentView.swift | 6 +-- .../Views/Pages/PageDetailView.swift | 8 ++-- .../Views/Posts/PagePickerView.swift | 2 +- .../Views/Posts/PostDetailView.swift | 6 +-- .../Views/Posts/PostImageView.swift | 6 +-- .../Views/Posts/PostListView.swift | 2 +- .../Views/Tags/TagDetailView.swift | 8 ++-- 64 files changed, 233 insertions(+), 217 deletions(-) diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index 7535ec4..2104fb9 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -1767,7 +1767,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.2; + MARKETING_VERSION = 1.3; PRODUCT_BUNDLE_IDENTIFIER = de.christophhagen.CHDataManagement; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -1806,7 +1806,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.2; + MARKETING_VERSION = 1.3; PRODUCT_BUNDLE_IDENTIFIER = de.christophhagen.CHDataManagement; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; diff --git a/CHDataManagement/Generator/Blocks/GalleryBlock.swift b/CHDataManagement/Generator/Blocks/GalleryBlock.swift index d006a2a..165b20e 100644 --- a/CHDataManagement/Generator/Blocks/GalleryBlock.swift +++ b/CHDataManagement/Generator/Blocks/GalleryBlock.swift @@ -37,7 +37,7 @@ struct GalleryBlock: BlockLineProcessor { $0.imageSet(width: imageWidth, height: imageWidth, language: language) } imageSets.forEach(results.require) - let id = firstImage.id.replacingOccurrences(of: ".", with: "-") + let id = firstImage.identifier.replacingOccurrences(of: ".", with: "-") let gallery = ImageGallery(id: id, images: imageSets, standalone: true) results.require(footer: gallery.standaloneFooter) results.require(headers: .swiperJs, .swiperCss) diff --git a/CHDataManagement/Generator/Blocks/PhoneScreensBlock.swift b/CHDataManagement/Generator/Blocks/PhoneScreensBlock.swift index 0a7408c..28c5b42 100644 --- a/CHDataManagement/Generator/Blocks/PhoneScreensBlock.swift +++ b/CHDataManagement/Generator/Blocks/PhoneScreensBlock.swift @@ -50,7 +50,7 @@ struct PhoneScreensBlock: OrderedKeyBlockProcessor { } if key == .tall { if tall != nil { - print("Another tall image: \(file.id)") + print("Another tall image: \(file.identifier)") invalid(markdown) return "" } @@ -69,7 +69,7 @@ struct PhoneScreensBlock: OrderedKeyBlockProcessor { } // key == .wide if wide != nil { - print("Another wide image: \(file.id)") + print("Another wide image: \(file.identifier)") invalid(markdown) return "" } diff --git a/CHDataManagement/Generator/Commands/HtmlCommand.swift b/CHDataManagement/Generator/Commands/HtmlCommand.swift index f979e10..8de5ed2 100644 --- a/CHDataManagement/Generator/Commands/HtmlCommand.swift +++ b/CHDataManagement/Generator/Commands/HtmlCommand.swift @@ -185,7 +185,7 @@ struct HtmlCommand: CommandProcessor { results.missing(file: fileId, source: "HTML: \(source)") return } - results.warning("Could not determine image version for file '\(file.id)' for \(source)") + results.warning("Could not determine image version for file '\(file.identifier)' for \(source)") } private func findFileWith(relativePath: String, type: FileType, source: String) { diff --git a/CHDataManagement/Generator/ImageGenerator.swift b/CHDataManagement/Generator/ImageGenerator.swift index a758c7a..0f20ffb 100644 --- a/CHDataManagement/Generator/ImageGenerator.swift +++ b/CHDataManagement/Generator/ImageGenerator.swift @@ -58,19 +58,19 @@ final class ImageGenerator { } guard let data = version.image.dataContent() else { - print("ImageGenerator: Failed to load data for image \(version.image.id)") + print("ImageGenerator: Failed to load data for image \(version.image.identifier)") return false } guard let originalImage = NSImage(data: data) else { - print("ImageGenerator: Failed to load image \(version.image.id)") + print("ImageGenerator: Failed to load image \(version.image.identifier)") return false } let representation = create(image: originalImage, width: CGFloat(version.maximumWidth), height: CGFloat(version.maximumHeight)) guard let data = create(image: representation, type: version.type, quality: version.quality) else { - print("ImageGenerator: Failed to get data for type \(version.type) of image \(version.image.id)") + print("ImageGenerator: Failed to get data for type \(version.type) of image \(version.image.identifier)") return false } @@ -161,7 +161,7 @@ final class ImageGenerator { process.waitUntilExit() if process.terminationStatus != 0 { - print("ImageGenerator: Failed to create AVIF image \(version.image.id)") + print("ImageGenerator: Failed to create AVIF image \(version.image.identifier)") let outputData = pipe.fileHandleForReading.readDataToEndOfFile() let outputString = String(data: outputData, encoding: .utf8) ?? "" print(outputString) diff --git a/CHDataManagement/Generator/ImageVersion.swift b/CHDataManagement/Generator/ImageVersion.swift index e4a4f79..4e4abdb 100644 --- a/CHDataManagement/Generator/ImageVersion.swift +++ b/CHDataManagement/Generator/ImageVersion.swift @@ -56,14 +56,14 @@ struct ImageVersion { extension ImageVersion: Identifiable { var id: String { - image.id + "-" + versionId + image.identifier + "-" + versionId } } extension ImageVersion: Equatable { static func == (lhs: ImageVersion, rhs: ImageVersion) -> Bool { - lhs.image.id == rhs.image.id && + lhs.image.identifier == rhs.image.identifier && lhs.maximumWidth == rhs.maximumWidth && lhs.maximumHeight == rhs.maximumHeight && lhs.type == rhs.type @@ -73,7 +73,7 @@ extension ImageVersion: Equatable { extension ImageVersion: Hashable { func hash(into hasher: inout Hasher) { - hasher.combine(image.id) + hasher.combine(image.identifier) hasher.combine(maximumWidth) hasher.combine(maximumHeight) hasher.combine(type) diff --git a/CHDataManagement/Generator/Page Generators/PageGenerator.swift b/CHDataManagement/Generator/Page Generators/PageGenerator.swift index eed989e..ffd0ee0 100644 --- a/CHDataManagement/Generator/Page Generators/PageGenerator.swift +++ b/CHDataManagement/Generator/Page Generators/PageGenerator.swift @@ -33,7 +33,7 @@ final class PageGenerator { language: language, results: results) let rawPageContent: String - if let existing = content.storage.pageContent(for: page.id, language: language) { + if let existing = content.storage.pageContent(for: page.identifier, language: language) { rawPageContent = existing } else { rawPageContent = makeEmptyPageContent(in: language) diff --git a/CHDataManagement/Generator/Post Lists/PostContentGenerator.swift b/CHDataManagement/Generator/Post Lists/PostContentGenerator.swift index c67b894..f5adedf 100644 --- a/CHDataManagement/Generator/Post Lists/PostContentGenerator.swift +++ b/CHDataManagement/Generator/Post Lists/PostContentGenerator.swift @@ -44,7 +44,7 @@ struct PostContentGenerator { } private var postDescription: String { - "content of post \(post.id) (\(language.shortText))" + "content of post \(post.identifier) (\(language.shortText))" } private func handleLink( diff --git a/CHDataManagement/Generator/Post Lists/PostListPageGenerator.swift b/CHDataManagement/Generator/Post Lists/PostListPageGenerator.swift index c12d8c6..d8dd02f 100644 --- a/CHDataManagement/Generator/Post Lists/PostListPageGenerator.swift +++ b/CHDataManagement/Generator/Post Lists/PostListPageGenerator.swift @@ -95,7 +95,7 @@ final class PostListPageGenerator { post: post).generate() return FeedEntryData( - entryId: post.id, + entryId: post.identifier, title: localized.title, textAboveTitle: post.dateText(in: language), link: linkUrl, diff --git a/CHDataManagement/Generator/Results/GenerationResults.swift b/CHDataManagement/Generator/Results/GenerationResults.swift index 7a6680f..557ea36 100644 --- a/CHDataManagement/Generator/Results/GenerationResults.swift +++ b/CHDataManagement/Generator/Results/GenerationResults.swift @@ -161,7 +161,7 @@ final class GenerationResults: ObservableObject { update { self.unsavedOutputFiles = unsavedOutputFiles } let emptyPages = cache.values.filter { $0.pageIsEmpty }.map { $0.itemId }.compactMap { id -> LocalizedPageId? in guard case .page(let page) = id.itemType else { return nil } - return LocalizedPageId(language: id.language, pageId: page.id) + return LocalizedPageId(language: id.language, pageId: page.identifier) }.asSet() update { self.emptyPages = emptyPages } let redirects = cache.values.compactMap { $0.redirect }.reduce(into: [:]) { $0[$1.originalUrl] = $1.newUrl } diff --git a/CHDataManagement/Generator/Results/PageGenerationResults.swift b/CHDataManagement/Generator/Results/PageGenerationResults.swift index a1e5eaf..d4116ab 100644 --- a/CHDataManagement/Generator/Results/PageGenerationResults.swift +++ b/CHDataManagement/Generator/Results/PageGenerationResults.swift @@ -11,7 +11,7 @@ extension ImageToGenerate: Hashable { func hash(into hasher: inout Hasher) { hasher.combine(size) - hasher.combine(image.id) + hasher.combine(image.identifier) } } @@ -284,7 +284,7 @@ final class PageGenerationResults: ObservableObject { func markPageAsEmpty() { guard case .page(let page) = itemId.itemType else { return } onMain { self.pageIsEmpty = true } - delegate.empty(.init(language: itemId.language, pageId: page.id)) + delegate.empty(.init(language: itemId.language, pageId: page.identifier)) } func redirect(from originalUrl: String, to newUrl: String) { diff --git a/CHDataManagement/Main/MainContentView.swift b/CHDataManagement/Main/MainContentView.swift index d11826e..4e89d61 100644 --- a/CHDataManagement/Main/MainContentView.swift +++ b/CHDataManagement/Main/MainContentView.swift @@ -2,7 +2,7 @@ import SwiftUI protocol MainContentView: View { - associatedtype Item: Identifiable + associatedtype Item init(item: Item) diff --git a/CHDataManagement/Main/SelectedDetailView.swift b/CHDataManagement/Main/SelectedDetailView.swift index e20dd25..1721b94 100644 --- a/CHDataManagement/Main/SelectedDetailView.swift +++ b/CHDataManagement/Main/SelectedDetailView.swift @@ -12,7 +12,7 @@ struct SelectedDetailView: View where Contained: MainContentView { var body: some View { if let item = selected { Contained(item: item) - .id(item.id) + //.id(item.id) } else { EmptyView() } diff --git a/CHDataManagement/Mock/File+Mock.swift b/CHDataManagement/Mock/File+Mock.swift index 831eb56..c3f7df9 100644 --- a/CHDataManagement/Mock/File+Mock.swift +++ b/CHDataManagement/Mock/File+Mock.swift @@ -20,6 +20,6 @@ extension FileResource { } static var mock: FileResource { - Content.mock.files.first(where: { $0.id == "my-file.txt" })! + Content.mock.files.first(where: { $0.identifier == "my-file.txt" })! } } diff --git a/CHDataManagement/Mock/Page+Mock.swift b/CHDataManagement/Mock/Page+Mock.swift index 8b35d04..c2e553b 100644 --- a/CHDataManagement/Mock/Page+Mock.swift +++ b/CHDataManagement/Mock/Page+Mock.swift @@ -28,13 +28,13 @@ extension Page { lastModified: nil, originalUrl: "projects/electronics/my-first-project/en.html"), tags: [ - content.tags.first(where: { $0.id == "electronics" })! + content.tags.first(where: { $0.identifier == "electronics" })! ]) ] } static var empty: Page { - Content.mock.pages.first(where: { $0.id == "my-id" })! + Content.mock.pages.first(where: { $0.identifier == "my-id" })! } } } diff --git a/CHDataManagement/Mock/Post+Mock.swift b/CHDataManagement/Mock/Post+Mock.swift index 544377f..a5ae03c 100644 --- a/CHDataManagement/Mock/Post+Mock.swift +++ b/CHDataManagement/Mock/Post+Mock.swift @@ -27,9 +27,9 @@ extension Post { startDate: .now, endDate: nil, tags: [ - content.tags.first(where: { $0.id == "nature" })!, - content.tags.first(where: { $0.id == "sports" })!, - content.tags.first(where: { $0.id == "hiking" })! + content.tags.first(where: { $0.identifier == "nature" })!, + content.tags.first(where: { $0.identifier == "sports" })!, + content.tags.first(where: { $0.identifier == "hiking" })! ], german: .init( content: content, @@ -47,44 +47,44 @@ extension Post { createdDate: .now, startDate: .now.addingTimeInterval(-86400), endDate: .now, tags: [ - content.tags.first(where: { $0.id == "nature" })!, - content.tags.first(where: { $0.id == "sports" })!, - content.tags.first(where: { $0.id == "hiking" })!, - content.tags.first(where: { $0.id == "mountains" })! + content.tags.first(where: { $0.identifier == "nature" })!, + content.tags.first(where: { $0.identifier == "sports" })!, + content.tags.first(where: { $0.identifier == "hiking" })!, + content.tags.first(where: { $0.identifier == "mountains" })! ], german: LocalizedPost( content: content, title: "Eine lange Wanderung", text: "Sehr schöne und einsame Tour von Oberau zum Krottenkopf. Abwechslungsreich und stellenweise fordernd, aufgrund der Länge und der Höhenmeter auch anstrengend.", images: [ - content.files.first(where: { $0.id == "image1" })!, - content.files.first(where: { $0.id == "image2" })!, - content.files.first(where: { $0.id == "image3" })!, - content.files.first(where: { $0.id == "image4" })! + content.files.first(where: { $0.identifier == "image1" })!, + content.files.first(where: { $0.identifier == "image2" })!, + content.files.first(where: { $0.identifier == "image3" })!, + content.files.first(where: { $0.identifier == "image4" })! ]), english: LocalizedPost( content: content, title: "A longer hike", text: "Very nice and solitary hike from Oberau to Krottenkopf. Challenging and rewarding, due to the length and height.", images: [ - content.files.first(where: { $0.id == "image1" })!, - content.files.first(where: { $0.id == "image2" })!, - content.files.first(where: { $0.id == "image3" })!, - content.files.first(where: { $0.id == "image4" })! + content.files.first(where: { $0.identifier == "image1" })!, + content.files.first(where: { $0.identifier == "image2" })!, + content.files.first(where: { $0.identifier == "image3" })!, + content.files.first(where: { $0.identifier == "image4" })! ])) ] } static var empty: Post { - Content.mock.posts.first(where: { $0.id == "empty" })! + Content.mock.posts.first(where: { $0.identifier == "empty" })! } static var hike: Post { - Content.mock.posts.first(where: { $0.id == "hike" })! + Content.mock.posts.first(where: { $0.identifier == "hike" })! } static var hike2: Post { - Content.mock.posts.first(where: { $0.id == "hike2" })! + Content.mock.posts.first(where: { $0.identifier == "hike2" })! } } } diff --git a/CHDataManagement/Mock/Tag+Mock.swift b/CHDataManagement/Mock/Tag+Mock.swift index 0682117..cbf59a6 100644 --- a/CHDataManagement/Mock/Tag+Mock.swift +++ b/CHDataManagement/Mock/Tag+Mock.swift @@ -13,7 +13,7 @@ extension Tag { urlComponent: "elektronik", name: "Elektronik", linkPreview: .init(description: "Eine Beschreibung des Tags", - image: content.files.first(where: { $0.id == "image2" })!), + image: content.files.first(where: { $0.identifier == "image2" })!), originalUrl: "projects/electronics" ), english: .init( @@ -22,7 +22,7 @@ extension Tag { name: "Electronics", linkPreview: .init( description: "Some description of the tag", - image: content.files.first(where: { $0.id == "image1" })!), + image: content.files.first(where: { $0.identifier == "image1" })!), originalUrl: "projects/electronics") ), Tag( @@ -53,23 +53,23 @@ extension Tag { } static var electronics: Tag { - Content.mock.tags.first(where: { $0.id == "electronics" })! + Content.mock.tags.first(where: { $0.identifier == "electronics" })! } static var nature: Tag { - Content.mock.tags.first(where: { $0.id == "nature" })! + Content.mock.tags.first(where: { $0.identifier == "nature" })! } static var sports: Tag { - Content.mock.tags.first(where: { $0.id == "sports" })! + Content.mock.tags.first(where: { $0.identifier == "sports" })! } static var hiking: Tag { - Content.mock.tags.first(where: { $0.id == "hiking" })! + Content.mock.tags.first(where: { $0.identifier == "hiking" })! } static var mountains: Tag { - Content.mock.tags.first(where: { $0.id == "mountains" })! + Content.mock.tags.first(where: { $0.identifier == "mountains" })! } } } diff --git a/CHDataManagement/Model/Content+Generation.swift b/CHDataManagement/Model/Content+Generation.swift index b98b608..bf9bf4e 100644 --- a/CHDataManagement/Model/Content+Generation.swift +++ b/CHDataManagement/Model/Content+Generation.swift @@ -69,7 +69,7 @@ extension Content { continue } let path = file.absoluteUrl - if !storage.copy(file: file.id, to: path) { + if !storage.copy(file: file.identifier, to: path) { results.general.unsavedOutput(path, source: .general) } } @@ -147,23 +147,23 @@ extension Content { // MARK: Find items by id func page(_ pageId: String) -> Page? { - pages.first { $0.id == pageId } + pages.first { $0.identifier == pageId } } func image(_ imageId: String) -> FileResource? { - files.first { $0.id == imageId && $0.type.isImage } + files.first { $0.identifier == imageId && $0.type.isImage } } func video(_ videoId: String) -> FileResource? { - files.first { $0.id == videoId && $0.type.isVideo } + files.first { $0.identifier == videoId && $0.type.isVideo } } func file(_ fileId: String) -> FileResource? { - files.first { $0.id == fileId } + files.first { $0.identifier == fileId } } func tag(_ tagId: String) -> Tag? { - tags.first { $0.id == tagId } + tags.first { $0.identifier == tagId } } // MARK: Generation input @@ -322,12 +322,12 @@ extension Content { 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.identifier) in language \(language)") return } guard storage.write(content, to: filePath) else { - print("Failed to save page \(page.id)") + print("Failed to save page \(page.identifier)") return } diff --git a/CHDataManagement/Model/Content+Save.swift b/CHDataManagement/Model/Content+Save.swift index 11e5b8d..5fc7800 100644 --- a/CHDataManagement/Model/Content+Save.swift +++ b/CHDataManagement/Model/Content+Save.swift @@ -83,16 +83,16 @@ extension Content { func removeUnlinkedFiles() -> Bool { var success = true - if !storage.deletePostFiles(notIn: posts.map { $0.id }) { + if !storage.deletePostFiles(notIn: posts.map { $0.identifier }) { success = false } - if !storage.deletePageFiles(notIn: pages.map { $0.id }) { + if !storage.deletePageFiles(notIn: pages.map { $0.identifier }) { success = false } - if !storage.deleteTagFiles(notIn: tags.map { $0.id }) { + if !storage.deleteTagFiles(notIn: tags.map { $0.identifier }) { success = false } - if !storage.deleteFileResources(notIn: files.map { $0.id }) { + if !storage.deleteFileResources(notIn: files.map { $0.identifier }) { success = false } return success diff --git a/CHDataManagement/Model/Content+Validation.swift b/CHDataManagement/Model/Content+Validation.swift index 2835d7c..2f19d95 100644 --- a/CHDataManagement/Model/Content+Validation.swift +++ b/CHDataManagement/Model/Content+Validation.swift @@ -7,19 +7,19 @@ extension Content { private static let disallowedCharactersInFileIds = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-.")).inverted func isNewIdForTag(_ id: String) -> Bool { - tagOverview?.id != id && !tags.contains { $0.id == id } + tagOverview?.identifier != id && !tags.contains { $0.identifier == id } } func isNewIdForPage(_ id: String) -> Bool { - !pages.contains { $0.id == id } + !pages.contains { $0.identifier == id } } func isNewIdForPost(_ id: String) -> Bool { - !posts.contains { $0.id == id } + !posts.contains { $0.identifier == id } } func isNewIdForFile(_ id: String) -> Bool { - !files.contains { $0.id == id } + !files.contains { $0.identifier == id } } func isValidIdForTagOrPageOrPost(_ id: String) -> Bool { diff --git a/CHDataManagement/Model/Content.swift b/CHDataManagement/Model/Content.swift index bda9f00..ed7d765 100644 --- a/CHDataManagement/Model/Content.swift +++ b/CHDataManagement/Model/Content.swift @@ -2,7 +2,7 @@ import Foundation import SwiftUI import Combine -final class Content: ObservableObject { +final class Content: ChangeObservableItem { @ObservedObject var storage: Storage @@ -47,6 +47,8 @@ final class Content: ObservableObject { var errorCallback: ((StorageError) -> Void)? + var cancellables: Set = [] + /// A cache of file sizes private var fileSizes: [String: Int] = [:] @@ -200,9 +202,9 @@ final class Content: ObservableObject { for file in self.files { guard file.type.isVideo else { continue } guard !file.isExternallyStored else { continue } - guard !storage.hasVideoThumbnail(for: file.id) else { continue } - if await imageGenerator.createVideoThumbnail(for: file.id) { - print("Generated thumbnail for \(file.id)") + guard !storage.hasVideoThumbnail(for: file.identifier) else { continue } + if await imageGenerator.createVideoThumbnail(for: file.identifier) { + print("Generated thumbnail for \(file.identifier)") file.didChange() } } @@ -229,6 +231,10 @@ final class Content: ObservableObject { self.lastSave = .now } + func needsSaving() { + needsSave() + } + // MARK: File sizes func size(of file: String) -> Int? { diff --git a/CHDataManagement/Model/DateItem.swift b/CHDataManagement/Model/DateItem.swift index 691ab1e..7d95fb4 100644 --- a/CHDataManagement/Model/DateItem.swift +++ b/CHDataManagement/Model/DateItem.swift @@ -2,7 +2,7 @@ import Foundation protocol DateItem { - var id: String { get } + var identifier: String { get } var startDate: Date { get } @@ -20,7 +20,7 @@ extension Sequence where Element: DateItem { func sortedByStartDateAndId() -> [Element] { sorted { (lhs, rhs) -> Bool in if lhs.startDate == rhs.startDate { - return lhs.id < rhs.id + return lhs.identifier < rhs.identifier } return lhs.startDate > rhs.startDate } diff --git a/CHDataManagement/Model/FileResource.swift b/CHDataManagement/Model/FileResource.swift index 4b48dbb..f8e570f 100644 --- a/CHDataManagement/Model/FileResource.swift +++ b/CHDataManagement/Model/FileResource.swift @@ -49,18 +49,18 @@ final class FileResource: Item, LocalizedItem { /// The dimensions of the image var imageDimensions: CGSize? { - get { content.dimensions(of: id) } + get { content.dimensions(of: identifier) } set { - content.cache(dimensions: newValue, of: id) + content.cache(dimensions: newValue, of: identifier) didChange(save: false) } } /// The size of the file in bytes var fileSize: Int? { - get { content.size(of: id) } + get { content.size(of: identifier) } set { - content.cache(size: newValue, of: id) + content.cache(size: newValue, of: identifier) didChange(save: false) } } @@ -114,11 +114,11 @@ final class FileResource: Item, LocalizedItem { // MARK: Text func textContent() -> String { - content.storage.fileContent(for: id) ?? "" + content.storage.fileContent(for: identifier) ?? "" } func save(textContent: String) -> Bool { - guard content.storage.save(fileContent: textContent, for: id) else { + guard content.storage.save(fileContent: textContent, for: identifier) else { return false } modifiedDate = .now @@ -126,7 +126,7 @@ final class FileResource: Item, LocalizedItem { } func dataContent() -> Foundation.Data? { - content.storage.fileData(for: id) + content.storage.fileData(for: identifier) } // MARK: Images @@ -165,7 +165,7 @@ final class FileResource: Item, LocalizedItem { } update(fileSize: displayImageData.count) guard let loadedImage = NSImage(data: displayImageData) else { - print("Failed to create image \(id)") + print("Failed to create image \(identifier)") return nil } update(imageDimensions: loadedImage.size) @@ -191,14 +191,14 @@ final class FileResource: Item, LocalizedItem { private var displayImageData: Foundation.Data? { if type.isImage { - guard let data = content.storage.fileData(for: id) else { - print("Failed to load data for image \(id)") + guard let data = content.storage.fileData(for: identifier) else { + print("Failed to load data for image \(identifier)") return nil } return data } if type.isVideo { - return content.storage.getVideoThumbnail(for: id) + return content.storage.getVideoThumbnail(for: identifier) } return nil } @@ -234,7 +234,7 @@ final class FileResource: Item, LocalizedItem { func determineFileSize() { DispatchQueue.global(qos: .userInitiated).async { - let size = self.content.storage.size(of: self.id) + let size = self.content.storage.size(of: self.identifier) self.update(fileSize: size) } } @@ -249,7 +249,7 @@ final class FileResource: Item, LocalizedItem { /// The path to the output folder where image versions are stored (no leading slash) var outputImageFolder: String { - "\(content.settings.paths.imagesOutputFolderPath)/\(id.fileNameWithoutExtension)" + "\(content.settings.paths.imagesOutputFolderPath)/\(identifier.fileNameWithoutExtension)" } func outputPath(width: Int, height: Int, type: FileType?) -> String { @@ -293,9 +293,9 @@ final class FileResource: Item, LocalizedItem { func createVideoThumbnail() { guard type.isVideo else { return } - guard !content.storage.hasVideoThumbnail(for: id) else { return } + guard !content.storage.hasVideoThumbnail(for: identifier) else { return } Task { - if await content.imageGenerator.createVideoThumbnail(for: id) { + if await content.imageGenerator.createVideoThumbnail(for: identifier) { didChange() } } @@ -322,7 +322,7 @@ final class FileResource: Item, LocalizedItem { return "/" + customOutputPath } } - let path = pathPrefix + "/" + id + let path = pathPrefix + "/" + identifier return makeCleanAbsolutePath(path) } @@ -353,14 +353,14 @@ final class FileResource: Item, LocalizedItem { @discardableResult func update(id newId: String) -> Bool { guard !isExternallyStored else { - id = newId + identifier = newId return true } - guard content.storage.move(file: id, to: newId) else { - print("Failed to move file \(id) to \(newId)") + guard content.storage.move(file: identifier, to: newId) else { + print("Failed to move file \(identifier) to \(newId)") return false } - id = newId + identifier = newId return true } } @@ -368,7 +368,7 @@ final class FileResource: Item, LocalizedItem { extension FileResource: CustomStringConvertible { var description: String { - id + identifier } } @@ -418,6 +418,6 @@ extension FileResource: StorageItem { } func saveToDisk(_ data: Data) -> Bool { - content.storage.save(fileResource: data, for: id) + content.storage.save(fileResource: data, for: identifier) } } diff --git a/CHDataManagement/Model/Item/Item.swift b/CHDataManagement/Model/Item/Item.swift index 279611d..7f7edcb 100644 --- a/CHDataManagement/Model/Item/Item.swift +++ b/CHDataManagement/Model/Item/Item.swift @@ -7,11 +7,17 @@ class Item: ChangeObservingItem, Identifiable { @Published private var changeToggle = false + /// A session-id for the item for identification + let id = UUID() + + /// The unique, persistent identifier of the item + /// + /// This identifier is not used for `Identifiable`, since it may be changed through the UI. @Published - var id: String + var identifier: String init(content: Content, id: String) { - self.id = id + self.identifier = id super.init(content: content) observeChanges() @@ -44,14 +50,14 @@ class Item: ChangeObservingItem, Identifiable { } var itemId: ItemId { - .init(type: itemType, id: id) + .init(type: itemType, id: identifier) } } extension Item: Equatable { static func == (lhs: Item, rhs: Item) -> Bool { - lhs.id == rhs.id && lhs.itemType == rhs.itemType + lhs.id == rhs.id } } @@ -59,13 +65,12 @@ extension Item: Hashable { func hash(into hasher: inout Hasher) { hasher.combine(id) - hasher.combine(itemType) } } extension Item: Comparable { static func < (lhs: Item, rhs: Item) -> Bool { - lhs.id < rhs.id && lhs.itemType < rhs.itemType + lhs.identifier < rhs.identifier && lhs.itemType < rhs.itemType } } diff --git a/CHDataManagement/Model/Item/ItemReference.swift b/CHDataManagement/Model/Item/ItemReference.swift index 3b851a5..afd960d 100644 --- a/CHDataManagement/Model/Item/ItemReference.swift +++ b/CHDataManagement/Model/Item/ItemReference.swift @@ -31,11 +31,11 @@ extension ItemReference: Identifiable { case .feed: return "1-feed" case .post(let post): - return "2-post-\(post.id)" + return "2-post-\(post.identifier)" case .page(let page): - return "3-page-\(page.id)" + return "3-page-\(page.identifier)" case .tagPage(let tag): - return "5-tag-\(tag.id)" + return "5-tag-\(tag.identifier)" case .tagOverview: return "4-tag-overview" } @@ -76,11 +76,11 @@ extension ItemReference: CustomStringConvertible { case .feed: return "Feed" case .post(let post): - return "Post \(post.id)" + return "Post \(post.identifier)" case .page(let page): - return "Page \(page.id)" + return "Page \(page.identifier)" case .tagPage(let tag): - return "Tag \(tag.id)" + return "Tag \(tag.identifier)" case .tagOverview: return "Tag Overview" } diff --git a/CHDataManagement/Model/LinkPreview.swift b/CHDataManagement/Model/LinkPreview.swift index e6d0298..5d623de 100644 --- a/CHDataManagement/Model/LinkPreview.swift +++ b/CHDataManagement/Model/LinkPreview.swift @@ -37,7 +37,7 @@ final class LinkPreview: ObservableObject { // MARK: Storage var data: Data { - .init(title: title, description: description, image: image?.id) + .init(title: title, description: description, image: image?.identifier) } init(context: LoadingContext, data: Data) { diff --git a/CHDataManagement/Model/Loading/LoadingContext.swift b/CHDataManagement/Model/Loading/LoadingContext.swift index 33644d2..40de35d 100644 --- a/CHDataManagement/Model/Loading/LoadingContext.swift +++ b/CHDataManagement/Model/Loading/LoadingContext.swift @@ -27,7 +27,7 @@ final class LoadingContext { posts: posts.values.sortedByStartDateAndId(), pages: pages.values.sortedByStartDateAndId(), tags: tags.values.sorted(), - files: files.values.sorted { $0.id }, + files: files.values.sorted { $0.identifier }, tagOverview: tagOverview, errors: errors.sorted().map { StorageError(message: $0) }) } diff --git a/CHDataManagement/Model/LocalizedPost.swift b/CHDataManagement/Model/LocalizedPost.swift index 89388e4..eeaa6b2 100644 --- a/CHDataManagement/Model/LocalizedPost.swift +++ b/CHDataManagement/Model/LocalizedPost.swift @@ -96,7 +96,7 @@ extension LocalizedPost { } var data: Data { - .init(images: images.map { $0.id }, + .init(images: images.map { $0.identifier }, labels: labels.map { $0.data }.nonEmpty, title: title, text: text, diff --git a/CHDataManagement/Model/Page.swift b/CHDataManagement/Model/Page.swift index ec584d5..2ff9962 100644 --- a/CHDataManagement/Model/Page.swift +++ b/CHDataManagement/Model/Page.swift @@ -75,11 +75,11 @@ final class Page: Item, DateItem, LocalizedItem { @discardableResult func update(id newId: String) -> Bool { - guard content.storage.move(page: id, to: newId) else { - print("Failed to move files of page \(id)") + guard content.storage.move(page: identifier, to: newId) else { + print("Failed to move files of page \(identifier)") return false } - id = newId + identifier = newId return true } @@ -146,11 +146,11 @@ final class Page: Item, DateItem, LocalizedItem { } func pageContent(in language: ContentLanguage) -> String? { - content.storage.pageContent(for: id, language: language) + content.storage.pageContent(for: identifier, language: language) } func removeContent(in language: ContentLanguage) -> Bool { - guard content.storage.remove(pageContent: id, in: language) else { + guard content.storage.remove(pageContent: identifier, in: language) else { return false } if localized(in: language).update(hasContent: false) { @@ -160,7 +160,7 @@ final class Page: Item, DateItem, LocalizedItem { } func save(pageContent: String, in language: ContentLanguage) -> Bool { - guard content.storage.save(pageContent: pageContent, for: id, in: language) else { + guard content.storage.save(pageContent: pageContent, for: identifier, in: language) else { return false } if localized(in: language).update(hasContent: true) { @@ -175,7 +175,7 @@ final class Page: Item, DateItem, LocalizedItem { func updateContentExistence() { var didUpdate = false for language in ContentLanguage.allCases { - let hasContent = content.storage.hasPageContent(for: id, language: language) + let hasContent = content.storage.hasPageContent(for: identifier, language: language) if localized(in: language).update(hasContent: hasContent) { didUpdate = true } @@ -234,7 +234,7 @@ extension Page: StorageItem { .init( isDraft: isDraft, externalLink: externalLink, - tags: tags.map { $0.id }, + tags: tags.map { $0.identifier }, hideDate: hideDate ? true : nil, createdDate: createdDate, startDate: startDate, @@ -244,6 +244,6 @@ extension Page: StorageItem { } func saveToDisk(_ data: Data) -> Bool { - content.storage.save(page: data, for: id) + content.storage.save(page: data, for: identifier) } } diff --git a/CHDataManagement/Model/Post.swift b/CHDataManagement/Model/Post.swift index 94640f1..022d7a5 100644 --- a/CHDataManagement/Model/Post.swift +++ b/CHDataManagement/Model/Post.swift @@ -118,11 +118,11 @@ final class Post: Item, DateItem, LocalizedItem { A title for the UI, not the generation. */ override func title(in language: ContentLanguage) -> String { - localized(in: language).title ?? id + localized(in: language).title ?? identifier } func contains(_ string: String) -> Bool { - id.contains(string) || + identifier.contains(string) || german.contains(string) || english.contains(string) } @@ -135,11 +135,11 @@ final class Post: Item, DateItem, LocalizedItem { @discardableResult func update(id newId: String) -> Bool { - guard content.storage.move(post: id, to: newId) else { - print("Failed to move file of post \(id)") + guard content.storage.move(post: identifier, to: newId) else { + print("Failed to move file of post \(identifier)") return false } - id = newId + identifier = newId return true } @@ -149,10 +149,10 @@ final class Post: Item, DateItem, LocalizedItem { } func makePage() -> Page { - var id = self.id + var id = self.identifier var number = 2 while !content.isNewIdForPage(id) { - id += "\(self.id)-\(number)" + id += "\(self.identifier)-\(number)" number += 1 } // Move tags to page @@ -210,13 +210,13 @@ extension Post: StorageItem { createdDate: createdDate, startDate: startDate, endDate: endDate, - tags: tags.map { $0.id }, + tags: tags.map { $0.identifier }, german: german.data, english: english.data, - linkedPageId: linkedPage?.id) + linkedPageId: linkedPage?.identifier) } func saveToDisk(_ data: Data) -> Bool { - content.storage.save(post: data, for: id) + content.storage.save(post: data, for: identifier) } } diff --git a/CHDataManagement/Model/Settings/AudioPlayerSettings.swift b/CHDataManagement/Model/Settings/AudioPlayerSettings.swift index 4a15bed..a4a2539 100644 --- a/CHDataManagement/Model/Settings/AudioPlayerSettings.swift +++ b/CHDataManagement/Model/Settings/AudioPlayerSettings.swift @@ -61,8 +61,8 @@ extension AudioPlayerSettings { var data: Data { .init(playlistCoverImageSize: playlistCoverImageSize, smallCoverImageSize: smallCoverImageSize, - audioPlayerJsFile: audioPlayerJsFile?.id, - audioPlayerCssFile: audioPlayerCssFile?.id, + audioPlayerJsFile: audioPlayerJsFile?.identifier, + audioPlayerCssFile: audioPlayerCssFile?.identifier, german: german.data, english: english.data) } diff --git a/CHDataManagement/Model/Settings/GeneralSettings.swift b/CHDataManagement/Model/Settings/GeneralSettings.swift index 039952d..9e5d547 100644 --- a/CHDataManagement/Model/Settings/GeneralSettings.swift +++ b/CHDataManagement/Model/Settings/GeneralSettings.swift @@ -65,7 +65,7 @@ extension GeneralSettings { remotePortForUpload: remotePortForUpload, remotePathForUpload: remotePathForUpload, urlForPushNotification: urlForPushNotification, - requiredFiles: requiredFiles.nonEmpty?.map { $0.id }.sorted()) + requiredFiles: requiredFiles.nonEmpty?.map { $0.identifier }.sorted()) } struct Data: Codable, Equatable { diff --git a/CHDataManagement/Model/Settings/PageSettings.swift b/CHDataManagement/Model/Settings/PageSettings.swift index f373335..38f28d8 100644 --- a/CHDataManagement/Model/Settings/PageSettings.swift +++ b/CHDataManagement/Model/Settings/PageSettings.swift @@ -113,13 +113,13 @@ extension PageSettings { .init(contentWidth: contentWidth, largeImageWidth: largeImageWidth, pageLinkImageSize: pageLinkImageSize, - defaultCssFile: defaultCssFile?.id, - codeHighlightingJsFile: codeHighlightingJsFile?.id, - modelViewerJsFile: modelViewerJsFile?.id, - imageCompareJsFile: imageCompareJsFile?.id, - imageCompareCssFile: imageCompareCssFile?.id, - manifestFile: manifestFile?.id, - routeJsFile: routeJsFile?.id, + defaultCssFile: defaultCssFile?.identifier, + codeHighlightingJsFile: codeHighlightingJsFile?.identifier, + modelViewerJsFile: modelViewerJsFile?.identifier, + imageCompareJsFile: imageCompareJsFile?.identifier, + imageCompareCssFile: imageCompareCssFile?.identifier, + manifestFile: manifestFile?.identifier, + routeJsFile: routeJsFile?.identifier, german: german.data, english: english.data) } diff --git a/CHDataManagement/Model/Settings/PostSettings.swift b/CHDataManagement/Model/Settings/PostSettings.swift index e626f95..899a17e 100644 --- a/CHDataManagement/Model/Settings/PostSettings.swift +++ b/CHDataManagement/Model/Settings/PostSettings.swift @@ -72,9 +72,9 @@ extension PostSettings { var data: PostSettings.Data { .init(postsPerPage: postsPerPage, contentWidth: contentWidth, - swiperCssFile: swiperCssFile?.id, - swiperJsFile: swiperJsFile?.id, - defaultCssFile: defaultCssFile?.id, + swiperCssFile: swiperCssFile?.identifier, + swiperJsFile: swiperJsFile?.identifier, + defaultCssFile: defaultCssFile?.identifier, german: german.data, english: english.data) } diff --git a/CHDataManagement/Model/Tag.swift b/CHDataManagement/Model/Tag.swift index 4dcb586..2e8e429 100644 --- a/CHDataManagement/Model/Tag.swift +++ b/CHDataManagement/Model/Tag.swift @@ -37,11 +37,11 @@ class Tag: Item, LocalizedItem { @discardableResult func update(id newId: String) -> Bool { - guard content.storage.move(tag: id, to: newId) else { - print("Failed to move files of tag \(id)") + guard content.storage.move(tag: identifier, to: newId) else { + print("Failed to move files of tag \(identifier)") return false } - id = newId + identifier = newId return true } @@ -106,6 +106,6 @@ extension Tag: StorageItem { } func saveToDisk(_ data: Data) -> Bool { - content.storage.save(tag: data, for: id) + content.storage.save(tag: data, for: identifier) } } diff --git a/CHDataManagement/Model/TagOverview.swift b/CHDataManagement/Model/TagOverview.swift index ef78c14..4302041 100644 --- a/CHDataManagement/Model/TagOverview.swift +++ b/CHDataManagement/Model/TagOverview.swift @@ -2,6 +2,6 @@ final class TagOverview: Tag { override var itemId: ItemId { - .init(type: .tagOverview, id: id) + .init(type: .tagOverview, id: identifier) } } diff --git a/CHDataManagement/Views/Files/AddFileView.swift b/CHDataManagement/Views/Files/AddFileView.swift index ada9cc9..9446b4d 100644 --- a/CHDataManagement/Views/Files/AddFileView.swift +++ b/CHDataManagement/Views/Files/AddFileView.swift @@ -78,6 +78,10 @@ struct AddFileView: View { } private func importSelectedFiles() { + guard !filesToAdd.isEmpty else { + dismiss() + return + } for file in filesToAdd { guard file.isSelected else { print("Skipping unselected file \(file.uniqueId)") diff --git a/CHDataManagement/Views/Files/FileContentView.swift b/CHDataManagement/Views/Files/FileContentView.swift index 69c5921..3c263d9 100644 --- a/CHDataManagement/Views/Files/FileContentView.swift +++ b/CHDataManagement/Views/Files/FileContentView.swift @@ -41,7 +41,7 @@ struct FileContentView: View { .foregroundStyle(.secondary) case .text, .code: TextFileContentView(file: file) - .id(file.id + file.modifiedDate.description) + .id(file.identifier + file.modifiedDate.description) case .video: VStack { if let image = file.imageToDisplay { diff --git a/CHDataManagement/Views/Files/FileDetailView.swift b/CHDataManagement/Views/Files/FileDetailView.swift index 9ee733a..432f60a 100644 --- a/CHDataManagement/Views/Files/FileDetailView.swift +++ b/CHDataManagement/Views/Files/FileDetailView.swift @@ -72,11 +72,12 @@ struct FileDetailView: View { } IdPropertyView( - id: $file.id, + id: $file.identifier, title: "Name", footer: "The unique name of the file, which is also used to reference it in posts and pages.", validation: file.isValid, update: { file.update(id: $0) }) + .id(file.id) switch language { case .english: @@ -154,7 +155,7 @@ struct FileDetailView: View { } private func showFileInFinder() { - content.storage.openFinderWindow(withSelectedFile: file.id) + content.storage.openFinderWindow(withSelectedFile: file.identifier) } private func markFileAsChanged() { @@ -169,11 +170,11 @@ struct FileDetailView: View { private func replaceFile() { guard let url = openFilePanel() else { - print("File '\(file.id)': No file selected as replacement") + print("File '\(file.identifier)': No file selected as replacement") return } - guard content.storage.importExternalFile(at: url, fileId: file.id) else { - print("File '\(file.id)': Failed to replace file") + guard content.storage.importExternalFile(at: url, fileId: file.identifier) else { + print("File '\(file.identifier)': Failed to replace file") return } @@ -197,7 +198,7 @@ struct FileDetailView: View { let response = panel.runModal() guard response == .OK else { - print("File '\(file.id)': Failed to select file to replace") + print("File '\(file.identifier)': Failed to select file to replace") return nil } @@ -209,8 +210,8 @@ struct FileDetailView: View { return } - guard content.storage.removeFileContent(file: file.id) else { - print("File '\(file.id)': Failed to delete file to make it external") + guard content.storage.removeFileContent(file: file.identifier) else { + print("File '\(file.identifier)': Failed to delete file to make it external") return } DispatchQueue.main.async { @@ -220,8 +221,8 @@ struct FileDetailView: View { } private func deleteFile() { - guard content.storage.delete(file: file.id) else { - print("File '\(file.id)': Failed to delete file in content folder") + guard content.storage.delete(file: file.identifier) else { + print("File '\(file.identifier)': Failed to delete file in content folder") return } content.remove(file) diff --git a/CHDataManagement/Views/Files/FileListView.swift b/CHDataManagement/Views/Files/FileListView.swift index 9a6e549..ff04816 100644 --- a/CHDataManagement/Views/Files/FileListView.swift +++ b/CHDataManagement/Views/Files/FileListView.swift @@ -32,7 +32,7 @@ struct FileListView: View { guard !searchString.isEmpty else { return filesBySelectedType } - return filesBySelectedType.filter { $0.id.contains(searchString) } + return filesBySelectedType.filter { $0.identifier.contains(searchString) } } var body: some View { @@ -55,10 +55,10 @@ struct FileListView: View { LazyVStack(spacing: 0) { ForEach(filteredFiles) { file in SelectableListItem(selected: selectedFile == file) { - Text(file.id) + Text(file.identifier) .lineLimit(1) } - .id(file.id) + .id(file.identifier) .onTapGesture { selectedFile = file } diff --git a/CHDataManagement/Views/Files/FileToAdd.swift b/CHDataManagement/Views/Files/FileToAdd.swift index 82b83b3..36e77b7 100644 --- a/CHDataManagement/Views/Files/FileToAdd.swift +++ b/CHDataManagement/Views/Files/FileToAdd.swift @@ -30,7 +30,7 @@ final class FileToAdd: ObservableObject { } var idAlreadyExists: Bool { - content.files.contains { $0.id == uniqueId } + content.files.contains { $0.identifier == uniqueId } } } diff --git a/CHDataManagement/Views/Files/MultiFileSelectionView.swift b/CHDataManagement/Views/Files/MultiFileSelectionView.swift index 8dd96f7..7e30f8d 100644 --- a/CHDataManagement/Views/Files/MultiFileSelectionView.swift +++ b/CHDataManagement/Views/Files/MultiFileSelectionView.swift @@ -43,7 +43,7 @@ struct MultiFileSelectionView: View { guard !searchString.isEmpty else { return filesBySelectedType } - return filesBySelectedType.filter { $0.id.contains(searchString) } + return filesBySelectedType.filter { $0.identifier.contains(searchString) } } var body: some View { @@ -59,7 +59,7 @@ struct MultiFileSelectionView: View { .foregroundStyle(.red) .contentShape(Rectangle()) .onTapGesture { deselect(file: file) } - Text(file.id) + Text(file.identifier) Spacer() } } @@ -99,7 +99,7 @@ struct MultiFileSelectionView: View { Image(systemSymbol: .plusCircleFill) .foregroundStyle(.green) } - Text(file.id) + Text(file.identifier) Spacer() } .contentShape(Rectangle()) diff --git a/CHDataManagement/Views/Files/TextFileContentView.swift b/CHDataManagement/Views/Files/TextFileContentView.swift index 06ece75..277da38 100644 --- a/CHDataManagement/Views/Files/TextFileContentView.swift +++ b/CHDataManagement/Views/Files/TextFileContentView.swift @@ -49,9 +49,9 @@ struct TextFileContentView: View { private func reload() { fileContent = file.textContent() - loadedFile = file.id + loadedFile = file.identifier loadedFileDate = file.modifiedDate - print("Loaded content of file \(file.id)") + print("Loaded content of file \(file.identifier)") } private func save() { @@ -59,25 +59,25 @@ struct TextFileContentView: View { print("[ERROR] Text File View: No file loaded to save") return } - guard loadedFile == file.id else { + guard loadedFile == file.identifier else { print("[ERROR] Text File View: Not saving since file changed") reload() return } guard loadedFileDate == file.modifiedDate else { - print("Text File View: Not saving changed file \(file.id)") + print("Text File View: Not saving changed file \(file.identifier)") reload() return } guard fileContent != "" else { - print("Text File View: Not saving empty file \(file.id)") + print("Text File View: Not saving empty file \(file.identifier)") return } guard file.save(textContent: fileContent) else { - print("[ERROR] Text File View: Failed to save file \(file.id)") + print("[ERROR] Text File View: Failed to save file \(file.identifier)") return } loadedFileDate = file.modifiedDate - print("Text File View: Saved file \(file.id)") + print("Text File View: Saved file \(file.identifier)") } } diff --git a/CHDataManagement/Views/Generation/GenerationContentView.swift b/CHDataManagement/Views/Generation/GenerationContentView.swift index 8551438..a1a4808 100644 --- a/CHDataManagement/Views/Generation/GenerationContentView.swift +++ b/CHDataManagement/Views/Generation/GenerationContentView.swift @@ -72,11 +72,11 @@ struct GenerationContentView: View { GenerationStringIssuesView( text: "required files", statusWhenNonEmpty: .nominal, - items: content.results.requiredFiles) { $0.id } + items: content.results.requiredFiles) { $0.identifier } GenerationStringIssuesView( text: "external files", statusWhenNonEmpty: .nominal, - items: content.results.externalFiles) { $0.id } + items: content.results.externalFiles) { $0.identifier } GenerationIssuesView( text: "empty pages", statusWhenNonEmpty: .warning, @@ -96,14 +96,14 @@ struct GenerationContentView: View { statusWhenNonEmpty: .warning, items: draftPages, buttonText: "Show", - itemText: { $0.id }, + itemText: { $0.identifier }, action: { show($0) }) GenerationIssuesActionView( title: "draft posts", statusWhenNonEmpty: .warning, items: draftPosts, buttonText: "Show", - itemText: { $0.id }, + itemText: { $0.identifier }, action: { show($0) }) GenerationIssuesView( text: "additional output files", @@ -117,10 +117,10 @@ struct GenerationContentView: View { } GenerationStringIssuesView( text: "inaccessible files", - items: content.results.inaccessibleFiles) { $0.id } + items: content.results.inaccessibleFiles) { $0.identifier } GenerationStringIssuesView( text: "unparsable files", - items: content.results.unparsableFiles) { $0.id } + items: content.results.unparsableFiles) { $0.identifier } GenerationStringIssuesView( text: "unsaved output files", items: content.results.unsavedOutputFiles) diff --git a/CHDataManagement/Views/Generic/FilePropertyView.swift b/CHDataManagement/Views/Generic/FilePropertyView.swift index d22cfef..40409e3 100644 --- a/CHDataManagement/Views/Generic/FilePropertyView.swift +++ b/CHDataManagement/Views/Generic/FilePropertyView.swift @@ -24,7 +24,7 @@ struct FilePropertyView: View { var body: some View { GenericPropertyView(title: title, footer: footer) { HStack { - Text(selectedFile?.id ?? "No file selected") + Text(selectedFile?.identifier ?? "No file selected") Spacer() Button("Select") { showFileSelectionSheet = true diff --git a/CHDataManagement/Views/Generic/OptionalImagePropertyView.swift b/CHDataManagement/Views/Generic/OptionalImagePropertyView.swift index c25f2e9..2f269f6 100644 --- a/CHDataManagement/Views/Generic/OptionalImagePropertyView.swift +++ b/CHDataManagement/Views/Generic/OptionalImagePropertyView.swift @@ -36,7 +36,7 @@ struct OptionalImagePropertyView: View { } HStack { - Text(selectedImage?.id ?? "No file selected") + Text(selectedImage?.identifier ?? "No file selected") Spacer() Button("Select") { showSelectionSheet = true diff --git a/CHDataManagement/Views/Generic/PagePropertyView.swift b/CHDataManagement/Views/Generic/PagePropertyView.swift index e62c89c..bca9610 100644 --- a/CHDataManagement/Views/Generic/PagePropertyView.swift +++ b/CHDataManagement/Views/Generic/PagePropertyView.swift @@ -15,7 +15,7 @@ struct PagePropertyView: View { var body: some View { GenericPropertyView(title: title, footer: footer) { HStack { - Text(selectedPage?.id ?? "No page selected") + Text(selectedPage?.identifier ?? "No page selected") Spacer() Button("Select") { showPageSelectionSheet = true diff --git a/CHDataManagement/Views/Generic/TagDisplayView.swift b/CHDataManagement/Views/Generic/TagDisplayView.swift index 6e39b54..9975fff 100644 --- a/CHDataManagement/Views/Generic/TagDisplayView.swift +++ b/CHDataManagement/Views/Generic/TagDisplayView.swift @@ -16,7 +16,7 @@ struct TagDisplayView: View { var body: some View { FlowHStack { - ForEach(tags, id: \.id) { tag in + ForEach(tags, id: \.identifier) { tag in TagView(text: tag.localized(in: language).name) .foregroundStyle(.white) } diff --git a/CHDataManagement/Views/Generic/TagPickerView.swift b/CHDataManagement/Views/Generic/TagPickerView.swift index 54c221f..3b6315b 100644 --- a/CHDataManagement/Views/Generic/TagPickerView.swift +++ b/CHDataManagement/Views/Generic/TagPickerView.swift @@ -27,7 +27,7 @@ struct TagPickerView: View { Text("Select a tag to link to") List(content.tags, selection: $newSelection) { tag in let loc = tag.localized(in: language) - Text("\(loc.title) (\(tag.id))") + Text("\(loc.title) (\(tag.identifier))") .tag(tag) } .frame(minHeight: 300) diff --git a/CHDataManagement/Views/Generic/TagPropertyView.swift b/CHDataManagement/Views/Generic/TagPropertyView.swift index db394c9..fc4d6a1 100644 --- a/CHDataManagement/Views/Generic/TagPropertyView.swift +++ b/CHDataManagement/Views/Generic/TagPropertyView.swift @@ -15,7 +15,7 @@ struct TagPropertyView: View { var body: some View { GenericPropertyView(title: title, footer: footer) { HStack { - Text(selectedTag?.id ?? "No tag selected") + Text(selectedTag?.identifier ?? "No tag selected") Spacer() Button("Select") { showTagSelectionSheet = true diff --git a/CHDataManagement/Views/Pages/Commands/Insert+Buttons.swift b/CHDataManagement/Views/Pages/Commands/Insert+Buttons.swift index 4e53400..ab7d656 100644 --- a/CHDataManagement/Views/Pages/Commands/Insert+Buttons.swift +++ b/CHDataManagement/Views/Pages/Commands/Insert+Buttons.swift @@ -20,7 +20,7 @@ final class InsertableFileButton: ObservableObject { """ icon: \(label.icon.rawValue) text: \(label.value) - file: \(file.id) + file: \(file.identifier) """ guard let downloadedFileName else { return result @@ -86,7 +86,7 @@ struct InsertableButtons: View, InsertableCommandView { var id: String { switch self { case .file(let file): - return "file-\(file.file?.id ?? "none")" + return "file-\(file.file?.identifier ?? "none")" case .url(let url): return "url-\(url.url)" case .event(let event): @@ -161,7 +161,7 @@ private struct FileButtonView: View { var body: some View { HStack { LabelEditingView(label: $content.label) - Button("\(content.file?.id ?? "Select file")", action: { showFileSelectionSheet = true }) + Button("\(content.file?.identifier ?? "Select file")", action: { showFileSelectionSheet = true }) OptionalTextField("", text: $content.downloadedFileName, prompt: "Downloaded file name") .textFieldStyle(.roundedBorder) } diff --git a/CHDataManagement/Views/Pages/Commands/Insert+Gallery.swift b/CHDataManagement/Views/Pages/Commands/Insert+Gallery.swift index e0b225d..17dd8b4 100644 --- a/CHDataManagement/Views/Pages/Commands/Insert+Gallery.swift +++ b/CHDataManagement/Views/Pages/Commands/Insert+Gallery.swift @@ -28,7 +28,7 @@ struct InsertableGallery: View, InsertableCommandView { } return ( ["```\(GalleryBlock.blockId)"] + - images.map { $0.id } + + images.map { $0.identifier } + ["```"] ).joined(separator: "\n") } diff --git a/CHDataManagement/Views/Pages/Commands/Insert+Image.swift b/CHDataManagement/Views/Pages/Commands/Insert+Image.swift index 8795dab..c31366a 100644 --- a/CHDataManagement/Views/Pages/Commands/Insert+Image.swift +++ b/CHDataManagement/Views/Pages/Commands/Insert+Image.swift @@ -24,9 +24,9 @@ struct InsertableImage: View, InsertableCommandView { return nil } guard let caption else { - return "![image](\(selectedImage.id))" + return "![image](\(selectedImage.identifier))" } - return "![image](\(selectedImage.id);\(caption))" + return "![image](\(selectedImage.identifier);\(caption))" } } diff --git a/CHDataManagement/Views/Pages/Commands/Insert+Link.swift b/CHDataManagement/Views/Pages/Commands/Insert+Link.swift index 88ff85e..750cdcc 100644 --- a/CHDataManagement/Views/Pages/Commands/Insert+Link.swift +++ b/CHDataManagement/Views/Pages/Commands/Insert+Link.swift @@ -45,11 +45,11 @@ struct InsertableLink: View, InsertableCommandView { case .post, .tagOverview: return nil case .page: - return selectedPage?.id + return selectedPage?.identifier case .tag: - return selectedTag?.id + return selectedTag?.identifier case .file: - return selectedFile?.id + return selectedFile?.identifier } } diff --git a/CHDataManagement/Views/Pages/Commands/Insert+Route.swift b/CHDataManagement/Views/Pages/Commands/Insert+Route.swift index 04982bc..1c9b8da 100644 --- a/CHDataManagement/Views/Pages/Commands/Insert+Route.swift +++ b/CHDataManagement/Views/Pages/Commands/Insert+Route.swift @@ -40,8 +40,8 @@ struct InsertableRoute: View, InsertableCommandView { return nil } var result = ["```route"] - result.append("\(RouteBlock.Key.image.rawValue): \(selectedImage.id)") - result.append("\(RouteBlock.Key.file.rawValue): \(dataFile.id)") + result.append("\(RouteBlock.Key.image.rawValue): \(selectedImage.identifier)") + result.append("\(RouteBlock.Key.file.rawValue): \(dataFile.identifier)") if components != Set(RouteStatisticType.allCases) { let list = components .map { $0.rawValue } diff --git a/CHDataManagement/Views/Pages/Commands/Insert+Video.swift b/CHDataManagement/Views/Pages/Commands/Insert+Video.swift index 1cd333b..8537b14 100644 --- a/CHDataManagement/Views/Pages/Commands/Insert+Video.swift +++ b/CHDataManagement/Views/Pages/Commands/Insert+Video.swift @@ -50,16 +50,16 @@ struct InsertableVideo: View, InsertableCommandView { var lines: [String] = [] lines.append("```video") if let posterImage { - lines.append("\(VideoBlock.Key.poster): \(posterImage.id)") + lines.append("\(VideoBlock.Key.poster): \(posterImage.identifier)") } if let videoH265 { - lines.append("\(VideoBlock.Key.h265): \(videoH265.id)") + lines.append("\(VideoBlock.Key.h265): \(videoH265.identifier)") } if let videoH264 { - lines.append("\(VideoBlock.Key.h264): \(videoH264.id)") + lines.append("\(VideoBlock.Key.h264): \(videoH264.identifier)") } if let videoWebm { - lines.append("\(VideoBlock.Key.webm): \(videoWebm.id)") + lines.append("\(VideoBlock.Key.webm): \(videoWebm.identifier)") } if controls { lines.append(VideoBlock.Key.controls.rawValue) } if autoplay { lines.append(VideoBlock.Key.autoplay.rawValue) } diff --git a/CHDataManagement/Views/Pages/PageContentResultsView.swift b/CHDataManagement/Views/Pages/PageContentResultsView.swift index d5bfa0a..693d7fa 100644 --- a/CHDataManagement/Views/Pages/PageContentResultsView.swift +++ b/CHDataManagement/Views/Pages/PageContentResultsView.swift @@ -35,7 +35,7 @@ struct PageContentResultsView: View { TextWithSymbol( symbol: $0.type.category.symbol, color: .blue, - text: $0.id) + text: $0.identifier) } + results.missingFiles.keys.map { TextWithSymbol( diff --git a/CHDataManagement/Views/Pages/PageContentView.swift b/CHDataManagement/Views/Pages/PageContentView.swift index 9bda8e7..33e2980 100644 --- a/CHDataManagement/Views/Pages/PageContentView.swift +++ b/CHDataManagement/Views/Pages/PageContentView.swift @@ -32,7 +32,7 @@ struct PageContentView: View { if page.isExternalUrl { VStack { PageTitleView(page: page.localized(in: language)) - .id(page.id + language.rawValue) + .id(page.identifier + language.rawValue) Spacer() Text("No content available for external page") .font(.title) @@ -42,10 +42,10 @@ struct PageContentView: View { } else { VStack(alignment: .leading) { PageTitleView(page: page.localized(in: language)) - .id(page.id + language.rawValue) + .id(page.identifier + language.rawValue) TagDisplayView(tags: $page.tags) LocalizedPageContentView(page: page, language: language) - .id(page.id + language.rawValue) + .id(page.identifier + language.rawValue) } .padding() } diff --git a/CHDataManagement/Views/Pages/PageDetailView.swift b/CHDataManagement/Views/Pages/PageDetailView.swift index 6f14573..c567337 100644 --- a/CHDataManagement/Views/Pages/PageDetailView.swift +++ b/CHDataManagement/Views/Pages/PageDetailView.swift @@ -30,7 +30,7 @@ struct PageDetailView: View { title: "Page", text: "A page contains longer content") IdPropertyView( - id: $page.id, + id: $page.identifier, footer: "The page id is used to link to it internally.", validation: page.isValid, update: { page.update(id: $0) }) @@ -75,7 +75,7 @@ struct PageDetailView: View { isExternalPage: page.isExternalUrl, page: page.localized(in: language), transferImage: transferImage) - .id(page.id + language.rawValue) + .id(page.identifier + language.rawValue) ColoredButton(delete: deletePage) } .padding() @@ -83,8 +83,8 @@ struct PageDetailView: View { } private func deletePage() { - guard content.storage.delete(page: page.id) else { - print("Page '\(page.id)': Failed to delete file in content folder") + guard content.storage.delete(page: page.identifier) else { + print("Page '\(page.identifier)': Failed to delete file in content folder") return } content.remove(page) diff --git a/CHDataManagement/Views/Posts/PagePickerView.swift b/CHDataManagement/Views/Posts/PagePickerView.swift index d25e8e3..39ed513 100644 --- a/CHDataManagement/Views/Posts/PagePickerView.swift +++ b/CHDataManagement/Views/Posts/PagePickerView.swift @@ -27,7 +27,7 @@ struct PagePickerView: View { Text("Select a page to link to") List(content.pages, selection: $newSelection) { page in let loc = page.localized(in: language) - Text("\(loc.title) (\(page.id))") + Text("\(loc.title) (\(page.identifier))") .tag(page) } .frame(minHeight: 300) diff --git a/CHDataManagement/Views/Posts/PostDetailView.swift b/CHDataManagement/Views/Posts/PostDetailView.swift index bf04ba8..f55574b 100644 --- a/CHDataManagement/Views/Posts/PostDetailView.swift +++ b/CHDataManagement/Views/Posts/PostDetailView.swift @@ -43,7 +43,7 @@ struct PostDetailView: View { } IdPropertyView( - id: $post.id, + id: $post.identifier, footer: "The id is used to link to post and store them", validation: post.isValid, update: { post.update(id: $0) }) @@ -99,8 +99,8 @@ struct PostDetailView: View { } private func deletePost() { - guard content.storage.delete(post: post.id) else { - print("Post '\(post.id)': Failed to delete file in content folder") + guard content.storage.delete(post: post.identifier) else { + print("Post '\(post.identifier)': Failed to delete file in content folder") return } content.remove(post) diff --git a/CHDataManagement/Views/Posts/PostImageView.swift b/CHDataManagement/Views/Posts/PostImageView.swift index ed60e3a..0cb7c3e 100644 --- a/CHDataManagement/Views/Posts/PostImageView.swift +++ b/CHDataManagement/Views/Posts/PostImageView.swift @@ -18,7 +18,7 @@ struct PostImageView: View { .resizable() .aspectRatio(contentMode: .fit) .frame(height: 100) - Text(image.id) + Text(image.identifier) .font(.title) Text("Failed to load image") .font(.body) @@ -32,7 +32,7 @@ struct PostImageView: View { .resizable() .aspectRatio(contentMode: .fit) .frame(height: 100) - Text(image.id) + Text(image.identifier) .font(.title) Button("Generate preview") { generateVideoPreview(image) @@ -48,7 +48,7 @@ struct PostImageView: View { .resizable() .aspectRatio(contentMode: .fit) .frame(height: 100) - Text(image.id) + Text(image.identifier) .font(.title) Text("Invalid media type") .font(.body) diff --git a/CHDataManagement/Views/Posts/PostListView.swift b/CHDataManagement/Views/Posts/PostListView.swift index 709c54c..c3ef79b 100644 --- a/CHDataManagement/Views/Posts/PostListView.swift +++ b/CHDataManagement/Views/Posts/PostListView.swift @@ -10,7 +10,7 @@ private struct PostListItem: View { var body: some View { HStack { - LocalizedPostListItem(id: post.id, post: post.localized(in: language)) + LocalizedPostListItem(id: post.identifier, post: post.localized(in: language)) if post.isDraft { TextIndicator(text: "Draft", background: .yellow) } else { diff --git a/CHDataManagement/Views/Tags/TagDetailView.swift b/CHDataManagement/Views/Tags/TagDetailView.swift index 4521978..e9b663e 100644 --- a/CHDataManagement/Views/Tags/TagDetailView.swift +++ b/CHDataManagement/Views/Tags/TagDetailView.swift @@ -32,7 +32,7 @@ struct TagDetailView: View { footer: "Indicate if the tag should appear in the tag list of posts and pages. If the tag is not visible, then it can still be used as a filter.") IdPropertyView( - id: $tag.id, + id: $tag.identifier, title: "Tag id", footer: "The unique id of the tag for references", validation: tag.isValid) { @@ -42,7 +42,7 @@ struct TagDetailView: View { LocalizedTagDetailView( tag: tag.localized(in: language), transferImage: transferImage) - .id(tag.id + language.rawValue) + .id(tag.identifier + language.rawValue) ColoredButton(delete: deleteTag) } .padding() @@ -50,8 +50,8 @@ struct TagDetailView: View { } private func deleteTag() { - guard content.storage.delete(tag: tag.id) else { - print("Tag '\(tag.id)': Failed to delete file in content folder") + guard content.storage.delete(tag: tag.identifier) else { + print("Tag '\(tag.identifier)': Failed to delete file in content folder") return } content.remove(tag)