Save and check page content automatically
This commit is contained in:
parent
41171c31db
commit
7ebc9d8404
@ -155,6 +155,14 @@ final class Page: Item, DateItem, LocalizedItem {
|
|||||||
content.storage.pageContent(for: id, language: language)
|
content.storage.pageContent(for: id, language: language)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeContent(in language: ContentLanguage) -> Bool {
|
||||||
|
guard content.storage.remove(pageContent: id, in: language) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
localized(in: language).hasContent = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func save(pageContent: String, in language: ContentLanguage) -> Bool {
|
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: id, in: language) else {
|
||||||
return false
|
return false
|
||||||
|
@ -77,6 +77,12 @@ final class Storage: ObservableObject {
|
|||||||
return contentScope.write(pageContent, to: path)
|
return contentScope.write(pageContent, to: path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func remove(pageContent pageId: String, in language: ContentLanguage) -> Bool {
|
||||||
|
guard let contentScope else { return false }
|
||||||
|
let path = pageContentPath(page: pageId, language: language)
|
||||||
|
return contentScope.deleteFile(at: path)
|
||||||
|
}
|
||||||
|
|
||||||
func save(page: Page.Data, for pageId: String) -> Bool {
|
func save(page: Page.Data, for pageId: String) -> Bool {
|
||||||
guard let contentScope else { return false }
|
guard let contentScope else { return false }
|
||||||
let path = pageMetadataPath(page: pageId)
|
let path = pageMetadataPath(page: pageId)
|
||||||
|
@ -2,6 +2,37 @@ import SwiftUI
|
|||||||
import SFSafeSymbols
|
import SFSafeSymbols
|
||||||
import HighlightedTextEditor
|
import HighlightedTextEditor
|
||||||
|
|
||||||
|
enum PageContentSaveStatus {
|
||||||
|
case isSaved
|
||||||
|
case failedToSave
|
||||||
|
case needsSave
|
||||||
|
case notLoaded
|
||||||
|
|
||||||
|
var symbol: SFSymbol {
|
||||||
|
switch self {
|
||||||
|
case .notLoaded:
|
||||||
|
return .questionmarkSquareDashed
|
||||||
|
case .isSaved:
|
||||||
|
return .checkmarkCircleFill
|
||||||
|
case .needsSave:
|
||||||
|
return .hourglassCircleFill
|
||||||
|
case .failedToSave:
|
||||||
|
return .exclamationmarkTriangleFill
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var color: Color {
|
||||||
|
switch self {
|
||||||
|
case .isSaved, .notLoaded:
|
||||||
|
return .green
|
||||||
|
case .needsSave:
|
||||||
|
return .yellow
|
||||||
|
case .failedToSave:
|
||||||
|
return .red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct LocalizedPageContentView: View {
|
struct LocalizedPageContentView: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
@ -15,36 +46,23 @@ struct LocalizedPageContentView: View {
|
|||||||
@State
|
@State
|
||||||
private var pageContent: String = ""
|
private var pageContent: String = ""
|
||||||
|
|
||||||
@State
|
|
||||||
private var pageContentUsedForGeneration: String = ""
|
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var generationResults: PageGenerationResults?
|
private var generationResults: PageGenerationResults?
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var didChangeContent = false
|
private var saveState: PageContentSaveStatus = .notLoaded
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var lastSave: Date = .now
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var lastModification: Date = .now
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack(alignment: .firstTextBaseline) {
|
|
||||||
Button(action: loadContent) {
|
|
||||||
Text("Load")
|
|
||||||
}
|
|
||||||
Button(action: saveContent) {
|
|
||||||
Text("Save")
|
|
||||||
}
|
|
||||||
Button(action: checkContent) {
|
|
||||||
Text("Check")
|
|
||||||
}.disabled(content.isGeneratingWebsite)
|
|
||||||
if content.isGeneratingWebsite {
|
|
||||||
ProgressView()
|
|
||||||
.scaleEffect(0.6)
|
|
||||||
.frame(height: 15)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
Image(systemSymbol: saveState.symbol)
|
||||||
|
.foregroundStyle(saveState.color)
|
||||||
if let generationResults {
|
if let generationResults {
|
||||||
PageContentResultsView(results: generationResults)
|
PageContentResultsView(results: generationResults)
|
||||||
}
|
}
|
||||||
@ -59,69 +77,94 @@ struct LocalizedPageContentView: View {
|
|||||||
text: $pageContent,
|
text: $pageContent,
|
||||||
highlightRules: .markdown)
|
highlightRules: .markdown)
|
||||||
.onChange(of: pageContent) {
|
.onChange(of: pageContent) {
|
||||||
didChangeContent = true
|
didChangeContent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear(perform: loadContent)
|
.onAppear(perform: loadContent)
|
||||||
.onDisappear(perform: saveContent)
|
.onDisappear { saveContentIfNeeded(isFinalSave: true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private func didChangeContent() {
|
||||||
|
if saveState == .notLoaded {
|
||||||
|
// Content was changed due to loading
|
||||||
|
saveState = .isSaved
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saveState = .needsSave
|
||||||
|
|
||||||
|
// Wait a few seconds for a save, to allow additional changes
|
||||||
|
// Reduces the number of saves
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10)) {
|
||||||
|
self.saveContentIfNeeded()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadContent() {
|
private func loadContent() {
|
||||||
let language = language
|
let language = language
|
||||||
guard page.localized(in: language).hasContent else {
|
guard page.localized(in: language).hasContent else {
|
||||||
pageContent = "New file"
|
pageContent = "New file"
|
||||||
DispatchQueue.main.async {
|
|
||||||
didChangeContent = false
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let content = page.pageContent(in: language) else {
|
guard let content = page.pageContent(in: language) else {
|
||||||
print("Failed to load page content")
|
print("Failed to load page content")
|
||||||
pageContent = "Failed to load"
|
pageContent = "Failed to load"
|
||||||
DispatchQueue.main.async {
|
|
||||||
didChangeContent = false
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard content != "" else {
|
guard content != "" else {
|
||||||
pageContent = "New file"
|
pageContent = "New file"
|
||||||
DispatchQueue.main.async {
|
|
||||||
didChangeContent = false
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pageContent = content
|
pageContent = content
|
||||||
checkContent()
|
checkContent()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
didChangeContent = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func saveContent() {
|
private func saveContentIfNeeded(isFinalSave: Bool = false) {
|
||||||
|
switch saveState {
|
||||||
|
case .isSaved, .notLoaded:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isFinalSave, Date.now.timeIntervalSince(lastModification) < 10 {
|
||||||
|
// Additional modification made
|
||||||
|
// Wait for next scheduled invocation of saveIfNeeded()
|
||||||
|
// if the overall unsaved time is not too long
|
||||||
|
if Date.now.timeIntervalSince(lastSave) < 60 {
|
||||||
|
//print("Waiting while modifying")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
print("Saving content after 30 seconds of modifications")
|
||||||
|
}
|
||||||
|
saveUnconditionally()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func saveUnconditionally() {
|
||||||
guard pageContent != "New file", pageContent != "" else {
|
guard pageContent != "New file", pageContent != "" else {
|
||||||
// TODO: Delete file?
|
guard page.removeContent(in: language) else {
|
||||||
return
|
print("Failed to remove empty content from disk")
|
||||||
}
|
saveState = .failedToSave
|
||||||
guard didChangeContent else {
|
return
|
||||||
|
}
|
||||||
|
saveState = .notLoaded
|
||||||
|
generationResults = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard page.save(pageContent: pageContent, in: language) else {
|
guard page.save(pageContent: pageContent, in: language) else {
|
||||||
print("Failed to save content")
|
print("Failed to save content")
|
||||||
|
saveState = .failedToSave
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
didChangeContent = false
|
saveState = .isSaved
|
||||||
|
checkContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func checkContent() {
|
private func checkContent() {
|
||||||
let content = self.pageContent
|
|
||||||
guard content != pageContentUsedForGeneration else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard !self.content.isGeneratingWebsite else {
|
guard !self.content.isGeneratingWebsite else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.content.check(content: content, of: page, for: language) {
|
self.content.check(content: pageContent, of: page, for: language) {
|
||||||
self.generationResults = $0
|
self.generationResults = $0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user