Compare commits
7 Commits
062e7d289a
...
a4710d525b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4710d525b | ||
|
|
a8920a4cd2 | ||
|
|
cb041eb6ed | ||
|
|
329519e15b | ||
|
|
d6502fb09c | ||
|
|
dd720d6646 | ||
|
|
e689903f3c |
@@ -207,6 +207,7 @@
|
|||||||
E2F3B3982DC54F9400CFA712 /* ChangeObservingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B3972DC54F8600CFA712 /* ChangeObservingItem.swift */; };
|
E2F3B3982DC54F9400CFA712 /* ChangeObservingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B3972DC54F8600CFA712 /* ChangeObservingItem.swift */; };
|
||||||
E2F3B39C2DC5542E00CFA712 /* LabelEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B39B2DC5542E00CFA712 /* LabelEditingView.swift */; };
|
E2F3B39C2DC5542E00CFA712 /* LabelEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B39B2DC5542E00CFA712 /* LabelEditingView.swift */; };
|
||||||
E2F3B39E2DC55B1C00CFA712 /* LabelCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B39D2DC55B1C00CFA712 /* LabelCreationView.swift */; };
|
E2F3B39E2DC55B1C00CFA712 /* LabelCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B39D2DC55B1C00CFA712 /* LabelCreationView.swift */; };
|
||||||
|
E2F3B3A22DC769C300CFA712 /* DeleteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B3A12DC769BF00CFA712 /* DeleteButton.swift */; };
|
||||||
E2FD1D0D2D2DBBA600B48627 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */; };
|
E2FD1D0D2D2DBBA600B48627 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */; };
|
||||||
E2FD1D192D2DC4F500B48627 /* LoadingContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */; };
|
E2FD1D192D2DC4F500B48627 /* LoadingContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */; };
|
||||||
E2FD1D1B2D2DC63800B48627 /* LinkPreviewDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */; };
|
E2FD1D1B2D2DC63800B48627 /* LinkPreviewDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */; };
|
||||||
@@ -490,6 +491,7 @@
|
|||||||
E2F3B3972DC54F8600CFA712 /* ChangeObservingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeObservingItem.swift; sourceTree = "<group>"; };
|
E2F3B3972DC54F8600CFA712 /* ChangeObservingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeObservingItem.swift; sourceTree = "<group>"; };
|
||||||
E2F3B39B2DC5542E00CFA712 /* LabelEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelEditingView.swift; sourceTree = "<group>"; };
|
E2F3B39B2DC5542E00CFA712 /* LabelEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelEditingView.swift; sourceTree = "<group>"; };
|
||||||
E2F3B39D2DC55B1C00CFA712 /* LabelCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelCreationView.swift; sourceTree = "<group>"; };
|
E2F3B39D2DC55B1C00CFA712 /* LabelCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelCreationView.swift; sourceTree = "<group>"; };
|
||||||
|
E2F3B3A12DC769BF00CFA712 /* DeleteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteButton.swift; sourceTree = "<group>"; };
|
||||||
E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreview.swift; sourceTree = "<group>"; };
|
E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreview.swift; sourceTree = "<group>"; };
|
||||||
E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingContext.swift; sourceTree = "<group>"; };
|
E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingContext.swift; sourceTree = "<group>"; };
|
||||||
E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewDetailView.swift; sourceTree = "<group>"; };
|
E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewDetailView.swift; sourceTree = "<group>"; };
|
||||||
@@ -829,6 +831,7 @@
|
|||||||
E2A21C372CB9A4F10060935B /* Generic */ = {
|
E2A21C372CB9A4F10060935B /* Generic */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E2F3B3A12DC769BF00CFA712 /* DeleteButton.swift */,
|
||||||
E229902F2D0F75CF009F8D77 /* BoolPropertyView.swift */,
|
E229902F2D0F75CF009F8D77 /* BoolPropertyView.swift */,
|
||||||
E22990312D0F7678009F8D77 /* DatePropertyView.swift */,
|
E22990312D0F7678009F8D77 /* DatePropertyView.swift */,
|
||||||
E29D312F2D03A2BD0051B7F4 /* DescriptionField.swift */,
|
E29D312F2D03A2BD0051B7F4 /* DescriptionField.swift */,
|
||||||
@@ -1449,6 +1452,7 @@
|
|||||||
E25DA58B2D020C9500AEF16D /* PageImage.swift in Sources */,
|
E25DA58B2D020C9500AEF16D /* PageImage.swift in Sources */,
|
||||||
E2521DFC2D5020BE00C56662 /* PostContentGenerator.swift in Sources */,
|
E2521DFC2D5020BE00C56662 /* PostContentGenerator.swift in Sources */,
|
||||||
E21850232CF10C850090B18B /* TagSelectionView.swift in Sources */,
|
E21850232CF10C850090B18B /* TagSelectionView.swift in Sources */,
|
||||||
|
E2F3B3A22DC769C300CFA712 /* DeleteButton.swift in Sources */,
|
||||||
E2A21C332CB5BCAC0060935B /* PageContentView.swift in Sources */,
|
E2A21C332CB5BCAC0060935B /* PageContentView.swift in Sources */,
|
||||||
E22990402D0F95EC009F8D77 /* FolderOnDiskPropertyView.swift in Sources */,
|
E22990402D0F95EC009F8D77 /* FolderOnDiskPropertyView.swift in Sources */,
|
||||||
E2FE0F422D2B4821002963B7 /* OtherCodeBlock.swift in Sources */,
|
E2FE0F422D2B4821002963B7 /* OtherCodeBlock.swift in Sources */,
|
||||||
|
|||||||
@@ -260,6 +260,18 @@ final class GenerationResults: ObservableObject {
|
|||||||
let unused = existingFiles.subtracting(outputFiles)
|
let unused = existingFiles.subtracting(outputFiles)
|
||||||
update { self.unusedFilesInOutput = unused }
|
update { self.unusedFilesInOutput = unused }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sources(forMissingPage page: String) -> [(page: LocalizedItemId, source: String)] {
|
||||||
|
var all = [(page: LocalizedItemId, source: String)]()
|
||||||
|
for (id, results) in cache {
|
||||||
|
guard let sources = results.missingLinkedPages[page]?.sorted() else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let additions = sources.map { (page: id, source: $0) }
|
||||||
|
all.append(contentsOf: additions)
|
||||||
|
}
|
||||||
|
return all
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension Dictionary where Value == Set<LocalizedItemId> {
|
private extension Dictionary where Value == Set<LocalizedItemId> {
|
||||||
|
|||||||
@@ -16,4 +16,28 @@ final class SelectedContent: ObservableObject {
|
|||||||
|
|
||||||
@Published
|
@Published
|
||||||
var file: FileResource?
|
var file: FileResource?
|
||||||
|
|
||||||
|
func remove(_ post: Post) {
|
||||||
|
if self.post == post {
|
||||||
|
self.post = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(_ page: Page) {
|
||||||
|
if self.page == page {
|
||||||
|
self.page = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(_ tag: Tag) {
|
||||||
|
if self.tag == tag {
|
||||||
|
self.tag = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(_ file: FileResource) {
|
||||||
|
if self.file == file {
|
||||||
|
self.file = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ final class Content: ObservableObject {
|
|||||||
loadFromDisk(callback: callback)
|
loadFromDisk(callback: callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Removing items
|
||||||
|
|
||||||
func remove(_ file: FileResource) {
|
func remove(_ file: FileResource) {
|
||||||
files.remove(file)
|
files.remove(file)
|
||||||
for post in posts {
|
for post in posts {
|
||||||
@@ -129,6 +131,33 @@ final class Content: ObservableObject {
|
|||||||
settings.remove(file)
|
settings.remove(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func remove(_ post: Post) {
|
||||||
|
posts.remove(post)
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(_ page: Page) {
|
||||||
|
pages.remove(page)
|
||||||
|
for post in posts {
|
||||||
|
if post.linkedPage == page {
|
||||||
|
post.linkedPage = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Check for page links and other references in content
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(_ tag: Tag) {
|
||||||
|
tags.remove(tag)
|
||||||
|
for post in posts {
|
||||||
|
post.tags.remove(tag)
|
||||||
|
}
|
||||||
|
for page in pages {
|
||||||
|
page.tags.remove(tag)
|
||||||
|
}
|
||||||
|
// TODO: Check for tag links and other references is content
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Loading
|
||||||
|
|
||||||
func file(withOutputPath: String) -> FileResource? {
|
func file(withOutputPath: String) -> FileResource? {
|
||||||
files.first { $0.absoluteUrl == withOutputPath }
|
files.first { $0.absoluteUrl == withOutputPath }
|
||||||
}
|
}
|
||||||
@@ -214,11 +243,17 @@ final class Content: ObservableObject {
|
|||||||
|
|
||||||
private var imageDimensions: [String: CGSize] = [:]
|
private var imageDimensions: [String: CGSize] = [:]
|
||||||
|
|
||||||
|
private let imageDimensionsQueue = DispatchQueue(label: "imageDimensionsQueue")
|
||||||
|
|
||||||
func dimensions(of image: String) -> CGSize? {
|
func dimensions(of image: String) -> CGSize? {
|
||||||
|
imageDimensionsQueue.sync {
|
||||||
imageDimensions[image]
|
imageDimensions[image]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func cache(dimensions: CGSize?, of image: String) {
|
func cache(dimensions: CGSize?, of image: String) {
|
||||||
|
imageDimensionsQueue.sync {
|
||||||
imageDimensions[image] = dimensions
|
imageDimensions[image] = dimensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,3 +31,10 @@ extension LocalizedItemId: Comparable {
|
|||||||
return lhs.language < rhs.language
|
return lhs.language < rhs.language
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension LocalizedItemId: CustomStringConvertible {
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
"\(itemType) (\(language))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -147,6 +147,27 @@ final class Storage: ObservableObject {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Completely delete a post file from the content folder
|
||||||
|
*/
|
||||||
|
func delete(page pageId: String) -> Bool {
|
||||||
|
guard let contentScope else { return false }
|
||||||
|
guard contentScope.deleteFile(at: pageMetadataPath(page: pageId)) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Move the existing content files
|
||||||
|
var result = true
|
||||||
|
for language in ContentLanguage.allCases {
|
||||||
|
// Copy as many files as possible, since metadata was already moved
|
||||||
|
// Don't fail early
|
||||||
|
if !contentScope.deleteFile(at: pageContentPath(page: pageId, language: language)) {
|
||||||
|
print("Failed to delete content file \(language) of page \(pageId)")
|
||||||
|
result = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Posts
|
// MARK: Posts
|
||||||
|
|
||||||
private func postFileName(_ postId: String) -> String {
|
private func postFileName(_ postId: String) -> String {
|
||||||
@@ -186,6 +207,14 @@ final class Storage: ObservableObject {
|
|||||||
return contentScope.move(postFilePath(post: postId), to: postFilePath(post: newId))
|
return contentScope.move(postFilePath(post: postId), to: postFilePath(post: newId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Completely delete a post file from the content folder
|
||||||
|
*/
|
||||||
|
func delete(post postId: String) -> Bool {
|
||||||
|
guard let contentScope else { return false }
|
||||||
|
return contentScope.deleteFile(at: postFilePath(post: postId))
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Tags
|
// MARK: Tags
|
||||||
|
|
||||||
private func tagFileName(tagId: String) -> String {
|
private func tagFileName(tagId: String) -> String {
|
||||||
@@ -225,6 +254,11 @@ final class Storage: ObservableObject {
|
|||||||
return contentScope.move(tagFilePath(tag: tagId), to: tagFilePath(tag: newId))
|
return contentScope.move(tagFilePath(tag: tagId), to: tagFilePath(tag: newId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func delete(tag tagId: String) -> Bool {
|
||||||
|
guard let contentScope else { return false }
|
||||||
|
return contentScope.deleteFile(at: tagFilePath(tag: tagId))
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Files
|
// MARK: Files
|
||||||
|
|
||||||
func size(of file: String) -> Int? {
|
func size(of file: String) -> Int? {
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ struct FileDetailView: View {
|
|||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var content: Content
|
private var content: Content
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var selection: SelectedContent
|
||||||
|
|
||||||
@Environment(\.language)
|
@Environment(\.language)
|
||||||
private var language
|
private var language
|
||||||
|
|
||||||
@@ -218,6 +221,7 @@ struct FileDetailView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
content.remove(file)
|
content.remove(file)
|
||||||
|
selection.remove(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ struct GenerationContentView: View {
|
|||||||
@Environment(\.dismiss)
|
@Environment(\.dismiss)
|
||||||
private var dismiss
|
private var dismiss
|
||||||
|
|
||||||
|
var draftPages: Set<Page> {
|
||||||
|
Set(content.pages.filter { $0.isDraft })
|
||||||
|
}
|
||||||
|
|
||||||
|
var draftPosts: Set<Post> {
|
||||||
|
Set(content.posts.filter { $0.isDraft })
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Website Generation")
|
Text("Website Generation")
|
||||||
@@ -45,7 +53,7 @@ struct GenerationContentView: View {
|
|||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "output files",
|
text: "output files",
|
||||||
statusWhenNonEmpty: .nominal,
|
statusWhenNonEmpty: .nominal,
|
||||||
items: $content.results.outputFiles)
|
items: content.results.outputFiles)
|
||||||
GenerationResultsIssueView(
|
GenerationResultsIssueView(
|
||||||
text: "\(content.results.imagesToGenerate.count) images",
|
text: "\(content.results.imagesToGenerate.count) images",
|
||||||
status: .nominal,
|
status: .nominal,
|
||||||
@@ -57,54 +65,68 @@ struct GenerationContentView: View {
|
|||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "external links",
|
text: "external links",
|
||||||
statusWhenNonEmpty: .nominal,
|
statusWhenNonEmpty: .nominal,
|
||||||
items: $content.results.externalLinks)
|
items: content.results.externalLinks)
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "required files",
|
text: "required files",
|
||||||
statusWhenNonEmpty: .nominal,
|
statusWhenNonEmpty: .nominal,
|
||||||
items: $content.results.requiredFiles) { $0.id }
|
items: content.results.requiredFiles) { $0.id }
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "external files",
|
text: "external files",
|
||||||
statusWhenNonEmpty: .nominal,
|
statusWhenNonEmpty: .nominal,
|
||||||
items: $content.results.externalFiles) { $0.id }
|
items: content.results.externalFiles) { $0.id }
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "empty pages",
|
text: "empty pages",
|
||||||
statusWhenNonEmpty: .warning,
|
statusWhenNonEmpty: .warning,
|
||||||
items: $content.results.emptyPages) { "\($0.pageId) (\($0.language))" }
|
items: content.results.emptyPages) { "\($0.pageId) (\($0.language))" }
|
||||||
|
GenerationStringIssuesView(
|
||||||
|
text: "draft pages",
|
||||||
|
statusWhenNonEmpty: .warning,
|
||||||
|
items: draftPages) { $0.id }
|
||||||
|
GenerationStringIssuesView(
|
||||||
|
text: "draft posts",
|
||||||
|
statusWhenNonEmpty: .warning,
|
||||||
|
items: draftPosts) { $0.id }
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "additional output files",
|
text: "additional output files",
|
||||||
statusWhenNonEmpty: .warning,
|
statusWhenNonEmpty: .warning,
|
||||||
items: $content.results.unusedFilesInOutput)
|
items: content.results.unusedFilesInOutput)
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "inaccessible files",
|
text: "inaccessible files",
|
||||||
items: $content.results.inaccessibleFiles) { $0.id }
|
items: content.results.inaccessibleFiles) { $0.id }
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "unparsable files",
|
text: "unparsable files",
|
||||||
items: $content.results.unparsableFiles) { $0.id }
|
items: content.results.unparsableFiles) { $0.id }
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "unsaved output files",
|
text: "unsaved output files",
|
||||||
items: $content.results.unsavedOutputFiles)
|
items: content.results.unsavedOutputFiles)
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "failed image generations",
|
text: "failed image generations",
|
||||||
items: $content.results.failedImages) { $0.outputPath }
|
items: content.results.failedImages) { $0.outputPath }
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "missing files",
|
text: "missing files",
|
||||||
items: $content.results.missingFiles)
|
items: content.results.missingFiles)
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "missing tags",
|
text: "missing tags",
|
||||||
items: $content.results.missingTags)
|
items: content.results.missingTags)
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "missing pages",
|
text: "missing pages",
|
||||||
items: $content.results.missingPages)
|
items: content.results.missingPages) { pageId in
|
||||||
|
let sources = content.results.sources(forMissingPage: pageId)
|
||||||
|
.map { "\($0.page): \($0.source)"}
|
||||||
|
.joined(separator: ", ")
|
||||||
|
|
||||||
|
return "\(pageId) (\(sources))"
|
||||||
|
}
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "invalid commands",
|
text: "invalid commands",
|
||||||
items: $content.results.invalidCommands)
|
items: content.results.invalidCommands)
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "invalid blocks",
|
text: "invalid blocks",
|
||||||
items: $content.results.invalidBlocks)
|
items: content.results.invalidBlocks)
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "warnings",
|
text: "warnings",
|
||||||
statusWhenNonEmpty: .warning,
|
statusWhenNonEmpty: .warning,
|
||||||
items: $content.results.warnings)
|
items: content.results.warnings)
|
||||||
HorizontalCenter {
|
HorizontalCenter {
|
||||||
Button(action: { dismiss() }) {
|
Button(action: { dismiss() }) {
|
||||||
Text("Close")
|
Text("Close")
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ struct GenerationStringIssuesView<T>: View where T: Hashable {
|
|||||||
|
|
||||||
let statusWhenNonEmpty: IssueStatus
|
let statusWhenNonEmpty: IssueStatus
|
||||||
|
|
||||||
@Binding
|
let items: Set<T>
|
||||||
var items: Set<T>
|
|
||||||
|
|
||||||
let map: (T) -> String
|
let map: (T) -> String
|
||||||
|
|
||||||
@@ -18,10 +17,10 @@ struct GenerationStringIssuesView<T>: View where T: Hashable {
|
|||||||
items.isEmpty ? .nominal : statusWhenNonEmpty
|
items.isEmpty ? .nominal : statusWhenNonEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
init(text: String, statusWhenNonEmpty: IssueStatus = .error, items: Binding<Set<T>>, map: @escaping (T) -> String) {
|
init(text: String, statusWhenNonEmpty: IssueStatus = .error, items: Set<T>, map: @escaping (T) -> String) {
|
||||||
self.text = text
|
self.text = text
|
||||||
self.statusWhenNonEmpty = statusWhenNonEmpty
|
self.statusWhenNonEmpty = statusWhenNonEmpty
|
||||||
self._items = items
|
self.items = items
|
||||||
self.map = map
|
self.map = map
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,10 +55,10 @@ struct GenerationStringIssuesView<T>: View where T: Hashable {
|
|||||||
|
|
||||||
extension GenerationStringIssuesView where T == String {
|
extension GenerationStringIssuesView where T == String {
|
||||||
|
|
||||||
init(text: String, statusWhenNonEmpty: IssueStatus = .error, items: Binding<Set<String>>) {
|
init(text: String, statusWhenNonEmpty: IssueStatus = .error, items: Set<String>) {
|
||||||
self.text = text
|
self.text = text
|
||||||
self.statusWhenNonEmpty = statusWhenNonEmpty
|
self.statusWhenNonEmpty = statusWhenNonEmpty
|
||||||
self._items = items
|
self.items = items
|
||||||
self.map = { $0 }
|
self.map = { $0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
CHDataManagement/Views/Generic/DeleteButton.swift
Normal file
20
CHDataManagement/Views/Generic/DeleteButton.swift
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct DeleteButton: View {
|
||||||
|
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: action) {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Image(systemSymbol: .trash)
|
||||||
|
Text("Delete")
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.foregroundStyle(Color.white)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 8).fill(Color.red))
|
||||||
|
}.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,9 @@ struct PageDetailView: View {
|
|||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var content: Content
|
private var content: Content
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var selection: SelectedContent
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
private var page: Page
|
private var page: Page
|
||||||
|
|
||||||
@@ -73,10 +76,20 @@ struct PageDetailView: View {
|
|||||||
page: page.localized(in: language),
|
page: page.localized(in: language),
|
||||||
transferImage: transferImage)
|
transferImage: transferImage)
|
||||||
.id(page.id + language.rawValue)
|
.id(page.id + language.rawValue)
|
||||||
|
DeleteButton(action: deletePage)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func deletePage() {
|
||||||
|
guard content.storage.delete(page: page.id) else {
|
||||||
|
print("Page '\(page.id)': Failed to delete file in content folder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content.remove(page)
|
||||||
|
selection.remove(page)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PageDetailView: MainContentView {
|
extension PageDetailView: MainContentView {
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ struct PostDetailView: View {
|
|||||||
LocalizedPostDetailView(
|
LocalizedPostDetailView(
|
||||||
post: post.localized(in: language),
|
post: post.localized(in: language),
|
||||||
transferImage: transferImage)
|
transferImage: transferImage)
|
||||||
|
DeleteButton(action: deletePost)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
@@ -90,6 +91,15 @@ struct PostDetailView: View {
|
|||||||
selection.tab = .pages
|
selection.tab = .pages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func deletePost() {
|
||||||
|
guard content.storage.delete(post: post.id) else {
|
||||||
|
print("Post '\(post.id)': Failed to delete file in content folder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content.remove(post)
|
||||||
|
selection.remove(post)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PostDetailView: MainContentView {
|
extension PostDetailView: MainContentView {
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ struct TagDetailView: View {
|
|||||||
@Environment(\.language)
|
@Environment(\.language)
|
||||||
private var language
|
private var language
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var content: Content
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var selection: SelectedContent
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var tag: Tag
|
var tag: Tag
|
||||||
|
|
||||||
@@ -37,10 +43,20 @@ struct TagDetailView: View {
|
|||||||
tag: tag.localized(in: language),
|
tag: tag.localized(in: language),
|
||||||
transferImage: transferImage)
|
transferImage: transferImage)
|
||||||
.id(tag.id + language.rawValue)
|
.id(tag.id + language.rawValue)
|
||||||
|
DeleteButton(action: deleteTag)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func deleteTag() {
|
||||||
|
guard content.storage.delete(tag: tag.id) else {
|
||||||
|
print("Tag '\(tag.id)': Failed to delete file in content folder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content.remove(tag)
|
||||||
|
selection.remove(tag)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TagDetailView: MainContentView {
|
extension TagDetailView: MainContentView {
|
||||||
|
|||||||
Reference in New Issue
Block a user