Fix id of Items, saving
This commit is contained in:
@ -1767,7 +1767,7 @@
|
|||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||||
MARKETING_VERSION = 1.2;
|
MARKETING_VERSION = 1.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.christophhagen.CHDataManagement;
|
PRODUCT_BUNDLE_IDENTIFIER = de.christophhagen.CHDataManagement;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
@ -1806,7 +1806,7 @@
|
|||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||||
MARKETING_VERSION = 1.2;
|
MARKETING_VERSION = 1.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.christophhagen.CHDataManagement;
|
PRODUCT_BUNDLE_IDENTIFIER = de.christophhagen.CHDataManagement;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
|
@ -37,7 +37,7 @@ struct GalleryBlock: BlockLineProcessor {
|
|||||||
$0.imageSet(width: imageWidth, height: imageWidth, language: language)
|
$0.imageSet(width: imageWidth, height: imageWidth, language: language)
|
||||||
}
|
}
|
||||||
imageSets.forEach(results.require)
|
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)
|
let gallery = ImageGallery(id: id, images: imageSets, standalone: true)
|
||||||
results.require(footer: gallery.standaloneFooter)
|
results.require(footer: gallery.standaloneFooter)
|
||||||
results.require(headers: .swiperJs, .swiperCss)
|
results.require(headers: .swiperJs, .swiperCss)
|
||||||
|
@ -50,7 +50,7 @@ struct PhoneScreensBlock: OrderedKeyBlockProcessor {
|
|||||||
}
|
}
|
||||||
if key == .tall {
|
if key == .tall {
|
||||||
if tall != nil {
|
if tall != nil {
|
||||||
print("Another tall image: \(file.id)")
|
print("Another tall image: \(file.identifier)")
|
||||||
invalid(markdown)
|
invalid(markdown)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ struct PhoneScreensBlock: OrderedKeyBlockProcessor {
|
|||||||
}
|
}
|
||||||
// key == .wide
|
// key == .wide
|
||||||
if wide != nil {
|
if wide != nil {
|
||||||
print("Another wide image: \(file.id)")
|
print("Another wide image: \(file.identifier)")
|
||||||
invalid(markdown)
|
invalid(markdown)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,7 @@ struct HtmlCommand: CommandProcessor {
|
|||||||
results.missing(file: fileId, source: "HTML: \(source)")
|
results.missing(file: fileId, source: "HTML: \(source)")
|
||||||
return
|
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) {
|
private func findFileWith(relativePath: String, type: FileType, source: String) {
|
||||||
|
@ -58,19 +58,19 @@ final class ImageGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
guard let data = version.image.dataContent() else {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let originalImage = NSImage(data: data) else {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let representation = create(image: originalImage, width: CGFloat(version.maximumWidth), height: CGFloat(version.maximumHeight))
|
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 {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ final class ImageGenerator {
|
|||||||
process.waitUntilExit()
|
process.waitUntilExit()
|
||||||
|
|
||||||
if process.terminationStatus != 0 {
|
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 outputData = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||||
let outputString = String(data: outputData, encoding: .utf8) ?? ""
|
let outputString = String(data: outputData, encoding: .utf8) ?? ""
|
||||||
print(outputString)
|
print(outputString)
|
||||||
|
@ -56,14 +56,14 @@ struct ImageVersion {
|
|||||||
extension ImageVersion: Identifiable {
|
extension ImageVersion: Identifiable {
|
||||||
|
|
||||||
var id: String {
|
var id: String {
|
||||||
image.id + "-" + versionId
|
image.identifier + "-" + versionId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ImageVersion: Equatable {
|
extension ImageVersion: Equatable {
|
||||||
|
|
||||||
static func == (lhs: ImageVersion, rhs: ImageVersion) -> Bool {
|
static func == (lhs: ImageVersion, rhs: ImageVersion) -> Bool {
|
||||||
lhs.image.id == rhs.image.id &&
|
lhs.image.identifier == rhs.image.identifier &&
|
||||||
lhs.maximumWidth == rhs.maximumWidth &&
|
lhs.maximumWidth == rhs.maximumWidth &&
|
||||||
lhs.maximumHeight == rhs.maximumHeight &&
|
lhs.maximumHeight == rhs.maximumHeight &&
|
||||||
lhs.type == rhs.type
|
lhs.type == rhs.type
|
||||||
@ -73,7 +73,7 @@ extension ImageVersion: Equatable {
|
|||||||
extension ImageVersion: Hashable {
|
extension ImageVersion: Hashable {
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(image.id)
|
hasher.combine(image.identifier)
|
||||||
hasher.combine(maximumWidth)
|
hasher.combine(maximumWidth)
|
||||||
hasher.combine(maximumHeight)
|
hasher.combine(maximumHeight)
|
||||||
hasher.combine(type)
|
hasher.combine(type)
|
||||||
|
@ -33,7 +33,7 @@ final class PageGenerator {
|
|||||||
language: language, results: results)
|
language: language, results: results)
|
||||||
|
|
||||||
let rawPageContent: String
|
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
|
rawPageContent = existing
|
||||||
} else {
|
} else {
|
||||||
rawPageContent = makeEmptyPageContent(in: language)
|
rawPageContent = makeEmptyPageContent(in: language)
|
||||||
|
@ -44,7 +44,7 @@ struct PostContentGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var postDescription: String {
|
private var postDescription: String {
|
||||||
"content of post \(post.id) (\(language.shortText))"
|
"content of post \(post.identifier) (\(language.shortText))"
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleLink(
|
private func handleLink(
|
||||||
|
@ -95,7 +95,7 @@ final class PostListPageGenerator {
|
|||||||
post: post).generate()
|
post: post).generate()
|
||||||
|
|
||||||
return FeedEntryData(
|
return FeedEntryData(
|
||||||
entryId: post.id,
|
entryId: post.identifier,
|
||||||
title: localized.title,
|
title: localized.title,
|
||||||
textAboveTitle: post.dateText(in: language),
|
textAboveTitle: post.dateText(in: language),
|
||||||
link: linkUrl,
|
link: linkUrl,
|
||||||
|
@ -161,7 +161,7 @@ final class GenerationResults: ObservableObject {
|
|||||||
update { self.unsavedOutputFiles = unsavedOutputFiles }
|
update { self.unsavedOutputFiles = unsavedOutputFiles }
|
||||||
let emptyPages = cache.values.filter { $0.pageIsEmpty }.map { $0.itemId }.compactMap { id -> LocalizedPageId? in
|
let emptyPages = cache.values.filter { $0.pageIsEmpty }.map { $0.itemId }.compactMap { id -> LocalizedPageId? in
|
||||||
guard case .page(let page) = id.itemType else { return nil }
|
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()
|
}.asSet()
|
||||||
update { self.emptyPages = emptyPages }
|
update { self.emptyPages = emptyPages }
|
||||||
let redirects = cache.values.compactMap { $0.redirect }.reduce(into: [:]) { $0[$1.originalUrl] = $1.newUrl }
|
let redirects = cache.values.compactMap { $0.redirect }.reduce(into: [:]) { $0[$1.originalUrl] = $1.newUrl }
|
||||||
|
@ -11,7 +11,7 @@ extension ImageToGenerate: Hashable {
|
|||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(size)
|
hasher.combine(size)
|
||||||
hasher.combine(image.id)
|
hasher.combine(image.identifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,7 +284,7 @@ final class PageGenerationResults: ObservableObject {
|
|||||||
func markPageAsEmpty() {
|
func markPageAsEmpty() {
|
||||||
guard case .page(let page) = itemId.itemType else { return }
|
guard case .page(let page) = itemId.itemType else { return }
|
||||||
onMain { self.pageIsEmpty = true }
|
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) {
|
func redirect(from originalUrl: String, to newUrl: String) {
|
||||||
|
@ -2,7 +2,7 @@ import SwiftUI
|
|||||||
|
|
||||||
protocol MainContentView: View {
|
protocol MainContentView: View {
|
||||||
|
|
||||||
associatedtype Item: Identifiable
|
associatedtype Item
|
||||||
|
|
||||||
init(item: Item)
|
init(item: Item)
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ struct SelectedDetailView<Contained>: View where Contained: MainContentView {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
if let item = selected {
|
if let item = selected {
|
||||||
Contained(item: item)
|
Contained(item: item)
|
||||||
.id(item.id)
|
//.id(item.id)
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,6 @@ extension FileResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var mock: 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" })!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,13 @@ extension Page {
|
|||||||
lastModified: nil,
|
lastModified: nil,
|
||||||
originalUrl: "projects/electronics/my-first-project/en.html"),
|
originalUrl: "projects/electronics/my-first-project/en.html"),
|
||||||
tags: [
|
tags: [
|
||||||
content.tags.first(where: { $0.id == "electronics" })!
|
content.tags.first(where: { $0.identifier == "electronics" })!
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
static var empty: Page {
|
static var empty: Page {
|
||||||
Content.mock.pages.first(where: { $0.id == "my-id" })!
|
Content.mock.pages.first(where: { $0.identifier == "my-id" })!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,9 +27,9 @@ extension Post {
|
|||||||
startDate: .now,
|
startDate: .now,
|
||||||
endDate: nil,
|
endDate: nil,
|
||||||
tags: [
|
tags: [
|
||||||
content.tags.first(where: { $0.id == "nature" })!,
|
content.tags.first(where: { $0.identifier == "nature" })!,
|
||||||
content.tags.first(where: { $0.id == "sports" })!,
|
content.tags.first(where: { $0.identifier == "sports" })!,
|
||||||
content.tags.first(where: { $0.id == "hiking" })!
|
content.tags.first(where: { $0.identifier == "hiking" })!
|
||||||
],
|
],
|
||||||
german: .init(
|
german: .init(
|
||||||
content: content,
|
content: content,
|
||||||
@ -47,44 +47,44 @@ extension Post {
|
|||||||
createdDate: .now,
|
createdDate: .now,
|
||||||
startDate: .now.addingTimeInterval(-86400), endDate: .now,
|
startDate: .now.addingTimeInterval(-86400), endDate: .now,
|
||||||
tags: [
|
tags: [
|
||||||
content.tags.first(where: { $0.id == "nature" })!,
|
content.tags.first(where: { $0.identifier == "nature" })!,
|
||||||
content.tags.first(where: { $0.id == "sports" })!,
|
content.tags.first(where: { $0.identifier == "sports" })!,
|
||||||
content.tags.first(where: { $0.id == "hiking" })!,
|
content.tags.first(where: { $0.identifier == "hiking" })!,
|
||||||
content.tags.first(where: { $0.id == "mountains" })!
|
content.tags.first(where: { $0.identifier == "mountains" })!
|
||||||
],
|
],
|
||||||
german: LocalizedPost(
|
german: LocalizedPost(
|
||||||
content: content,
|
content: content,
|
||||||
title: "Eine lange Wanderung",
|
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.",
|
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: [
|
images: [
|
||||||
content.files.first(where: { $0.id == "image1" })!,
|
content.files.first(where: { $0.identifier == "image1" })!,
|
||||||
content.files.first(where: { $0.id == "image2" })!,
|
content.files.first(where: { $0.identifier == "image2" })!,
|
||||||
content.files.first(where: { $0.id == "image3" })!,
|
content.files.first(where: { $0.identifier == "image3" })!,
|
||||||
content.files.first(where: { $0.id == "image4" })!
|
content.files.first(where: { $0.identifier == "image4" })!
|
||||||
]),
|
]),
|
||||||
english: LocalizedPost(
|
english: LocalizedPost(
|
||||||
content: content,
|
content: content,
|
||||||
title: "A longer hike",
|
title: "A longer hike",
|
||||||
text: "Very nice and solitary hike from Oberau to Krottenkopf. Challenging and rewarding, due to the length and height.",
|
text: "Very nice and solitary hike from Oberau to Krottenkopf. Challenging and rewarding, due to the length and height.",
|
||||||
images: [
|
images: [
|
||||||
content.files.first(where: { $0.id == "image1" })!,
|
content.files.first(where: { $0.identifier == "image1" })!,
|
||||||
content.files.first(where: { $0.id == "image2" })!,
|
content.files.first(where: { $0.identifier == "image2" })!,
|
||||||
content.files.first(where: { $0.id == "image3" })!,
|
content.files.first(where: { $0.identifier == "image3" })!,
|
||||||
content.files.first(where: { $0.id == "image4" })!
|
content.files.first(where: { $0.identifier == "image4" })!
|
||||||
]))
|
]))
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
static var empty: Post {
|
static var empty: Post {
|
||||||
Content.mock.posts.first(where: { $0.id == "empty" })!
|
Content.mock.posts.first(where: { $0.identifier == "empty" })!
|
||||||
}
|
}
|
||||||
|
|
||||||
static var hike: Post {
|
static var hike: Post {
|
||||||
Content.mock.posts.first(where: { $0.id == "hike" })!
|
Content.mock.posts.first(where: { $0.identifier == "hike" })!
|
||||||
}
|
}
|
||||||
|
|
||||||
static var hike2: Post {
|
static var hike2: Post {
|
||||||
Content.mock.posts.first(where: { $0.id == "hike2" })!
|
Content.mock.posts.first(where: { $0.identifier == "hike2" })!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ extension Tag {
|
|||||||
urlComponent: "elektronik",
|
urlComponent: "elektronik",
|
||||||
name: "Elektronik",
|
name: "Elektronik",
|
||||||
linkPreview: .init(description: "Eine Beschreibung des Tags",
|
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"
|
originalUrl: "projects/electronics"
|
||||||
),
|
),
|
||||||
english: .init(
|
english: .init(
|
||||||
@ -22,7 +22,7 @@ extension Tag {
|
|||||||
name: "Electronics",
|
name: "Electronics",
|
||||||
linkPreview: .init(
|
linkPreview: .init(
|
||||||
description: "Some description of the tag",
|
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")
|
originalUrl: "projects/electronics")
|
||||||
),
|
),
|
||||||
Tag(
|
Tag(
|
||||||
@ -53,23 +53,23 @@ extension Tag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var electronics: 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 {
|
static var nature: Tag {
|
||||||
Content.mock.tags.first(where: { $0.id == "nature" })!
|
Content.mock.tags.first(where: { $0.identifier == "nature" })!
|
||||||
}
|
}
|
||||||
|
|
||||||
static var sports: Tag {
|
static var sports: Tag {
|
||||||
Content.mock.tags.first(where: { $0.id == "sports" })!
|
Content.mock.tags.first(where: { $0.identifier == "sports" })!
|
||||||
}
|
}
|
||||||
|
|
||||||
static var hiking: Tag {
|
static var hiking: Tag {
|
||||||
Content.mock.tags.first(where: { $0.id == "hiking" })!
|
Content.mock.tags.first(where: { $0.identifier == "hiking" })!
|
||||||
}
|
}
|
||||||
|
|
||||||
static var mountains: Tag {
|
static var mountains: Tag {
|
||||||
Content.mock.tags.first(where: { $0.id == "mountains" })!
|
Content.mock.tags.first(where: { $0.identifier == "mountains" })!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ extension Content {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let path = file.absoluteUrl
|
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)
|
results.general.unsavedOutput(path, source: .general)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,23 +147,23 @@ extension Content {
|
|||||||
// MARK: Find items by id
|
// MARK: Find items by id
|
||||||
|
|
||||||
func page(_ pageId: String) -> Page? {
|
func page(_ pageId: String) -> Page? {
|
||||||
pages.first { $0.id == pageId }
|
pages.first { $0.identifier == pageId }
|
||||||
}
|
}
|
||||||
|
|
||||||
func image(_ imageId: String) -> FileResource? {
|
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? {
|
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? {
|
func file(_ fileId: String) -> FileResource? {
|
||||||
files.first { $0.id == fileId }
|
files.first { $0.identifier == fileId }
|
||||||
}
|
}
|
||||||
|
|
||||||
func tag(_ tagId: String) -> Tag? {
|
func tag(_ tagId: String) -> Tag? {
|
||||||
tags.first { $0.id == tagId }
|
tags.first { $0.identifier == tagId }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Generation input
|
// MARK: Generation input
|
||||||
@ -322,12 +322,12 @@ extension Content {
|
|||||||
let pageUrl = settings.general.url + relativePageUrl
|
let pageUrl = settings.general.url + relativePageUrl
|
||||||
|
|
||||||
guard let content = pageGenerator.generate(page: page, language: language, results: results, pageUrl: pageUrl) else {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard storage.write(content, to: filePath) else {
|
guard storage.write(content, to: filePath) else {
|
||||||
print("Failed to save page \(page.id)")
|
print("Failed to save page \(page.identifier)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,16 +83,16 @@ extension Content {
|
|||||||
|
|
||||||
func removeUnlinkedFiles() -> Bool {
|
func removeUnlinkedFiles() -> Bool {
|
||||||
var success = true
|
var success = true
|
||||||
if !storage.deletePostFiles(notIn: posts.map { $0.id }) {
|
if !storage.deletePostFiles(notIn: posts.map { $0.identifier }) {
|
||||||
success = false
|
success = false
|
||||||
}
|
}
|
||||||
if !storage.deletePageFiles(notIn: pages.map { $0.id }) {
|
if !storage.deletePageFiles(notIn: pages.map { $0.identifier }) {
|
||||||
success = false
|
success = false
|
||||||
}
|
}
|
||||||
if !storage.deleteTagFiles(notIn: tags.map { $0.id }) {
|
if !storage.deleteTagFiles(notIn: tags.map { $0.identifier }) {
|
||||||
success = false
|
success = false
|
||||||
}
|
}
|
||||||
if !storage.deleteFileResources(notIn: files.map { $0.id }) {
|
if !storage.deleteFileResources(notIn: files.map { $0.identifier }) {
|
||||||
success = false
|
success = false
|
||||||
}
|
}
|
||||||
return success
|
return success
|
||||||
|
@ -7,19 +7,19 @@ extension Content {
|
|||||||
private static let disallowedCharactersInFileIds = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-.")).inverted
|
private static let disallowedCharactersInFileIds = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-.")).inverted
|
||||||
|
|
||||||
func isNewIdForTag(_ id: String) -> Bool {
|
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 {
|
func isNewIdForPage(_ id: String) -> Bool {
|
||||||
!pages.contains { $0.id == id }
|
!pages.contains { $0.identifier == id }
|
||||||
}
|
}
|
||||||
|
|
||||||
func isNewIdForPost(_ id: String) -> Bool {
|
func isNewIdForPost(_ id: String) -> Bool {
|
||||||
!posts.contains { $0.id == id }
|
!posts.contains { $0.identifier == id }
|
||||||
}
|
}
|
||||||
|
|
||||||
func isNewIdForFile(_ id: String) -> Bool {
|
func isNewIdForFile(_ id: String) -> Bool {
|
||||||
!files.contains { $0.id == id }
|
!files.contains { $0.identifier == id }
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidIdForTagOrPageOrPost(_ id: String) -> Bool {
|
func isValidIdForTagOrPageOrPost(_ id: String) -> Bool {
|
||||||
|
@ -2,7 +2,7 @@ import Foundation
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
final class Content: ObservableObject {
|
final class Content: ChangeObservableItem {
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var storage: Storage
|
var storage: Storage
|
||||||
@ -47,6 +47,8 @@ final class Content: ObservableObject {
|
|||||||
|
|
||||||
var errorCallback: ((StorageError) -> Void)?
|
var errorCallback: ((StorageError) -> Void)?
|
||||||
|
|
||||||
|
var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
/// A cache of file sizes
|
/// A cache of file sizes
|
||||||
private var fileSizes: [String: Int] = [:]
|
private var fileSizes: [String: Int] = [:]
|
||||||
|
|
||||||
@ -200,9 +202,9 @@ final class Content: ObservableObject {
|
|||||||
for file in self.files {
|
for file in self.files {
|
||||||
guard file.type.isVideo else { continue }
|
guard file.type.isVideo else { continue }
|
||||||
guard !file.isExternallyStored else { continue }
|
guard !file.isExternallyStored else { continue }
|
||||||
guard !storage.hasVideoThumbnail(for: file.id) else { continue }
|
guard !storage.hasVideoThumbnail(for: file.identifier) else { continue }
|
||||||
if await imageGenerator.createVideoThumbnail(for: file.id) {
|
if await imageGenerator.createVideoThumbnail(for: file.identifier) {
|
||||||
print("Generated thumbnail for \(file.id)")
|
print("Generated thumbnail for \(file.identifier)")
|
||||||
file.didChange()
|
file.didChange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -229,6 +231,10 @@ final class Content: ObservableObject {
|
|||||||
self.lastSave = .now
|
self.lastSave = .now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func needsSaving() {
|
||||||
|
needsSave()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: File sizes
|
// MARK: File sizes
|
||||||
|
|
||||||
func size(of file: String) -> Int? {
|
func size(of file: String) -> Int? {
|
||||||
|
@ -2,7 +2,7 @@ import Foundation
|
|||||||
|
|
||||||
protocol DateItem {
|
protocol DateItem {
|
||||||
|
|
||||||
var id: String { get }
|
var identifier: String { get }
|
||||||
|
|
||||||
var startDate: Date { get }
|
var startDate: Date { get }
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ extension Sequence where Element: DateItem {
|
|||||||
func sortedByStartDateAndId() -> [Element] {
|
func sortedByStartDateAndId() -> [Element] {
|
||||||
sorted { (lhs, rhs) -> Bool in
|
sorted { (lhs, rhs) -> Bool in
|
||||||
if lhs.startDate == rhs.startDate {
|
if lhs.startDate == rhs.startDate {
|
||||||
return lhs.id < rhs.id
|
return lhs.identifier < rhs.identifier
|
||||||
}
|
}
|
||||||
return lhs.startDate > rhs.startDate
|
return lhs.startDate > rhs.startDate
|
||||||
}
|
}
|
||||||
|
@ -49,18 +49,18 @@ final class FileResource: Item, LocalizedItem {
|
|||||||
|
|
||||||
/// The dimensions of the image
|
/// The dimensions of the image
|
||||||
var imageDimensions: CGSize? {
|
var imageDimensions: CGSize? {
|
||||||
get { content.dimensions(of: id) }
|
get { content.dimensions(of: identifier) }
|
||||||
set {
|
set {
|
||||||
content.cache(dimensions: newValue, of: id)
|
content.cache(dimensions: newValue, of: identifier)
|
||||||
didChange(save: false)
|
didChange(save: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The size of the file in bytes
|
/// The size of the file in bytes
|
||||||
var fileSize: Int? {
|
var fileSize: Int? {
|
||||||
get { content.size(of: id) }
|
get { content.size(of: identifier) }
|
||||||
set {
|
set {
|
||||||
content.cache(size: newValue, of: id)
|
content.cache(size: newValue, of: identifier)
|
||||||
didChange(save: false)
|
didChange(save: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,11 +114,11 @@ final class FileResource: Item, LocalizedItem {
|
|||||||
// MARK: Text
|
// MARK: Text
|
||||||
|
|
||||||
func textContent() -> String {
|
func textContent() -> String {
|
||||||
content.storage.fileContent(for: id) ?? ""
|
content.storage.fileContent(for: identifier) ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func save(textContent: String) -> Bool {
|
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
|
return false
|
||||||
}
|
}
|
||||||
modifiedDate = .now
|
modifiedDate = .now
|
||||||
@ -126,7 +126,7 @@ final class FileResource: Item, LocalizedItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dataContent() -> Foundation.Data? {
|
func dataContent() -> Foundation.Data? {
|
||||||
content.storage.fileData(for: id)
|
content.storage.fileData(for: identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Images
|
// MARK: Images
|
||||||
@ -165,7 +165,7 @@ final class FileResource: Item, LocalizedItem {
|
|||||||
}
|
}
|
||||||
update(fileSize: displayImageData.count)
|
update(fileSize: displayImageData.count)
|
||||||
guard let loadedImage = NSImage(data: displayImageData) else {
|
guard let loadedImage = NSImage(data: displayImageData) else {
|
||||||
print("Failed to create image \(id)")
|
print("Failed to create image \(identifier)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
update(imageDimensions: loadedImage.size)
|
update(imageDimensions: loadedImage.size)
|
||||||
@ -191,14 +191,14 @@ final class FileResource: Item, LocalizedItem {
|
|||||||
|
|
||||||
private var displayImageData: Foundation.Data? {
|
private var displayImageData: Foundation.Data? {
|
||||||
if type.isImage {
|
if type.isImage {
|
||||||
guard let data = content.storage.fileData(for: id) else {
|
guard let data = content.storage.fileData(for: identifier) else {
|
||||||
print("Failed to load data for image \(id)")
|
print("Failed to load data for image \(identifier)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
if type.isVideo {
|
if type.isVideo {
|
||||||
return content.storage.getVideoThumbnail(for: id)
|
return content.storage.getVideoThumbnail(for: identifier)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -234,7 +234,7 @@ final class FileResource: Item, LocalizedItem {
|
|||||||
|
|
||||||
func determineFileSize() {
|
func determineFileSize() {
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
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)
|
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)
|
/// The path to the output folder where image versions are stored (no leading slash)
|
||||||
var outputImageFolder: String {
|
var outputImageFolder: String {
|
||||||
"\(content.settings.paths.imagesOutputFolderPath)/\(id.fileNameWithoutExtension)"
|
"\(content.settings.paths.imagesOutputFolderPath)/\(identifier.fileNameWithoutExtension)"
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputPath(width: Int, height: Int, type: FileType?) -> String {
|
func outputPath(width: Int, height: Int, type: FileType?) -> String {
|
||||||
@ -293,9 +293,9 @@ final class FileResource: Item, LocalizedItem {
|
|||||||
|
|
||||||
func createVideoThumbnail() {
|
func createVideoThumbnail() {
|
||||||
guard type.isVideo else { return }
|
guard type.isVideo else { return }
|
||||||
guard !content.storage.hasVideoThumbnail(for: id) else { return }
|
guard !content.storage.hasVideoThumbnail(for: identifier) else { return }
|
||||||
Task {
|
Task {
|
||||||
if await content.imageGenerator.createVideoThumbnail(for: id) {
|
if await content.imageGenerator.createVideoThumbnail(for: identifier) {
|
||||||
didChange()
|
didChange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -322,7 +322,7 @@ final class FileResource: Item, LocalizedItem {
|
|||||||
return "/" + customOutputPath
|
return "/" + customOutputPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let path = pathPrefix + "/" + id
|
let path = pathPrefix + "/" + identifier
|
||||||
return makeCleanAbsolutePath(path)
|
return makeCleanAbsolutePath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,14 +353,14 @@ final class FileResource: Item, LocalizedItem {
|
|||||||
@discardableResult
|
@discardableResult
|
||||||
func update(id newId: String) -> Bool {
|
func update(id newId: String) -> Bool {
|
||||||
guard !isExternallyStored else {
|
guard !isExternallyStored else {
|
||||||
id = newId
|
identifier = newId
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
guard content.storage.move(file: id, to: newId) else {
|
guard content.storage.move(file: identifier, to: newId) else {
|
||||||
print("Failed to move file \(id) to \(newId)")
|
print("Failed to move file \(identifier) to \(newId)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
id = newId
|
identifier = newId
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,7 +368,7 @@ final class FileResource: Item, LocalizedItem {
|
|||||||
extension FileResource: CustomStringConvertible {
|
extension FileResource: CustomStringConvertible {
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
id
|
identifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,6 +418,6 @@ extension FileResource: StorageItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func saveToDisk(_ data: Data) -> Bool {
|
func saveToDisk(_ data: Data) -> Bool {
|
||||||
content.storage.save(fileResource: data, for: id)
|
content.storage.save(fileResource: data, for: identifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,17 @@ class Item: ChangeObservingItem, Identifiable {
|
|||||||
@Published
|
@Published
|
||||||
private var changeToggle = false
|
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
|
@Published
|
||||||
var id: String
|
var identifier: String
|
||||||
|
|
||||||
init(content: Content, id: String) {
|
init(content: Content, id: String) {
|
||||||
self.id = id
|
self.identifier = id
|
||||||
super.init(content: content)
|
super.init(content: content)
|
||||||
|
|
||||||
observeChanges()
|
observeChanges()
|
||||||
@ -44,14 +50,14 @@ class Item: ChangeObservingItem, Identifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var itemId: ItemId {
|
var itemId: ItemId {
|
||||||
.init(type: itemType, id: id)
|
.init(type: itemType, id: identifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Item: Equatable {
|
extension Item: Equatable {
|
||||||
|
|
||||||
static func == (lhs: Item, rhs: Item) -> Bool {
|
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) {
|
func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(id)
|
hasher.combine(id)
|
||||||
hasher.combine(itemType)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Item: Comparable {
|
extension Item: Comparable {
|
||||||
|
|
||||||
static func < (lhs: Item, rhs: Item) -> Bool {
|
static func < (lhs: Item, rhs: Item) -> Bool {
|
||||||
lhs.id < rhs.id && lhs.itemType < rhs.itemType
|
lhs.identifier < rhs.identifier && lhs.itemType < rhs.itemType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,11 +31,11 @@ extension ItemReference: Identifiable {
|
|||||||
case .feed:
|
case .feed:
|
||||||
return "1-feed"
|
return "1-feed"
|
||||||
case .post(let post):
|
case .post(let post):
|
||||||
return "2-post-\(post.id)"
|
return "2-post-\(post.identifier)"
|
||||||
case .page(let page):
|
case .page(let page):
|
||||||
return "3-page-\(page.id)"
|
return "3-page-\(page.identifier)"
|
||||||
case .tagPage(let tag):
|
case .tagPage(let tag):
|
||||||
return "5-tag-\(tag.id)"
|
return "5-tag-\(tag.identifier)"
|
||||||
case .tagOverview:
|
case .tagOverview:
|
||||||
return "4-tag-overview"
|
return "4-tag-overview"
|
||||||
}
|
}
|
||||||
@ -76,11 +76,11 @@ extension ItemReference: CustomStringConvertible {
|
|||||||
case .feed:
|
case .feed:
|
||||||
return "Feed"
|
return "Feed"
|
||||||
case .post(let post):
|
case .post(let post):
|
||||||
return "Post \(post.id)"
|
return "Post \(post.identifier)"
|
||||||
case .page(let page):
|
case .page(let page):
|
||||||
return "Page \(page.id)"
|
return "Page \(page.identifier)"
|
||||||
case .tagPage(let tag):
|
case .tagPage(let tag):
|
||||||
return "Tag \(tag.id)"
|
return "Tag \(tag.identifier)"
|
||||||
case .tagOverview:
|
case .tagOverview:
|
||||||
return "Tag Overview"
|
return "Tag Overview"
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ final class LinkPreview: ObservableObject {
|
|||||||
// MARK: Storage
|
// MARK: Storage
|
||||||
|
|
||||||
var data: Data {
|
var data: Data {
|
||||||
.init(title: title, description: description, image: image?.id)
|
.init(title: title, description: description, image: image?.identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(context: LoadingContext, data: Data) {
|
init(context: LoadingContext, data: Data) {
|
||||||
|
@ -27,7 +27,7 @@ final class LoadingContext {
|
|||||||
posts: posts.values.sortedByStartDateAndId(),
|
posts: posts.values.sortedByStartDateAndId(),
|
||||||
pages: pages.values.sortedByStartDateAndId(),
|
pages: pages.values.sortedByStartDateAndId(),
|
||||||
tags: tags.values.sorted(),
|
tags: tags.values.sorted(),
|
||||||
files: files.values.sorted { $0.id },
|
files: files.values.sorted { $0.identifier },
|
||||||
tagOverview: tagOverview,
|
tagOverview: tagOverview,
|
||||||
errors: errors.sorted().map { StorageError(message: $0) })
|
errors: errors.sorted().map { StorageError(message: $0) })
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ extension LocalizedPost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var data: Data {
|
var data: Data {
|
||||||
.init(images: images.map { $0.id },
|
.init(images: images.map { $0.identifier },
|
||||||
labels: labels.map { $0.data }.nonEmpty,
|
labels: labels.map { $0.data }.nonEmpty,
|
||||||
title: title,
|
title: title,
|
||||||
text: text,
|
text: text,
|
||||||
|
@ -75,11 +75,11 @@ final class Page: Item, DateItem, LocalizedItem {
|
|||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func update(id newId: String) -> Bool {
|
func update(id newId: String) -> Bool {
|
||||||
guard content.storage.move(page: id, to: newId) else {
|
guard content.storage.move(page: identifier, to: newId) else {
|
||||||
print("Failed to move files of page \(id)")
|
print("Failed to move files of page \(identifier)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
id = newId
|
identifier = newId
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,11 +146,11 @@ final class Page: Item, DateItem, LocalizedItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func pageContent(in language: ContentLanguage) -> String? {
|
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 {
|
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
|
return false
|
||||||
}
|
}
|
||||||
if localized(in: language).update(hasContent: 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 {
|
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
|
return false
|
||||||
}
|
}
|
||||||
if localized(in: language).update(hasContent: true) {
|
if localized(in: language).update(hasContent: true) {
|
||||||
@ -175,7 +175,7 @@ final class Page: Item, DateItem, LocalizedItem {
|
|||||||
func updateContentExistence() {
|
func updateContentExistence() {
|
||||||
var didUpdate = false
|
var didUpdate = false
|
||||||
for language in ContentLanguage.allCases {
|
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) {
|
if localized(in: language).update(hasContent: hasContent) {
|
||||||
didUpdate = true
|
didUpdate = true
|
||||||
}
|
}
|
||||||
@ -234,7 +234,7 @@ extension Page: StorageItem {
|
|||||||
.init(
|
.init(
|
||||||
isDraft: isDraft,
|
isDraft: isDraft,
|
||||||
externalLink: externalLink,
|
externalLink: externalLink,
|
||||||
tags: tags.map { $0.id },
|
tags: tags.map { $0.identifier },
|
||||||
hideDate: hideDate ? true : nil,
|
hideDate: hideDate ? true : nil,
|
||||||
createdDate: createdDate,
|
createdDate: createdDate,
|
||||||
startDate: startDate,
|
startDate: startDate,
|
||||||
@ -244,6 +244,6 @@ extension Page: StorageItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func saveToDisk(_ data: Data) -> Bool {
|
func saveToDisk(_ data: Data) -> Bool {
|
||||||
content.storage.save(page: data, for: id)
|
content.storage.save(page: data, for: identifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,11 +118,11 @@ final class Post: Item, DateItem, LocalizedItem {
|
|||||||
A title for the UI, not the generation.
|
A title for the UI, not the generation.
|
||||||
*/
|
*/
|
||||||
override func title(in language: ContentLanguage) -> String {
|
override func title(in language: ContentLanguage) -> String {
|
||||||
localized(in: language).title ?? id
|
localized(in: language).title ?? identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(_ string: String) -> Bool {
|
func contains(_ string: String) -> Bool {
|
||||||
id.contains(string) ||
|
identifier.contains(string) ||
|
||||||
german.contains(string) ||
|
german.contains(string) ||
|
||||||
english.contains(string)
|
english.contains(string)
|
||||||
}
|
}
|
||||||
@ -135,11 +135,11 @@ final class Post: Item, DateItem, LocalizedItem {
|
|||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func update(id newId: String) -> Bool {
|
func update(id newId: String) -> Bool {
|
||||||
guard content.storage.move(post: id, to: newId) else {
|
guard content.storage.move(post: identifier, to: newId) else {
|
||||||
print("Failed to move file of post \(id)")
|
print("Failed to move file of post \(identifier)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
id = newId
|
identifier = newId
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,10 +149,10 @@ final class Post: Item, DateItem, LocalizedItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makePage() -> Page {
|
func makePage() -> Page {
|
||||||
var id = self.id
|
var id = self.identifier
|
||||||
var number = 2
|
var number = 2
|
||||||
while !content.isNewIdForPage(id) {
|
while !content.isNewIdForPage(id) {
|
||||||
id += "\(self.id)-\(number)"
|
id += "\(self.identifier)-\(number)"
|
||||||
number += 1
|
number += 1
|
||||||
}
|
}
|
||||||
// Move tags to page
|
// Move tags to page
|
||||||
@ -210,13 +210,13 @@ extension Post: StorageItem {
|
|||||||
createdDate: createdDate,
|
createdDate: createdDate,
|
||||||
startDate: startDate,
|
startDate: startDate,
|
||||||
endDate: endDate,
|
endDate: endDate,
|
||||||
tags: tags.map { $0.id },
|
tags: tags.map { $0.identifier },
|
||||||
german: german.data,
|
german: german.data,
|
||||||
english: english.data,
|
english: english.data,
|
||||||
linkedPageId: linkedPage?.id)
|
linkedPageId: linkedPage?.identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveToDisk(_ data: Data) -> Bool {
|
func saveToDisk(_ data: Data) -> Bool {
|
||||||
content.storage.save(post: data, for: id)
|
content.storage.save(post: data, for: identifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,8 +61,8 @@ extension AudioPlayerSettings {
|
|||||||
var data: Data {
|
var data: Data {
|
||||||
.init(playlistCoverImageSize: playlistCoverImageSize,
|
.init(playlistCoverImageSize: playlistCoverImageSize,
|
||||||
smallCoverImageSize: smallCoverImageSize,
|
smallCoverImageSize: smallCoverImageSize,
|
||||||
audioPlayerJsFile: audioPlayerJsFile?.id,
|
audioPlayerJsFile: audioPlayerJsFile?.identifier,
|
||||||
audioPlayerCssFile: audioPlayerCssFile?.id,
|
audioPlayerCssFile: audioPlayerCssFile?.identifier,
|
||||||
german: german.data,
|
german: german.data,
|
||||||
english: english.data)
|
english: english.data)
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ extension GeneralSettings {
|
|||||||
remotePortForUpload: remotePortForUpload,
|
remotePortForUpload: remotePortForUpload,
|
||||||
remotePathForUpload: remotePathForUpload,
|
remotePathForUpload: remotePathForUpload,
|
||||||
urlForPushNotification: urlForPushNotification,
|
urlForPushNotification: urlForPushNotification,
|
||||||
requiredFiles: requiredFiles.nonEmpty?.map { $0.id }.sorted())
|
requiredFiles: requiredFiles.nonEmpty?.map { $0.identifier }.sorted())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Data: Codable, Equatable {
|
struct Data: Codable, Equatable {
|
||||||
|
@ -113,13 +113,13 @@ extension PageSettings {
|
|||||||
.init(contentWidth: contentWidth,
|
.init(contentWidth: contentWidth,
|
||||||
largeImageWidth: largeImageWidth,
|
largeImageWidth: largeImageWidth,
|
||||||
pageLinkImageSize: pageLinkImageSize,
|
pageLinkImageSize: pageLinkImageSize,
|
||||||
defaultCssFile: defaultCssFile?.id,
|
defaultCssFile: defaultCssFile?.identifier,
|
||||||
codeHighlightingJsFile: codeHighlightingJsFile?.id,
|
codeHighlightingJsFile: codeHighlightingJsFile?.identifier,
|
||||||
modelViewerJsFile: modelViewerJsFile?.id,
|
modelViewerJsFile: modelViewerJsFile?.identifier,
|
||||||
imageCompareJsFile: imageCompareJsFile?.id,
|
imageCompareJsFile: imageCompareJsFile?.identifier,
|
||||||
imageCompareCssFile: imageCompareCssFile?.id,
|
imageCompareCssFile: imageCompareCssFile?.identifier,
|
||||||
manifestFile: manifestFile?.id,
|
manifestFile: manifestFile?.identifier,
|
||||||
routeJsFile: routeJsFile?.id,
|
routeJsFile: routeJsFile?.identifier,
|
||||||
german: german.data,
|
german: german.data,
|
||||||
english: english.data)
|
english: english.data)
|
||||||
}
|
}
|
||||||
|
@ -72,9 +72,9 @@ extension PostSettings {
|
|||||||
var data: PostSettings.Data {
|
var data: PostSettings.Data {
|
||||||
.init(postsPerPage: postsPerPage,
|
.init(postsPerPage: postsPerPage,
|
||||||
contentWidth: contentWidth,
|
contentWidth: contentWidth,
|
||||||
swiperCssFile: swiperCssFile?.id,
|
swiperCssFile: swiperCssFile?.identifier,
|
||||||
swiperJsFile: swiperJsFile?.id,
|
swiperJsFile: swiperJsFile?.identifier,
|
||||||
defaultCssFile: defaultCssFile?.id,
|
defaultCssFile: defaultCssFile?.identifier,
|
||||||
german: german.data,
|
german: german.data,
|
||||||
english: english.data)
|
english: english.data)
|
||||||
}
|
}
|
||||||
|
@ -37,11 +37,11 @@ class Tag: Item, LocalizedItem {
|
|||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func update(id newId: String) -> Bool {
|
func update(id newId: String) -> Bool {
|
||||||
guard content.storage.move(tag: id, to: newId) else {
|
guard content.storage.move(tag: identifier, to: newId) else {
|
||||||
print("Failed to move files of tag \(id)")
|
print("Failed to move files of tag \(identifier)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
id = newId
|
identifier = newId
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +106,6 @@ extension Tag: StorageItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func saveToDisk(_ data: Data) -> Bool {
|
func saveToDisk(_ data: Data) -> Bool {
|
||||||
content.storage.save(tag: data, for: id)
|
content.storage.save(tag: data, for: identifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,6 @@
|
|||||||
final class TagOverview: Tag {
|
final class TagOverview: Tag {
|
||||||
|
|
||||||
override var itemId: ItemId {
|
override var itemId: ItemId {
|
||||||
.init(type: .tagOverview, id: id)
|
.init(type: .tagOverview, id: identifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,10 @@ struct AddFileView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func importSelectedFiles() {
|
private func importSelectedFiles() {
|
||||||
|
guard !filesToAdd.isEmpty else {
|
||||||
|
dismiss()
|
||||||
|
return
|
||||||
|
}
|
||||||
for file in filesToAdd {
|
for file in filesToAdd {
|
||||||
guard file.isSelected else {
|
guard file.isSelected else {
|
||||||
print("Skipping unselected file \(file.uniqueId)")
|
print("Skipping unselected file \(file.uniqueId)")
|
||||||
|
@ -41,7 +41,7 @@ struct FileContentView: View {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
case .text, .code:
|
case .text, .code:
|
||||||
TextFileContentView(file: file)
|
TextFileContentView(file: file)
|
||||||
.id(file.id + file.modifiedDate.description)
|
.id(file.identifier + file.modifiedDate.description)
|
||||||
case .video:
|
case .video:
|
||||||
VStack {
|
VStack {
|
||||||
if let image = file.imageToDisplay {
|
if let image = file.imageToDisplay {
|
||||||
|
@ -72,11 +72,12 @@ struct FileDetailView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IdPropertyView(
|
IdPropertyView(
|
||||||
id: $file.id,
|
id: $file.identifier,
|
||||||
title: "Name",
|
title: "Name",
|
||||||
footer: "The unique name of the file, which is also used to reference it in posts and pages.",
|
footer: "The unique name of the file, which is also used to reference it in posts and pages.",
|
||||||
validation: file.isValid,
|
validation: file.isValid,
|
||||||
update: { file.update(id: $0) })
|
update: { file.update(id: $0) })
|
||||||
|
.id(file.id)
|
||||||
|
|
||||||
switch language {
|
switch language {
|
||||||
case .english:
|
case .english:
|
||||||
@ -154,7 +155,7 @@ struct FileDetailView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func showFileInFinder() {
|
private func showFileInFinder() {
|
||||||
content.storage.openFinderWindow(withSelectedFile: file.id)
|
content.storage.openFinderWindow(withSelectedFile: file.identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func markFileAsChanged() {
|
private func markFileAsChanged() {
|
||||||
@ -169,11 +170,11 @@ struct FileDetailView: View {
|
|||||||
|
|
||||||
private func replaceFile() {
|
private func replaceFile() {
|
||||||
guard let url = openFilePanel() else {
|
guard let url = openFilePanel() else {
|
||||||
print("File '\(file.id)': No file selected as replacement")
|
print("File '\(file.identifier)': No file selected as replacement")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard content.storage.importExternalFile(at: url, fileId: file.id) else {
|
guard content.storage.importExternalFile(at: url, fileId: file.identifier) else {
|
||||||
print("File '\(file.id)': Failed to replace file")
|
print("File '\(file.identifier)': Failed to replace file")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +198,7 @@ struct FileDetailView: View {
|
|||||||
|
|
||||||
let response = panel.runModal()
|
let response = panel.runModal()
|
||||||
guard response == .OK else {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,8 +210,8 @@ struct FileDetailView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard content.storage.removeFileContent(file: file.id) else {
|
guard content.storage.removeFileContent(file: file.identifier) else {
|
||||||
print("File '\(file.id)': Failed to delete file to make it external")
|
print("File '\(file.identifier)': Failed to delete file to make it external")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@ -220,8 +221,8 @@ struct FileDetailView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func deleteFile() {
|
private func deleteFile() {
|
||||||
guard content.storage.delete(file: file.id) else {
|
guard content.storage.delete(file: file.identifier) else {
|
||||||
print("File '\(file.id)': Failed to delete file in content folder")
|
print("File '\(file.identifier)': Failed to delete file in content folder")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content.remove(file)
|
content.remove(file)
|
||||||
|
@ -32,7 +32,7 @@ struct FileListView: View {
|
|||||||
guard !searchString.isEmpty else {
|
guard !searchString.isEmpty else {
|
||||||
return filesBySelectedType
|
return filesBySelectedType
|
||||||
}
|
}
|
||||||
return filesBySelectedType.filter { $0.id.contains(searchString) }
|
return filesBySelectedType.filter { $0.identifier.contains(searchString) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -55,10 +55,10 @@ struct FileListView: View {
|
|||||||
LazyVStack(spacing: 0) {
|
LazyVStack(spacing: 0) {
|
||||||
ForEach(filteredFiles) { file in
|
ForEach(filteredFiles) { file in
|
||||||
SelectableListItem(selected: selectedFile == file) {
|
SelectableListItem(selected: selectedFile == file) {
|
||||||
Text(file.id)
|
Text(file.identifier)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
.id(file.id)
|
.id(file.identifier)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
selectedFile = file
|
selectedFile = file
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ final class FileToAdd: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var idAlreadyExists: Bool {
|
var idAlreadyExists: Bool {
|
||||||
content.files.contains { $0.id == uniqueId }
|
content.files.contains { $0.identifier == uniqueId }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ struct MultiFileSelectionView: View {
|
|||||||
guard !searchString.isEmpty else {
|
guard !searchString.isEmpty else {
|
||||||
return filesBySelectedType
|
return filesBySelectedType
|
||||||
}
|
}
|
||||||
return filesBySelectedType.filter { $0.id.contains(searchString) }
|
return filesBySelectedType.filter { $0.identifier.contains(searchString) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -59,7 +59,7 @@ struct MultiFileSelectionView: View {
|
|||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.onTapGesture { deselect(file: file) }
|
.onTapGesture { deselect(file: file) }
|
||||||
Text(file.id)
|
Text(file.identifier)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ struct MultiFileSelectionView: View {
|
|||||||
Image(systemSymbol: .plusCircleFill)
|
Image(systemSymbol: .plusCircleFill)
|
||||||
.foregroundStyle(.green)
|
.foregroundStyle(.green)
|
||||||
}
|
}
|
||||||
Text(file.id)
|
Text(file.identifier)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
|
@ -49,9 +49,9 @@ struct TextFileContentView: View {
|
|||||||
|
|
||||||
private func reload() {
|
private func reload() {
|
||||||
fileContent = file.textContent()
|
fileContent = file.textContent()
|
||||||
loadedFile = file.id
|
loadedFile = file.identifier
|
||||||
loadedFileDate = file.modifiedDate
|
loadedFileDate = file.modifiedDate
|
||||||
print("Loaded content of file \(file.id)")
|
print("Loaded content of file \(file.identifier)")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func save() {
|
private func save() {
|
||||||
@ -59,25 +59,25 @@ struct TextFileContentView: View {
|
|||||||
print("[ERROR] Text File View: No file loaded to save")
|
print("[ERROR] Text File View: No file loaded to save")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard loadedFile == file.id else {
|
guard loadedFile == file.identifier else {
|
||||||
print("[ERROR] Text File View: Not saving since file changed")
|
print("[ERROR] Text File View: Not saving since file changed")
|
||||||
reload()
|
reload()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard loadedFileDate == file.modifiedDate else {
|
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()
|
reload()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard fileContent != "" else {
|
guard fileContent != "" else {
|
||||||
print("Text File View: Not saving empty file \(file.id)")
|
print("Text File View: Not saving empty file \(file.identifier)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard file.save(textContent: fileContent) else {
|
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
|
return
|
||||||
}
|
}
|
||||||
loadedFileDate = file.modifiedDate
|
loadedFileDate = file.modifiedDate
|
||||||
print("Text File View: Saved file \(file.id)")
|
print("Text File View: Saved file \(file.identifier)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,11 +72,11 @@ struct GenerationContentView: View {
|
|||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "required files",
|
text: "required files",
|
||||||
statusWhenNonEmpty: .nominal,
|
statusWhenNonEmpty: .nominal,
|
||||||
items: content.results.requiredFiles) { $0.id }
|
items: content.results.requiredFiles) { $0.identifier }
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "external files",
|
text: "external files",
|
||||||
statusWhenNonEmpty: .nominal,
|
statusWhenNonEmpty: .nominal,
|
||||||
items: content.results.externalFiles) { $0.id }
|
items: content.results.externalFiles) { $0.identifier }
|
||||||
GenerationIssuesView(
|
GenerationIssuesView(
|
||||||
text: "empty pages",
|
text: "empty pages",
|
||||||
statusWhenNonEmpty: .warning,
|
statusWhenNonEmpty: .warning,
|
||||||
@ -96,14 +96,14 @@ struct GenerationContentView: View {
|
|||||||
statusWhenNonEmpty: .warning,
|
statusWhenNonEmpty: .warning,
|
||||||
items: draftPages,
|
items: draftPages,
|
||||||
buttonText: "Show",
|
buttonText: "Show",
|
||||||
itemText: { $0.id },
|
itemText: { $0.identifier },
|
||||||
action: { show($0) })
|
action: { show($0) })
|
||||||
GenerationIssuesActionView(
|
GenerationIssuesActionView(
|
||||||
title: "draft posts",
|
title: "draft posts",
|
||||||
statusWhenNonEmpty: .warning,
|
statusWhenNonEmpty: .warning,
|
||||||
items: draftPosts,
|
items: draftPosts,
|
||||||
buttonText: "Show",
|
buttonText: "Show",
|
||||||
itemText: { $0.id },
|
itemText: { $0.identifier },
|
||||||
action: { show($0) })
|
action: { show($0) })
|
||||||
GenerationIssuesView(
|
GenerationIssuesView(
|
||||||
text: "additional output files",
|
text: "additional output files",
|
||||||
@ -117,10 +117,10 @@ struct GenerationContentView: View {
|
|||||||
}
|
}
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "inaccessible files",
|
text: "inaccessible files",
|
||||||
items: content.results.inaccessibleFiles) { $0.id }
|
items: content.results.inaccessibleFiles) { $0.identifier }
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "unparsable files",
|
text: "unparsable files",
|
||||||
items: content.results.unparsableFiles) { $0.id }
|
items: content.results.unparsableFiles) { $0.identifier }
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "unsaved output files",
|
text: "unsaved output files",
|
||||||
items: content.results.unsavedOutputFiles)
|
items: content.results.unsavedOutputFiles)
|
||||||
|
@ -24,7 +24,7 @@ struct FilePropertyView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
GenericPropertyView(title: title, footer: footer) {
|
GenericPropertyView(title: title, footer: footer) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(selectedFile?.id ?? "No file selected")
|
Text(selectedFile?.identifier ?? "No file selected")
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Select") {
|
Button("Select") {
|
||||||
showFileSelectionSheet = true
|
showFileSelectionSheet = true
|
||||||
|
@ -36,7 +36,7 @@ struct OptionalImagePropertyView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text(selectedImage?.id ?? "No file selected")
|
Text(selectedImage?.identifier ?? "No file selected")
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Select") {
|
Button("Select") {
|
||||||
showSelectionSheet = true
|
showSelectionSheet = true
|
||||||
|
@ -15,7 +15,7 @@ struct PagePropertyView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
GenericPropertyView(title: title, footer: footer) {
|
GenericPropertyView(title: title, footer: footer) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(selectedPage?.id ?? "No page selected")
|
Text(selectedPage?.identifier ?? "No page selected")
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Select") {
|
Button("Select") {
|
||||||
showPageSelectionSheet = true
|
showPageSelectionSheet = true
|
||||||
|
@ -16,7 +16,7 @@ struct TagDisplayView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
FlowHStack {
|
FlowHStack {
|
||||||
ForEach(tags, id: \.id) { tag in
|
ForEach(tags, id: \.identifier) { tag in
|
||||||
TagView(text: tag.localized(in: language).name)
|
TagView(text: tag.localized(in: language).name)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ struct TagPickerView: View {
|
|||||||
Text("Select a tag to link to")
|
Text("Select a tag to link to")
|
||||||
List(content.tags, selection: $newSelection) { tag in
|
List(content.tags, selection: $newSelection) { tag in
|
||||||
let loc = tag.localized(in: language)
|
let loc = tag.localized(in: language)
|
||||||
Text("\(loc.title) (\(tag.id))")
|
Text("\(loc.title) (\(tag.identifier))")
|
||||||
.tag(tag)
|
.tag(tag)
|
||||||
}
|
}
|
||||||
.frame(minHeight: 300)
|
.frame(minHeight: 300)
|
||||||
|
@ -15,7 +15,7 @@ struct TagPropertyView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
GenericPropertyView(title: title, footer: footer) {
|
GenericPropertyView(title: title, footer: footer) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(selectedTag?.id ?? "No tag selected")
|
Text(selectedTag?.identifier ?? "No tag selected")
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Select") {
|
Button("Select") {
|
||||||
showTagSelectionSheet = true
|
showTagSelectionSheet = true
|
||||||
|
@ -20,7 +20,7 @@ final class InsertableFileButton: ObservableObject {
|
|||||||
"""
|
"""
|
||||||
icon: \(label.icon.rawValue)
|
icon: \(label.icon.rawValue)
|
||||||
text: \(label.value)
|
text: \(label.value)
|
||||||
file: \(file.id)
|
file: \(file.identifier)
|
||||||
"""
|
"""
|
||||||
guard let downloadedFileName else {
|
guard let downloadedFileName else {
|
||||||
return result
|
return result
|
||||||
@ -86,7 +86,7 @@ struct InsertableButtons: View, InsertableCommandView {
|
|||||||
var id: String {
|
var id: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .file(let file):
|
case .file(let file):
|
||||||
return "file-\(file.file?.id ?? "none")"
|
return "file-\(file.file?.identifier ?? "none")"
|
||||||
case .url(let url):
|
case .url(let url):
|
||||||
return "url-\(url.url)"
|
return "url-\(url.url)"
|
||||||
case .event(let event):
|
case .event(let event):
|
||||||
@ -161,7 +161,7 @@ private struct FileButtonView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
LabelEditingView(label: $content.label)
|
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")
|
OptionalTextField("", text: $content.downloadedFileName, prompt: "Downloaded file name")
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ struct InsertableGallery: View, InsertableCommandView {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
["```\(GalleryBlock.blockId)"] +
|
["```\(GalleryBlock.blockId)"] +
|
||||||
images.map { $0.id } +
|
images.map { $0.identifier } +
|
||||||
["```"]
|
["```"]
|
||||||
).joined(separator: "\n")
|
).joined(separator: "\n")
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,9 @@ struct InsertableImage: View, InsertableCommandView {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard let caption else {
|
guard let caption else {
|
||||||
return ")"
|
return ")"
|
||||||
}
|
}
|
||||||
return ";\(caption))"
|
return ";\(caption))"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,11 +45,11 @@ struct InsertableLink: View, InsertableCommandView {
|
|||||||
case .post, .tagOverview:
|
case .post, .tagOverview:
|
||||||
return nil
|
return nil
|
||||||
case .page:
|
case .page:
|
||||||
return selectedPage?.id
|
return selectedPage?.identifier
|
||||||
case .tag:
|
case .tag:
|
||||||
return selectedTag?.id
|
return selectedTag?.identifier
|
||||||
case .file:
|
case .file:
|
||||||
return selectedFile?.id
|
return selectedFile?.identifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,8 +40,8 @@ struct InsertableRoute: View, InsertableCommandView {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var result = ["```route"]
|
var result = ["```route"]
|
||||||
result.append("\(RouteBlock.Key.image.rawValue): \(selectedImage.id)")
|
result.append("\(RouteBlock.Key.image.rawValue): \(selectedImage.identifier)")
|
||||||
result.append("\(RouteBlock.Key.file.rawValue): \(dataFile.id)")
|
result.append("\(RouteBlock.Key.file.rawValue): \(dataFile.identifier)")
|
||||||
if components != Set(RouteStatisticType.allCases) {
|
if components != Set(RouteStatisticType.allCases) {
|
||||||
let list = components
|
let list = components
|
||||||
.map { $0.rawValue }
|
.map { $0.rawValue }
|
||||||
|
@ -50,16 +50,16 @@ struct InsertableVideo: View, InsertableCommandView {
|
|||||||
var lines: [String] = []
|
var lines: [String] = []
|
||||||
lines.append("```video")
|
lines.append("```video")
|
||||||
if let posterImage {
|
if let posterImage {
|
||||||
lines.append("\(VideoBlock.Key.poster): \(posterImage.id)")
|
lines.append("\(VideoBlock.Key.poster): \(posterImage.identifier)")
|
||||||
}
|
}
|
||||||
if let videoH265 {
|
if let videoH265 {
|
||||||
lines.append("\(VideoBlock.Key.h265): \(videoH265.id)")
|
lines.append("\(VideoBlock.Key.h265): \(videoH265.identifier)")
|
||||||
}
|
}
|
||||||
if let videoH264 {
|
if let videoH264 {
|
||||||
lines.append("\(VideoBlock.Key.h264): \(videoH264.id)")
|
lines.append("\(VideoBlock.Key.h264): \(videoH264.identifier)")
|
||||||
}
|
}
|
||||||
if let videoWebm {
|
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 controls { lines.append(VideoBlock.Key.controls.rawValue) }
|
||||||
if autoplay { lines.append(VideoBlock.Key.autoplay.rawValue) }
|
if autoplay { lines.append(VideoBlock.Key.autoplay.rawValue) }
|
||||||
|
@ -35,7 +35,7 @@ struct PageContentResultsView: View {
|
|||||||
TextWithSymbol(
|
TextWithSymbol(
|
||||||
symbol: $0.type.category.symbol,
|
symbol: $0.type.category.symbol,
|
||||||
color: .blue,
|
color: .blue,
|
||||||
text: $0.id)
|
text: $0.identifier)
|
||||||
}
|
}
|
||||||
+ results.missingFiles.keys.map {
|
+ results.missingFiles.keys.map {
|
||||||
TextWithSymbol(
|
TextWithSymbol(
|
||||||
|
@ -32,7 +32,7 @@ struct PageContentView: View {
|
|||||||
if page.isExternalUrl {
|
if page.isExternalUrl {
|
||||||
VStack {
|
VStack {
|
||||||
PageTitleView(page: page.localized(in: language))
|
PageTitleView(page: page.localized(in: language))
|
||||||
.id(page.id + language.rawValue)
|
.id(page.identifier + language.rawValue)
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("No content available for external page")
|
Text("No content available for external page")
|
||||||
.font(.title)
|
.font(.title)
|
||||||
@ -42,10 +42,10 @@ struct PageContentView: View {
|
|||||||
} else {
|
} else {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
PageTitleView(page: page.localized(in: language))
|
PageTitleView(page: page.localized(in: language))
|
||||||
.id(page.id + language.rawValue)
|
.id(page.identifier + language.rawValue)
|
||||||
TagDisplayView(tags: $page.tags)
|
TagDisplayView(tags: $page.tags)
|
||||||
LocalizedPageContentView(page: page, language: language)
|
LocalizedPageContentView(page: page, language: language)
|
||||||
.id(page.id + language.rawValue)
|
.id(page.identifier + language.rawValue)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ struct PageDetailView: View {
|
|||||||
title: "Page",
|
title: "Page",
|
||||||
text: "A page contains longer content")
|
text: "A page contains longer content")
|
||||||
IdPropertyView(
|
IdPropertyView(
|
||||||
id: $page.id,
|
id: $page.identifier,
|
||||||
footer: "The page id is used to link to it internally.",
|
footer: "The page id is used to link to it internally.",
|
||||||
validation: page.isValid,
|
validation: page.isValid,
|
||||||
update: { page.update(id: $0) })
|
update: { page.update(id: $0) })
|
||||||
@ -75,7 +75,7 @@ struct PageDetailView: View {
|
|||||||
isExternalPage: page.isExternalUrl,
|
isExternalPage: page.isExternalUrl,
|
||||||
page: page.localized(in: language),
|
page: page.localized(in: language),
|
||||||
transferImage: transferImage)
|
transferImage: transferImage)
|
||||||
.id(page.id + language.rawValue)
|
.id(page.identifier + language.rawValue)
|
||||||
ColoredButton(delete: deletePage)
|
ColoredButton(delete: deletePage)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
@ -83,8 +83,8 @@ struct PageDetailView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func deletePage() {
|
private func deletePage() {
|
||||||
guard content.storage.delete(page: page.id) else {
|
guard content.storage.delete(page: page.identifier) else {
|
||||||
print("Page '\(page.id)': Failed to delete file in content folder")
|
print("Page '\(page.identifier)': Failed to delete file in content folder")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content.remove(page)
|
content.remove(page)
|
||||||
|
@ -27,7 +27,7 @@ struct PagePickerView: View {
|
|||||||
Text("Select a page to link to")
|
Text("Select a page to link to")
|
||||||
List(content.pages, selection: $newSelection) { page in
|
List(content.pages, selection: $newSelection) { page in
|
||||||
let loc = page.localized(in: language)
|
let loc = page.localized(in: language)
|
||||||
Text("\(loc.title) (\(page.id))")
|
Text("\(loc.title) (\(page.identifier))")
|
||||||
.tag(page)
|
.tag(page)
|
||||||
}
|
}
|
||||||
.frame(minHeight: 300)
|
.frame(minHeight: 300)
|
||||||
|
@ -43,7 +43,7 @@ struct PostDetailView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IdPropertyView(
|
IdPropertyView(
|
||||||
id: $post.id,
|
id: $post.identifier,
|
||||||
footer: "The id is used to link to post and store them",
|
footer: "The id is used to link to post and store them",
|
||||||
validation: post.isValid,
|
validation: post.isValid,
|
||||||
update: { post.update(id: $0) })
|
update: { post.update(id: $0) })
|
||||||
@ -99,8 +99,8 @@ struct PostDetailView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func deletePost() {
|
private func deletePost() {
|
||||||
guard content.storage.delete(post: post.id) else {
|
guard content.storage.delete(post: post.identifier) else {
|
||||||
print("Post '\(post.id)': Failed to delete file in content folder")
|
print("Post '\(post.identifier)': Failed to delete file in content folder")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content.remove(post)
|
content.remove(post)
|
||||||
|
@ -18,7 +18,7 @@ struct PostImageView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.frame(height: 100)
|
.frame(height: 100)
|
||||||
Text(image.id)
|
Text(image.identifier)
|
||||||
.font(.title)
|
.font(.title)
|
||||||
Text("Failed to load image")
|
Text("Failed to load image")
|
||||||
.font(.body)
|
.font(.body)
|
||||||
@ -32,7 +32,7 @@ struct PostImageView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.frame(height: 100)
|
.frame(height: 100)
|
||||||
Text(image.id)
|
Text(image.identifier)
|
||||||
.font(.title)
|
.font(.title)
|
||||||
Button("Generate preview") {
|
Button("Generate preview") {
|
||||||
generateVideoPreview(image)
|
generateVideoPreview(image)
|
||||||
@ -48,7 +48,7 @@ struct PostImageView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.frame(height: 100)
|
.frame(height: 100)
|
||||||
Text(image.id)
|
Text(image.identifier)
|
||||||
.font(.title)
|
.font(.title)
|
||||||
Text("Invalid media type")
|
Text("Invalid media type")
|
||||||
.font(.body)
|
.font(.body)
|
||||||
|
@ -10,7 +10,7 @@ private struct PostListItem: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
LocalizedPostListItem(id: post.id, post: post.localized(in: language))
|
LocalizedPostListItem(id: post.identifier, post: post.localized(in: language))
|
||||||
if post.isDraft {
|
if post.isDraft {
|
||||||
TextIndicator(text: "Draft", background: .yellow)
|
TextIndicator(text: "Draft", background: .yellow)
|
||||||
} else {
|
} else {
|
||||||
|
@ -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.")
|
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(
|
IdPropertyView(
|
||||||
id: $tag.id,
|
id: $tag.identifier,
|
||||||
title: "Tag id",
|
title: "Tag id",
|
||||||
footer: "The unique id of the tag for references",
|
footer: "The unique id of the tag for references",
|
||||||
validation: tag.isValid) {
|
validation: tag.isValid) {
|
||||||
@ -42,7 +42,7 @@ struct TagDetailView: View {
|
|||||||
LocalizedTagDetailView(
|
LocalizedTagDetailView(
|
||||||
tag: tag.localized(in: language),
|
tag: tag.localized(in: language),
|
||||||
transferImage: transferImage)
|
transferImage: transferImage)
|
||||||
.id(tag.id + language.rawValue)
|
.id(tag.identifier + language.rawValue)
|
||||||
ColoredButton(delete: deleteTag)
|
ColoredButton(delete: deleteTag)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
@ -50,8 +50,8 @@ struct TagDetailView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func deleteTag() {
|
private func deleteTag() {
|
||||||
guard content.storage.delete(tag: tag.id) else {
|
guard content.storage.delete(tag: tag.identifier) else {
|
||||||
print("Tag '\(tag.id)': Failed to delete file in content folder")
|
print("Tag '\(tag.identifier)': Failed to delete file in content folder")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content.remove(tag)
|
content.remove(tag)
|
||||||
|
Reference in New Issue
Block a user