Improve page indicators, adding items
This commit is contained in:
parent
0590224f02
commit
0db6e411c3
@ -19,7 +19,6 @@
|
||||
E21850372CFCA55F0090B18B /* LocalizedPostSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850362CFCA5580090B18B /* LocalizedPostSettings.swift */; };
|
||||
E218503D2CFCFD910090B18B /* LocalizedPostFeedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */; };
|
||||
E22990152D0E2B7F009F8D77 /* ItemSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990142D0E2B74009F8D77 /* ItemSelectionView.swift */; };
|
||||
E22990172D0E330F009F8D77 /* TagOverviewPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990162D0E32F5009F8D77 /* TagOverviewPage.swift */; };
|
||||
E22990192D0E3546009F8D77 /* ItemReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990182D0E3546009F8D77 /* ItemReference.swift */; };
|
||||
E229901E2D0E4364009F8D77 /* LocalizedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E229901D2D0E4362009F8D77 /* LocalizedItem.swift */; };
|
||||
E22990202D0ECBE5009F8D77 /* TagOverviewDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E229901F2D0ECBD4009F8D77 /* TagOverviewDetailView.swift */; };
|
||||
@ -238,7 +237,7 @@
|
||||
E2FE0F682D2D2CF6002963B7 /* LocalizedPageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F672D2D2CF0002963B7 /* LocalizedPageSettings.swift */; };
|
||||
E2FE0F6C2D2D335E002963B7 /* LocalizedPageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */; };
|
||||
E2FE0F6E2D2D3689002963B7 /* LocalizedAudioPlayerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F6D2D2D3685002963B7 /* LocalizedAudioPlayerSettings.swift */; };
|
||||
E2FE0F702D2D5235002963B7 /* DraftIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F6F2D2D5231002963B7 /* DraftIndicator.swift */; };
|
||||
E2FE0F702D2D5235002963B7 /* TextIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F6F2D2D5231002963B7 /* TextIndicator.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@ -254,7 +253,6 @@
|
||||
E21850362CFCA5580090B18B /* LocalizedPostSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPostSettings.swift; sourceTree = "<group>"; };
|
||||
E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPostFeedSettingsView.swift; sourceTree = "<group>"; };
|
||||
E22990142D0E2B74009F8D77 /* ItemSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemSelectionView.swift; sourceTree = "<group>"; };
|
||||
E22990162D0E32F5009F8D77 /* TagOverviewPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagOverviewPage.swift; sourceTree = "<group>"; };
|
||||
E22990182D0E3546009F8D77 /* ItemReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemReference.swift; sourceTree = "<group>"; };
|
||||
E229901D2D0E4362009F8D77 /* LocalizedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedItem.swift; sourceTree = "<group>"; };
|
||||
E229901F2D0ECBD4009F8D77 /* TagOverviewDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagOverviewDetailView.swift; sourceTree = "<group>"; };
|
||||
@ -468,7 +466,7 @@
|
||||
E2FE0F672D2D2CF0002963B7 /* LocalizedPageSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageSettings.swift; sourceTree = "<group>"; };
|
||||
E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageSettingsView.swift; sourceTree = "<group>"; };
|
||||
E2FE0F6D2D2D3685002963B7 /* LocalizedAudioPlayerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedAudioPlayerSettings.swift; sourceTree = "<group>"; };
|
||||
E2FE0F6F2D2D5231002963B7 /* DraftIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftIndicator.swift; sourceTree = "<group>"; };
|
||||
E2FE0F6F2D2D5231002963B7 /* TextIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextIndicator.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -499,7 +497,6 @@
|
||||
E229901D2D0E4362009F8D77 /* LocalizedItem.swift */,
|
||||
E29D31A22D0CC98B0051B7F4 /* Item.swift */,
|
||||
E22990182D0E3546009F8D77 /* ItemReference.swift */,
|
||||
E22990162D0E32F5009F8D77 /* TagOverviewPage.swift */,
|
||||
);
|
||||
path = Item;
|
||||
sourceTree = "<group>";
|
||||
@ -576,7 +573,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E29D31992D0C451B0051B7F4 /* Pages */,
|
||||
E25DA5702D01015400AEF16D /* GenerationContentView.swift */,
|
||||
E29D318D2D0B2E640051B7F4 /* PageSettingsContentView.swift */,
|
||||
);
|
||||
path = Content;
|
||||
@ -634,18 +630,19 @@
|
||||
E2A21C342CB9A3CA0060935B /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E29D318C2D0B2E5E0051B7F4 /* Content */,
|
||||
E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */,
|
||||
E29D316E2D0822720051B7F4 /* SettingsListView.swift */,
|
||||
E29D31702D08234D0051B7F4 /* GenerationDetailView.swift */,
|
||||
E25DA5942D023BCC00AEF16D /* PageSettingsDetailView.swift */,
|
||||
E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */,
|
||||
E25DA5442D00952D00AEF16D /* SettingsSection.swift */,
|
||||
E2A21C352CB9A3D70060935B /* PathSettingsView.swift */,
|
||||
E25DA56C2D00EBC900AEF16D /* NavigationBarSettingsView.swift */,
|
||||
E29D316E2D0822720051B7F4 /* SettingsListView.swift */,
|
||||
E25DA5702D01015400AEF16D /* GenerationContentView.swift */,
|
||||
E29D31702D08234D0051B7F4 /* GenerationDetailView.swift */,
|
||||
E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */,
|
||||
E29D318C2D0B2E5E0051B7F4 /* Content */,
|
||||
E2FE0F032D2671FC002963B7 /* LocalizedNavigationBarSettingsView.swift */,
|
||||
E25DA56E2D00F99900AEF16D /* PostFeedSettingsView.swift */,
|
||||
E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */,
|
||||
E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */,
|
||||
E25DA56C2D00EBC900AEF16D /* NavigationBarSettingsView.swift */,
|
||||
E25DA5942D023BCC00AEF16D /* PageSettingsDetailView.swift */,
|
||||
E2A21C352CB9A3D70060935B /* PathSettingsView.swift */,
|
||||
E25DA56E2D00F99900AEF16D /* PostFeedSettingsView.swift */,
|
||||
E229901F2D0ECBD4009F8D77 /* TagOverviewDetailView.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
@ -654,7 +651,7 @@
|
||||
E2A21C372CB9A4F10060935B /* Generic */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F6F2D2D5231002963B7 /* DraftIndicator.swift */,
|
||||
E2FE0F6F2D2D5231002963B7 /* TextIndicator.swift */,
|
||||
E2FE0F232D2A8C1A002963B7 /* TagDisplayView.swift */,
|
||||
E229902F2D0F75CF009F8D77 /* BoolPropertyView.swift */,
|
||||
E22990312D0F7678009F8D77 /* DatePropertyView.swift */,
|
||||
@ -1117,7 +1114,7 @@
|
||||
E29D313D2D047C1B0051B7F4 /* LocalizedPageContentView.swift in Sources */,
|
||||
E2FE0F242D2A8C21002963B7 /* TagDisplayView.swift in Sources */,
|
||||
E2A37D2D2CED2EF10000979F /* OptionalTextField.swift in Sources */,
|
||||
E2FE0F702D2D5235002963B7 /* DraftIndicator.swift in Sources */,
|
||||
E2FE0F702D2D5235002963B7 /* TextIndicator.swift in Sources */,
|
||||
E2A37D2B2CED2CC30000979F /* TagDetailView.swift in Sources */,
|
||||
E2A21C282CB29B2A0060935B /* FeedEntryData.swift in Sources */,
|
||||
E29D31942D0B7D280051B7F4 /* SimpleImage.swift in Sources */,
|
||||
@ -1181,7 +1178,6 @@
|
||||
E25DA5752D018B6100AEF16D /* FileDetailView.swift in Sources */,
|
||||
E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */,
|
||||
E29D31A12D0C75CA0051B7F4 /* Content+Validation.swift in Sources */,
|
||||
E22990172D0E330F009F8D77 /* TagOverviewPage.swift in Sources */,
|
||||
E29D316D2D07A5050051B7F4 /* PageGenerationResults.swift in Sources */,
|
||||
E229903E2D0F8F02009F8D77 /* StringPropertyView.swift in Sources */,
|
||||
E25DA5192CFF035900AEF16D /* Array+Split.swift in Sources */,
|
||||
|
@ -23,10 +23,12 @@ extension Content {
|
||||
}
|
||||
|
||||
func isValidIdForTagOrPageOrPost(_ id: String) -> Bool {
|
||||
!id.isEmpty &&
|
||||
id.rangeOfCharacter(from: Content.disallowedCharactersInIds) == nil
|
||||
}
|
||||
|
||||
func isValidIdForFile(_ id: String) -> Bool {
|
||||
!id.isEmpty &&
|
||||
id.rangeOfCharacter(from: Content.disallowedCharactersInFileIds) == nil
|
||||
}
|
||||
|
||||
|
@ -48,4 +48,11 @@ extension ContentLanguage {
|
||||
case .german: return "German"
|
||||
}
|
||||
}
|
||||
|
||||
var shortText: String {
|
||||
switch self {
|
||||
case .english: "EN"
|
||||
case .german: "DE"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,108 +0,0 @@
|
||||
import Foundation
|
||||
/*
|
||||
final class TagOverviewPage: Item {
|
||||
|
||||
static let id = "all-tags"
|
||||
|
||||
@Published
|
||||
var german: LocalizedTagOverviewPage
|
||||
|
||||
@Published
|
||||
var english: LocalizedTagOverviewPage
|
||||
|
||||
|
||||
init(content: Content, german: LocalizedTagOverviewPage, english: LocalizedTagOverviewPage) {
|
||||
self.german = german
|
||||
self.english = english
|
||||
super.init(content: content, id: TagOverviewPage.id)
|
||||
}
|
||||
|
||||
override var itemType: ItemType {
|
||||
.tagOverview
|
||||
}
|
||||
|
||||
override func title(in language: ContentLanguage) -> String {
|
||||
localized(in: language).title
|
||||
}
|
||||
|
||||
override func absoluteUrl(in language: ContentLanguage) -> String {
|
||||
makeCleanAbsolutePath(internalPath(for: language))
|
||||
}
|
||||
|
||||
func filePathRelativeToOutputFolder(for language: ContentLanguage) -> String {
|
||||
makeCleanRelativePath(internalPath(for: language))
|
||||
}
|
||||
|
||||
private func internalPath(for language: ContentLanguage) -> String {
|
||||
content.settings.paths.tagsOutputFolderPath + "/" + localized(in: language).urlComponent
|
||||
}
|
||||
|
||||
func contains(urlComponent: String) -> Bool {
|
||||
english.urlComponent == urlComponent || german.urlComponent == urlComponent
|
||||
}
|
||||
|
||||
var file: TagOverviewFile {
|
||||
.init(german: german.file,
|
||||
english: english.file)
|
||||
}
|
||||
}
|
||||
|
||||
extension TagOverviewPage: LocalizedItem {
|
||||
|
||||
}
|
||||
|
||||
final class LocalizedTagOverviewPage: ObservableObject {
|
||||
|
||||
unowned let content: Content
|
||||
|
||||
@Published
|
||||
var title: String
|
||||
|
||||
/**
|
||||
The string to use when creating the url for the page.
|
||||
*/
|
||||
@Published
|
||||
var urlComponent: String
|
||||
|
||||
@Published
|
||||
var linkPreviewImage: FileResource?
|
||||
|
||||
@Published
|
||||
var linkPreviewTitle: String?
|
||||
|
||||
@Published
|
||||
var linkPreviewDescription: String?
|
||||
|
||||
init(content: Content, title: String, urlString: String, linkPreviewImage: FileResource? = nil, linkPreviewTitle: String? = nil, linkPreviewDescription: String? = nil) {
|
||||
self.content = content
|
||||
self.title = title
|
||||
self.urlComponent = urlString
|
||||
self.linkPreviewImage = linkPreviewImage
|
||||
self.linkPreviewTitle = linkPreviewTitle
|
||||
self.linkPreviewDescription = linkPreviewDescription
|
||||
}
|
||||
|
||||
init(content: Content, file: LocalizedTagOverviewFile, image: FileResource?) {
|
||||
self.content = content
|
||||
self.title = file.title
|
||||
self.urlComponent = file.url
|
||||
self.linkPreviewImage = image
|
||||
self.linkPreviewTitle = file.linkPreviewTitle
|
||||
self.linkPreviewDescription = file.linkPreviewDescription
|
||||
}
|
||||
|
||||
var file: LocalizedTagOverviewFile {
|
||||
.init(url: urlComponent,
|
||||
title: title,
|
||||
linkPreviewImage: linkPreviewImage?.id,
|
||||
linkPreviewTitle: linkPreviewTitle,
|
||||
linkPreviewDescription: linkPreviewDescription)
|
||||
}
|
||||
|
||||
func isValid(urlComponent: String) -> Bool {
|
||||
!urlComponent.isEmpty &&
|
||||
content.isValidIdForTagOrPageOrPost(urlComponent) &&
|
||||
!content.containsTag(withUrlComponent: urlComponent)
|
||||
}
|
||||
}
|
||||
*/
|
@ -82,7 +82,9 @@ final class ModelLoader {
|
||||
if pages.isEmpty { print("No pages loaded") }
|
||||
|
||||
pages.forEach { pageId, data in
|
||||
context.pages[pageId] = Page(context: context, id: pageId, data: data)
|
||||
let page = Page(context: context, id: pageId, data: data)
|
||||
page.updateContentExistence()
|
||||
context.pages[pageId] = page
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,9 @@ final class LocalizedPage: ObservableObject {
|
||||
@Published
|
||||
var hideTitle: Bool
|
||||
|
||||
@Published
|
||||
var hasContent: Bool = false
|
||||
|
||||
init(content: Content,
|
||||
urlString: String,
|
||||
title: String,
|
||||
|
@ -153,8 +153,26 @@ final class Page: Item, DateItem, LocalizedItem {
|
||||
content.storage.pageContent(for: id, language: language)
|
||||
}
|
||||
|
||||
func hasContent(in language: ContentLanguage) -> Bool {
|
||||
content.storage.hasPageContent(for: id, language: language)
|
||||
func save(pageContent: String, in language: ContentLanguage) -> Bool {
|
||||
guard content.storage.save(pageContent: pageContent, for: id, in: language) else {
|
||||
return false
|
||||
}
|
||||
localized(in: language).hasContent = true
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
Update the `hasContent` property of all localized pages.
|
||||
*/
|
||||
func updateContentExistence() {
|
||||
for language in ContentLanguage.allCases {
|
||||
localized(in: language).hasContent = content.storage.hasPageContent(for: id, language: language)
|
||||
}
|
||||
}
|
||||
|
||||
/// All languages for which the page has no content
|
||||
var missingContentLanguages: [ContentLanguage] {
|
||||
ContentLanguage.allCases.filter { !localized(in: $0).hasContent }
|
||||
}
|
||||
|
||||
func remove(_ file: FileResource) {
|
||||
|
@ -81,6 +81,7 @@ final class Post: Item, DateItem, LocalizedItem {
|
||||
func toggle(_ tag: Tag) {
|
||||
if let linkedPage {
|
||||
linkedPage.toggle(tag)
|
||||
didChange() // Otherwise tags will not be updated
|
||||
return
|
||||
}
|
||||
guard let index = tags.firstIndex(of: tag) else {
|
||||
|
@ -67,7 +67,7 @@ final class Storage: ObservableObject {
|
||||
id + ".json"
|
||||
}
|
||||
|
||||
func save(pageContent: String, for pageId: String, language: ContentLanguage) -> Bool {
|
||||
func save(pageContent: String, for pageId: String, in language: ContentLanguage) -> Bool {
|
||||
guard let contentScope else { return false }
|
||||
let path = pageContentPath(page: pageId, language: language)
|
||||
return contentScope.write(pageContent, to: path)
|
||||
|
@ -8,15 +8,38 @@ struct FileToAddView: View {
|
||||
|
||||
let delete: (FileToAdd) -> Void
|
||||
|
||||
var symbol: SFSymbol {
|
||||
if file.idAlreadyExists {
|
||||
return .docOnDoc
|
||||
}
|
||||
if file.isSelected {
|
||||
return .checkmarkCircleFill
|
||||
}
|
||||
return .circle
|
||||
}
|
||||
|
||||
var color: Color {
|
||||
if file.idAlreadyExists {
|
||||
return .red
|
||||
}
|
||||
if file.isSelected {
|
||||
return .blue
|
||||
}
|
||||
return .gray
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image(systemSymbol: file.isSelected ? .checkmarkCircleFill : .circle)
|
||||
Image(systemSymbol: symbol)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundStyle(.blue)
|
||||
.foregroundStyle(color)
|
||||
.onTapGesture {
|
||||
file.isSelected.toggle()
|
||||
if !file.idAlreadyExists {
|
||||
file.isSelected.toggle()
|
||||
}
|
||||
}
|
||||
Image(systemSymbol: .trashCircleFill)
|
||||
.resizable()
|
||||
|
@ -1,15 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct DraftIndicator: View {
|
||||
|
||||
var body: some View {
|
||||
Text("Draft")
|
||||
.foregroundStyle(.white)
|
||||
.padding(.vertical, 2)
|
||||
.padding(.horizontal, 5)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 5, style: .circular)
|
||||
.foregroundStyle(Color.gray)
|
||||
)
|
||||
}
|
||||
}
|
33
CHDataManagement/Views/Generic/TextIndicator.swift
Normal file
33
CHDataManagement/Views/Generic/TextIndicator.swift
Normal file
@ -0,0 +1,33 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TextIndicator: View {
|
||||
|
||||
let text: LocalizedStringKey
|
||||
|
||||
let color: Color
|
||||
|
||||
let background: Color
|
||||
|
||||
init(text: String, color: Color = .white, background: Color = Color.gray) {
|
||||
self.text = .init(stringLiteral: text)
|
||||
self.background = background
|
||||
self.color = color
|
||||
}
|
||||
|
||||
init(text: LocalizedStringKey, color: Color = .white, background: Color = Color.gray) {
|
||||
self.text = text
|
||||
self.background = background
|
||||
self.color = color
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.foregroundStyle(color)
|
||||
.padding(.vertical, 2)
|
||||
.padding(.horizontal, 5)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 5, style: .circular)
|
||||
.foregroundStyle(background)
|
||||
)
|
||||
}
|
||||
}
|
@ -17,18 +17,16 @@ struct AddPageView: View {
|
||||
@State
|
||||
private var newPageId = ""
|
||||
|
||||
private let allowedCharactersInPageId = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")).inverted
|
||||
|
||||
init(selected: Binding<Page?>) {
|
||||
self._selectedPage = selected
|
||||
}
|
||||
|
||||
private var idExists: Bool {
|
||||
content.pages.contains { $0.id == newPageId }
|
||||
!content.isNewIdForPage(newPageId)
|
||||
}
|
||||
|
||||
private var containsInvalidCharacters: Bool {
|
||||
newPageId.rangeOfCharacter(from: allowedCharactersInPageId) != nil
|
||||
private var isInvalidId: Bool {
|
||||
!content.isValidIdForTagOrPageOrPost(newPageId)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@ -42,12 +40,12 @@ struct AddPageView: View {
|
||||
if newPageId.isEmpty {
|
||||
Text("Enter the id of the new page to create")
|
||||
.foregroundStyle(.secondary)
|
||||
} else if isInvalidId {
|
||||
Text("The id contains invalid characters")
|
||||
.foregroundStyle(Color.red)
|
||||
} else if idExists {
|
||||
Text("A page with the same id already exists")
|
||||
.foregroundStyle(Color.red)
|
||||
} else if containsInvalidCharacters {
|
||||
Text("The id contains invalid characters")
|
||||
.foregroundStyle(Color.red)
|
||||
} else {
|
||||
Text("Create a new page with the id")
|
||||
.foregroundStyle(.secondary)
|
||||
@ -59,7 +57,7 @@ struct AddPageView: View {
|
||||
Button(action: addNewPage) {
|
||||
Text("Create")
|
||||
}
|
||||
.disabled(newPageId.isEmpty || containsInvalidCharacters || idExists)
|
||||
.disabled(isInvalidId || idExists)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
@ -7,13 +7,11 @@ struct LocalizedPageContentView: View {
|
||||
@EnvironmentObject
|
||||
var content: Content
|
||||
|
||||
let pageId: String
|
||||
@ObservedObject
|
||||
var page: Page
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
@ObservedObject
|
||||
var page: LocalizedPage
|
||||
|
||||
@State
|
||||
private var pageContent: String = ""
|
||||
|
||||
@ -26,18 +24,8 @@ struct LocalizedPageContentView: View {
|
||||
@State
|
||||
private var didChangeContent = false
|
||||
|
||||
init(pageId: String, page: LocalizedPage, language: ContentLanguage) {
|
||||
self.pageId = pageId
|
||||
self.page = page
|
||||
self.language = language
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
TextField("", text: $page.title)
|
||||
.font(.title)
|
||||
.textFieldStyle(.plain)
|
||||
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Button(action: loadContent) {
|
||||
Text("Load")
|
||||
@ -70,14 +58,14 @@ struct LocalizedPageContentView: View {
|
||||
|
||||
private func loadContent() {
|
||||
let language = language
|
||||
guard page.content.storage.hasPageContent(for: pageId, language: language) else {
|
||||
guard page.localized(in: language).hasContent else {
|
||||
pageContent = "New file"
|
||||
DispatchQueue.main.async {
|
||||
didChangeContent = false
|
||||
}
|
||||
return
|
||||
}
|
||||
guard let content = page.content.storage.pageContent(for: pageId, language: language) else {
|
||||
guard let content = page.pageContent(in: language) else {
|
||||
print("Failed to load page content")
|
||||
pageContent = "Failed to load"
|
||||
DispatchQueue.main.async {
|
||||
@ -108,7 +96,7 @@ struct LocalizedPageContentView: View {
|
||||
guard didChangeContent else {
|
||||
return
|
||||
}
|
||||
guard page.content.storage.save(pageContent: pageContent, for: pageId, language: language) else {
|
||||
guard page.save(pageContent: pageContent, in: language) else {
|
||||
print("Failed to save content")
|
||||
return
|
||||
}
|
||||
@ -120,9 +108,6 @@ struct LocalizedPageContentView: View {
|
||||
guard content != pageContentUsedForGeneration else {
|
||||
return
|
||||
}
|
||||
guard let page = self.content.page(pageId) else {
|
||||
return
|
||||
}
|
||||
guard !self.content.isGeneratingWebsite else {
|
||||
return
|
||||
}
|
||||
|
@ -41,8 +41,10 @@ struct PageContentView: View {
|
||||
}.padding()
|
||||
} else {
|
||||
VStack(alignment: .leading) {
|
||||
PageTitleView(page: page.localized(in: language))
|
||||
.id(page.id + language.rawValue)
|
||||
TagDisplayView(tags: $page.tags)
|
||||
LocalizedPageContentView(pageId: page.id, page: page.localized(in: language), language: language)
|
||||
LocalizedPageContentView(page: page, language: language)
|
||||
.id(page.id + language.rawValue)
|
||||
}
|
||||
.padding()
|
||||
|
@ -1,5 +1,40 @@
|
||||
import SwiftUI
|
||||
|
||||
private struct PageListItem: View {
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@ObservedObject
|
||||
var page: Page
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
LocalizedPageListItem(page: page.localized(in: language))
|
||||
Spacer()
|
||||
if page.isExternalUrl {
|
||||
TextIndicator(text: "External")
|
||||
} else if page.isDraft {
|
||||
TextIndicator(text: "Draft", background: .yellow)
|
||||
} else {
|
||||
ForEach(page.missingContentLanguages, id: \.self) { language in
|
||||
TextIndicator(text: language.shortText, background: Color.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct LocalizedPageListItem: View {
|
||||
|
||||
@ObservedObject
|
||||
var page: LocalizedPage
|
||||
|
||||
var body: some View {
|
||||
Text(page.title)
|
||||
}
|
||||
}
|
||||
|
||||
struct PageListView: View {
|
||||
|
||||
@Environment(\.language)
|
||||
@ -31,13 +66,8 @@ struct PageListView: View {
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.horizontal, 8)
|
||||
List(filteredPages, selection: $selectedPage) { page in
|
||||
HStack {
|
||||
Text(page.title(in: language))
|
||||
Spacer()
|
||||
if page.isDraft {
|
||||
DraftIndicator()
|
||||
}
|
||||
}.tag(page)
|
||||
PageListItem(page: page)
|
||||
.tag(page)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
@ -17,18 +17,16 @@ struct AddPostView: View {
|
||||
@State
|
||||
private var newPostId = ""
|
||||
|
||||
private let allowedCharactersInPostId = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")).inverted
|
||||
|
||||
init(selected: Binding<Post?>) {
|
||||
self._selectedPost = selected
|
||||
}
|
||||
|
||||
private var idExists: Bool {
|
||||
content.posts.contains { $0.id == newPostId }
|
||||
!content.isNewIdForPost(newPostId)
|
||||
}
|
||||
|
||||
private var containsInvalidCharacters: Bool {
|
||||
newPostId.rangeOfCharacter(from: allowedCharactersInPostId) != nil
|
||||
private var isInvalidId: Bool {
|
||||
!content.isValidIdForTagOrPageOrPost(newPostId)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@ -45,7 +43,7 @@ struct AddPostView: View {
|
||||
} else if idExists {
|
||||
Text("A post with the same id already exists")
|
||||
.foregroundStyle(Color.red)
|
||||
} else if containsInvalidCharacters {
|
||||
} else if isInvalidId {
|
||||
Text("The id contains invalid characters")
|
||||
.foregroundStyle(Color.red)
|
||||
} else {
|
||||
@ -59,7 +57,7 @@ struct AddPostView: View {
|
||||
Button(action: addNewPost) {
|
||||
Text("Create")
|
||||
}
|
||||
.disabled(newPostId.isEmpty || containsInvalidCharacters || idExists)
|
||||
.disabled(isInvalidId || idExists)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
@ -35,7 +35,7 @@ struct PostListView: View {
|
||||
Text(post.title(in: language))
|
||||
Spacer()
|
||||
if post.isDraft {
|
||||
DraftIndicator()
|
||||
TextIndicator(text: "Draft")
|
||||
}
|
||||
}.tag(post)
|
||||
}
|
||||
|
@ -69,9 +69,6 @@ struct TagSelectionView: View {
|
||||
return
|
||||
}
|
||||
selected.remove(at: index)
|
||||
|
||||
let insertIndex = tags.firstIndex(where: { $0 > tag }) ?? tags.endIndex
|
||||
tags.insert(tag, at: insertIndex)
|
||||
}
|
||||
|
||||
private func select(tag: Tag) {
|
||||
|
@ -294,7 +294,7 @@ struct PageIssueView: View {
|
||||
}
|
||||
let modified = pageContent.replacingOccurrences(of: oldString, with: newString)
|
||||
|
||||
guard content.storage.save(pageContent: modified, for: page.id, language: language) else {
|
||||
guard content.storage.save(pageContent: modified, for: page.id, in: language) else {
|
||||
print("Replaced \(oldString) with \(newString) in page \(page.id) (\(language))")
|
||||
return
|
||||
}
|
||||
|
@ -14,24 +14,69 @@ struct AddTagView: View {
|
||||
@Binding
|
||||
var selectedTag: Tag?
|
||||
|
||||
@State
|
||||
private var newId = ""
|
||||
|
||||
init(selected: Binding<Tag?>) {
|
||||
self._selectedTag = selected
|
||||
}
|
||||
|
||||
private var idExists: Bool {
|
||||
!content.isNewIdForTag(newId)
|
||||
}
|
||||
|
||||
private var isInvalidId: Bool {
|
||||
!content.isValidIdForTagOrPageOrPost(newId)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Text("Creating tag...")
|
||||
.onAppear(perform: addNewTag)
|
||||
VStack {
|
||||
Text("New tag")
|
||||
.font(.headline)
|
||||
|
||||
TextField("", text: $newId)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.frame(maxWidth: 350)
|
||||
if newId.isEmpty {
|
||||
Text("Enter the id of the new tag to create")
|
||||
.foregroundStyle(.secondary)
|
||||
} else if isInvalidId {
|
||||
Text("The id contains invalid characters")
|
||||
.foregroundStyle(Color.red)
|
||||
} else if idExists {
|
||||
Text("A tag with the same id already exists")
|
||||
.foregroundStyle(Color.red)
|
||||
} else {
|
||||
Text("Create a new tag with the id")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
HStack {
|
||||
Button(role: .cancel, action: dismissSheet) {
|
||||
Text("Cancel")
|
||||
}
|
||||
Button(action: addNewTag) {
|
||||
Text("Create")
|
||||
}
|
||||
.disabled(isInvalidId || idExists)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
private func addNewTag() {
|
||||
let newTag = Tag(
|
||||
let tag = Tag(
|
||||
content: content,
|
||||
id: "tag",
|
||||
id: newId,
|
||||
isVisible: true,
|
||||
german: .init(content: content, urlComponent: "tag", name: "Neuer Tag"),
|
||||
english: .init(content: content, urlComponent: "tag-en", name: "New Tag"))
|
||||
german: .init(content: content, urlComponent: newId, name: newId),
|
||||
english: .init(content: content, urlComponent: "\(newId)-en", name: "\(newId)-en"))
|
||||
// Add to top of the list, and resort when changing the name
|
||||
content.tags.insert(newTag, at: 0)
|
||||
content.tags.insert(tag, at: 0)
|
||||
selectedTag = tag
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private func dismissSheet() {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
@ -25,13 +25,18 @@ struct TagListView: View {
|
||||
return content.tags.filter { $0.localized(in: language).name.contains(searchString) }
|
||||
}
|
||||
|
||||
private var filteredAndSortedTags: [Tag] {
|
||||
filteredTags.sorted { $0.title(in: language) }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
TextField("", text: $searchString, prompt: Text("Search"))
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.horizontal, 8)
|
||||
List(filteredTags, selection: $selectedTag) { tag in
|
||||
Text(tag.localized(in: language).title).tag(tag)
|
||||
List(filteredAndSortedTags, selection: $selectedTag) { tag in
|
||||
TagListItem(tag: tag.localized(in: language))
|
||||
.tag(tag)
|
||||
}
|
||||
}.onAppear {
|
||||
if selectedTag == nil {
|
||||
@ -40,3 +45,13 @@ struct TagListView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TagListItem: View {
|
||||
|
||||
@ObservedObject
|
||||
var tag: LocalizedTag
|
||||
|
||||
var body: some View {
|
||||
Text(tag.title)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user