import Foundation final class Post: Item, DateItem, LocalizedItem { override var itemType: ItemType { .post } @Published var isDraft: Bool @Published var createdDate: Date @Published var startDate: Date @Published var hasEndDate: Bool @Published var potentialEndDate: Date /** The tags associated with the post This list is only used if ``linkedPage`` is `nil`, otherwise the tag list of the linked page is used. */ @Published var tags: [Tag] @Published var german: LocalizedPost @Published var english: LocalizedPost /// The page linked to by this post @Published var linkedPage: Page? init(content: Content, id: String, isDraft: Bool, createdDate: Date, startDate: Date, endDate: Date?, tags: [Tag], german: LocalizedPost, english: LocalizedPost, linkedPage: Page? = nil) { self.isDraft = isDraft self.createdDate = createdDate self.startDate = startDate self.hasEndDate = endDate != nil self.potentialEndDate = endDate ?? startDate self.tags = tags self.german = german self.english = english self.linkedPage = linkedPage super.init(content: content, id: id) } // MARK: Storage var savedData: Data? // MARK: Tags func usedTags() -> [Tag] { linkedPage?.tags ?? tags } /** Check if a tag is associated with this post. If the post is linked to a page, then the tags of the page are checked. */ func contains(_ tag: Tag) -> Bool { guard let linkedPage else { return tags.contains(tag) } return linkedPage.tags.contains(tag) } 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 { tags.append(tag) return } tags.remove(at: index) } func remove(_ tag: Tag) { if let linkedPage { linkedPage.remove(tag) return } tags.remove(tag) } func associate(_ tag: Tag) { if let linkedPage { linkedPage.associate(tag) return } guard !tags.contains(tag) else { return } tags.append(tag) } /** A title for the UI, not the generation. */ override func title(in language: ContentLanguage) -> String { localized(in: language).title ?? id } func contains(_ string: String) -> Bool { id.contains(string) || german.contains(string) || english.contains(string) } func isValid(id: String) -> Bool { !id.isEmpty && content.isValidIdForTagOrPageOrPost(id) && content.isNewIdForPost(id) } @discardableResult func update(id newId: String) -> Bool { guard content.storage.move(post: id, to: newId) else { print("Failed to move file of post \(id)") return false } id = newId return true } func remove(_ file: FileResource) { english.remove(file) german.remove(file) } func makePage() -> Page { var id = self.id var number = 2 while !content.isNewIdForPage(id) { id += "\(self.id)-\(number)" number += 1 } // Move tags to page let tags = self.tags self.tags = [] let german = english.makePage(urlString: id + "-de") let english = english.makePage(urlString: id + "-en") return Page( content: content, id: id, externalLink: nil, isDraft: true, createdDate: .now, hideDate: false, startDate: startDate, endDate: endDate, german: german, english: english, tags: tags) } } extension Post: StorageItem { convenience init(context: LoadingContext, id: String, data: Data) { self.init( content: context.content, id: id, isDraft: data.isDraft, createdDate: data.createdDate, startDate: data.startDate, endDate: data.endDate, tags: data.tags.compactMap(context.tag), german: .init(context: context, data: data.german), english: .init(context: context, data: data.english), linkedPage: data.linkedPageId.map(context.page)) savedData = data } struct Data: Codable, Equatable { let isDraft: Bool let createdDate: Date let startDate: Date let endDate: Date? let tags: [String] let german: LocalizedPost.Data let english: LocalizedPost.Data let linkedPageId: String? } var data: Data { .init( isDraft: isDraft, createdDate: createdDate, startDate: startDate, endDate: endDate, tags: tags.map { $0.id }, german: german.data, english: english.data, linkedPageId: linkedPage?.id) } func saveToDisk(_ data: Data) -> Bool { content.storage.save(post: data, for: id) } }