Fix id of Items, saving

This commit is contained in:
Christoph Hagen
2025-06-11 08:19:44 +02:00
parent 5970ce2e9f
commit 1d0eba9d78
64 changed files with 233 additions and 217 deletions

View File

@ -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;

View File

@ -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)

View File

@ -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 ""
}

View File

@ -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) {

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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(

View File

@ -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,

View File

@ -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 }

View File

@ -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) {

View File

@ -2,7 +2,7 @@ import SwiftUI
protocol MainContentView: View {
associatedtype Item: Identifiable
associatedtype Item
init(item: Item)

View File

@ -12,7 +12,7 @@ struct SelectedDetailView<Contained>: View where Contained: MainContentView {
var body: some View {
if let item = selected {
Contained(item: item)
.id(item.id)
//.id(item.id)
} else {
EmptyView()
}

View File

@ -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" })!
}
}

View File

@ -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" })!
}
}
}

View File

@ -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" })!
}
}
}

View File

@ -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" })!
}
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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<AnyCancellable> = []
/// 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? {

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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"
}

View File

@ -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) {

View File

@ -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) })
}

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -2,6 +2,6 @@
final class TagOverview: Tag {
override var itemId: ItemId {
.init(type: .tagOverview, id: id)
.init(type: .tagOverview, id: identifier)
}
}

View File

@ -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)")

View File

@ -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 {

View File

@ -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)

View File

@ -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
}

View File

@ -30,7 +30,7 @@ final class FileToAdd: ObservableObject {
}
var idAlreadyExists: Bool {
content.files.contains { $0.id == uniqueId }
content.files.contains { $0.identifier == uniqueId }
}
}

View File

@ -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())

View File

@ -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)")
}
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -28,7 +28,7 @@ struct InsertableGallery: View, InsertableCommandView {
}
return (
["```\(GalleryBlock.blockId)"] +
images.map { $0.id } +
images.map { $0.identifier } +
["```"]
).joined(separator: "\n")
}

View File

@ -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))"
}
}

View File

@ -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
}
}

View File

@ -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 }

View File

@ -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) }

View File

@ -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(

View File

@ -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()
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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 {

View File

@ -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)