import Foundation final class Page: Item, DateItem, LocalizedItem { override var itemType: ItemType { .page } /** The external link this page points to. If this value is not `nil`, then the page has no content and many other features are disabled. */ @Published var externalLink: String? @Published var isDraft: Bool @Published var createdDate: Date @Published var hideDate: Bool @Published var startDate: Date @Published var hasEndDate: Bool @Published var potentialEndDate: Date @Published var german: LocalizedPage @Published var english: LocalizedPage @Published var tags: [Tag] var savedData: Data? init(content: Content, id: String, externalLink: String?, isDraft: Bool, createdDate: Date, hideDate: Bool, startDate: Date, endDate: Date?, german: LocalizedPage, english: LocalizedPage, tags: [Tag]) { self.externalLink = externalLink self.isDraft = isDraft self.createdDate = createdDate self.hideDate = hideDate self.startDate = startDate self.hasEndDate = endDate != nil self.potentialEndDate = endDate ?? startDate self.german = german self.english = english self.tags = tags super.init(content: content, id: id) } func isValid(id: String) -> Bool { !id.isEmpty && content.isValidIdForTagOrPageOrPost(id) && content.isNewIdForPage(id) } @discardableResult func update(id newId: String) -> Bool { guard content.storage.move(page: id, to: newId) else { print("Failed to move files of page \(id)") return false } id = newId return true } var isExternalUrl: Bool { externalLink != nil } // MARK: Tags /** Check if a tag is associated with this page */ func contains(_ tag: Tag) -> Bool { tags.contains(tag) } func toggle(_ tag: Tag) { guard let index = tags.firstIndex(of: tag) else { tags.append(tag) return } tags.remove(at: index) } func remove(_ tag: Tag) { tags.remove(tag) } func associate(_ tag: Tag) { guard !tags.contains(tag) else { return } tags.append(tag) } // MARK: Paths override func absoluteUrl(in language: ContentLanguage) -> String { if let url = externalLink { return url } // TODO: Record link to trace connections between pages return makeCleanAbsolutePath(internalPath(for: language)) } override func title(in language: ContentLanguage) -> String { localized(in: language).title } func filePathRelativeToOutputFolder(for language: ContentLanguage) -> String { makeCleanRelativePath(internalPath(for: language)) } private func internalPath(for language: ContentLanguage) -> String { content.settings.paths.pagesOutputFolderPath + "/" + localized(in: language).urlString } override var itemReference: ItemReference { .page(self) } func contains(urlComponent: String) -> Bool { english.urlString == urlComponent || german.urlString == urlComponent } func pageContent(in language: ContentLanguage) -> String? { content.storage.pageContent(for: id, language: language) } func removeContent(in language: ContentLanguage) -> Bool { guard content.storage.remove(pageContent: id, in: language) else { return false } localized(in: language).hasContent = false return true } 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) { english.linkPreview.remove(file) german.linkPreview.remove(file) } } // MARK: Storage extension Page: StorageItem { convenience init(context: LoadingContext, id: String, data: Data) { self.init( content: context.content, id: id, externalLink: data.externalLink, isDraft: data.isDraft, createdDate: data.createdDate, hideDate: data.hideDate ?? false, startDate: data.startDate, endDate: data.endDate, german: .init(context: context, data: data.german), english: .init(context: context, data: data.english), tags: data.tags.compactMap(context.tag)) savedData = data } /// The structure to store the metadata of a page on disk struct Data: Codable, Equatable { let isDraft: Bool let externalLink: String? let tags: [String] let hideDate: Bool? let createdDate: Date let startDate: Date let endDate: Date? let german: LocalizedPage.Data let english: LocalizedPage.Data } var data: Data { .init( isDraft: isDraft, externalLink: externalLink, tags: tags.map { $0.id }, hideDate: hideDate ? true : nil, createdDate: createdDate, startDate: startDate, endDate: endDate, german: german.data, english: english.data) } func saveToDisk(_ data: Data) -> Bool { content.storage.save(page: data, for: id) } }