320 lines
9.4 KiB
Swift
320 lines
9.4 KiB
Swift
import SwiftUI
|
|
|
|
private struct ButtonAction {
|
|
|
|
let name: String
|
|
|
|
let action: () -> Void
|
|
}
|
|
|
|
private struct PopupSheet: View {
|
|
|
|
@Binding
|
|
var isPresented: Bool
|
|
|
|
@Binding
|
|
var title: String
|
|
|
|
@Binding
|
|
var message: String
|
|
|
|
var body: some View {
|
|
VStack {
|
|
Text(title)
|
|
.font(.headline)
|
|
Text(message)
|
|
Button("Dismiss") {
|
|
message = ""
|
|
isPresented = false
|
|
}
|
|
}.padding()
|
|
}
|
|
}
|
|
|
|
private struct PageIssueGenericView: View {
|
|
|
|
let issue: PageIssue
|
|
|
|
let buttons: [ButtonAction]
|
|
|
|
var body: some View {
|
|
HStack {
|
|
VStack(alignment: .leading) {
|
|
Text(issue.message.description)
|
|
Text("\(issue.title) (\(issue.language.rawValue.uppercased()))")
|
|
.font(.caption)
|
|
}
|
|
Spacer()
|
|
ForEach(buttons, id: \.name) { button in
|
|
Button(button.name, action: button.action)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct PageIssueView: View {
|
|
|
|
let issue: PageIssue
|
|
|
|
@EnvironmentObject
|
|
private var checker: PageIssueChecker
|
|
|
|
@EnvironmentObject
|
|
private var content: Content
|
|
|
|
@State
|
|
private var showPopupMessage = false
|
|
|
|
|
|
@State
|
|
private var popupTitle = "Error"
|
|
|
|
@State
|
|
private var popupMessage = ""
|
|
|
|
@State
|
|
private var showPagePicker = false
|
|
|
|
@State
|
|
private var selectedPage: Page?
|
|
|
|
@State
|
|
private var showFilePicker = false
|
|
|
|
@State
|
|
private var selectedFile: FileResource?
|
|
|
|
private var buttons: [ButtonAction] {
|
|
switch issue.message {
|
|
case .warning:
|
|
return [.init(name: "Retry", action: retryPageCheck)]
|
|
case .failedToLoadContent:
|
|
return [.init(name: "Retry", action: retryPageCheck)]
|
|
case .failedToParseContent:
|
|
return [.init(name: "Retry", action: retryPageCheck)]
|
|
case .missingFile(let missing, _):
|
|
return [
|
|
.init(name: "Select file", action: { selectFile(missingFile: missing) }),
|
|
.init(name: "Create external file", action: { createExternalFile(fileId: missing) })
|
|
]
|
|
case .missingPage(let missing, _):
|
|
return [
|
|
.init(name: "Select page", action: selectPage),
|
|
.init(name: "Create page", action: { createPage(pageId: missing) })
|
|
]
|
|
case .missingTag(let missing, _):
|
|
return [
|
|
.init(name: "Select tag", action: { selectTag(missingPage: missing) }),
|
|
.init(name: "Create tag", action: { createTag(tagId: missing) })
|
|
]
|
|
case .invalidCommand(_, let markdown):
|
|
return [.init(name: "Replace text", action: { replaceCommand(originalText: markdown) })]
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
PageIssueGenericView(issue: issue, buttons: buttons)
|
|
.sheet(isPresented: $showPopupMessage) {
|
|
PopupSheet(isPresented: $showPopupMessage, title: $popupTitle, message: $popupMessage)
|
|
}
|
|
.sheet(isPresented: $showPagePicker) {
|
|
if let page = selectedPage {
|
|
didSelect(page: page)
|
|
}
|
|
} content: {
|
|
PagePickerView(selectedPage: $selectedPage)
|
|
}
|
|
.sheet(isPresented: $showFilePicker) {
|
|
if let file = selectedFile {
|
|
didSelect(file: file)
|
|
}
|
|
} content: {
|
|
FileSelectionView(selectedFile: $selectedFile)
|
|
}
|
|
|
|
}
|
|
|
|
private func show(error: String) {
|
|
DispatchQueue.main.async {
|
|
self.popupTitle = "Error"
|
|
self.popupMessage = error
|
|
self.showPopupMessage = true
|
|
}
|
|
}
|
|
|
|
private func show(info: String) {
|
|
DispatchQueue.main.async {
|
|
self.popupTitle = "Info"
|
|
self.popupMessage = info
|
|
self.showPopupMessage = true
|
|
}
|
|
}
|
|
|
|
|
|
private func retryPageCheck() {
|
|
DispatchQueue.main.async {
|
|
checker.check(pages: content.pages, clearListBeforeStart: false)
|
|
}
|
|
}
|
|
|
|
private func selectFile(missingFile: String) {
|
|
selectedFile = nil
|
|
showFilePicker = true
|
|
}
|
|
|
|
private func didSelect(file newFile: FileResource) {
|
|
guard case .missingFile(let missingFile, let markdown) = issue.message else {
|
|
show(error: "Inconsistency: Selected file, but issue is not a missing file")
|
|
return
|
|
}
|
|
replace(missing: missingFile, with: newFile.id, in: markdown)
|
|
retryPageCheck()
|
|
DispatchQueue.main.async {
|
|
selectedFile = nil
|
|
}
|
|
}
|
|
|
|
private func createExternalFile(fileId: String) {
|
|
guard content.isValidIdForFile(fileId) else {
|
|
show(error: "Invalid file id, can't create external file")
|
|
return
|
|
}
|
|
|
|
let file = FileResource(
|
|
content: content,
|
|
id: fileId,
|
|
isExternallyStored: true,
|
|
english: "",
|
|
german: "")
|
|
content.add(file)
|
|
|
|
retryPageCheck()
|
|
}
|
|
|
|
private func selectPage() {
|
|
selectedPage = nil
|
|
showPagePicker = true
|
|
}
|
|
|
|
private func didSelect(page newPage: Page) {
|
|
guard case .missingPage(let missingPage, let markdown) = issue.message else {
|
|
show(error: "Inconsistency: Selected page, but issue is not a missing page")
|
|
return
|
|
}
|
|
|
|
replace(missing: missingPage, with: newPage.id, in: markdown)
|
|
retryPageCheck()
|
|
DispatchQueue.main.async {
|
|
selectedPage = nil
|
|
}
|
|
}
|
|
|
|
private func createPage(pageId: String) {
|
|
guard content.isValidIdForTagOrPageOrPost(pageId) else {
|
|
show(error: "Invalid page id, can't create page")
|
|
return
|
|
}
|
|
|
|
let deString = pageId + "-" + ContentLanguage.german.rawValue
|
|
|
|
let page = Page(
|
|
content: content,
|
|
id: pageId,
|
|
externalLink: nil,
|
|
isDraft: true,
|
|
createdDate: .now,
|
|
hideDate: false,
|
|
startDate: .now,
|
|
endDate: nil,
|
|
german: .init(content: content,
|
|
urlString: deString,
|
|
title: pageId),
|
|
english: .init(content: content,
|
|
urlString: pageId,
|
|
title: pageId),
|
|
tags: [],
|
|
requiredFiles: [])
|
|
content.pages.insert(page, at: 0)
|
|
|
|
retryPageCheck()
|
|
}
|
|
|
|
private func selectTag(missingPage: String) {
|
|
// TODO: Show sheet to select a tag
|
|
// TODO: Replace tag id in page content with new tag id
|
|
|
|
retryPageCheck()
|
|
}
|
|
|
|
private func createTag(tagId: String) {
|
|
guard content.isValidIdForTagOrPageOrPost(tagId) else {
|
|
show(error: "Invalid tag id, can't create tag")
|
|
return
|
|
}
|
|
|
|
let tag = Tag(content: content, id: tagId)
|
|
content.tags.append(tag)
|
|
|
|
retryPageCheck()
|
|
}
|
|
|
|
private func replaceCommand(originalText: String) {
|
|
// TODO: Show sheet with text input
|
|
// TODO: Replace original text in page content with new text
|
|
|
|
retryPageCheck()
|
|
}
|
|
|
|
// MARK: Page Content manipulation
|
|
|
|
private func replace(missing: String, with newText: String, in markdown: String) {
|
|
|
|
let newString = markdown.replacingOccurrences(of: missing, with: newText)
|
|
guard newString != markdown else {
|
|
show(error: "No change in content detected trying to perform replacement")
|
|
return
|
|
}
|
|
|
|
let occurrences = findOccurrences(of: markdown, in: issue.page, language: issue.language)
|
|
guard !occurrences.isEmpty else {
|
|
show(error: "No occurrences of '\(markdown)' found in the page")
|
|
return
|
|
}
|
|
replace(markdown, with: newString, in: issue.page, language: issue.language)
|
|
|
|
show(info: "Replaced \(occurrences.count) occurrences of '\(missing)' with '\(newText)'")
|
|
|
|
retryPageCheck()
|
|
}
|
|
|
|
private func replace(_ oldString: String, with newString: String, in page: Page, language: ContentLanguage) {
|
|
guard let pageContent = content.storage.pageContent(for: page.id, language: language) else {
|
|
print("Failed to replace in page \(page.id) (\(language)), no content")
|
|
return
|
|
}
|
|
let modified = pageContent.replacingOccurrences(of: oldString, with: newString)
|
|
|
|
guard content.storage.save(pageContent: modified, for: page.id, in: language) else {
|
|
print("Replaced \(oldString) with \(newString) in page \(page.id) (\(language))")
|
|
return
|
|
}
|
|
}
|
|
|
|
private func findOccurrences(of searchString: String, in page: Page, language: ContentLanguage) -> [String] {
|
|
guard let parts = content.storage.pageContent(for: page.id, language: language)?
|
|
.components(separatedBy: searchString) else {
|
|
print("Failed to get page content to find occurrences, no content")
|
|
return []
|
|
}
|
|
|
|
var occurrences: [String] = []
|
|
for index in parts.indices.dropLast() {
|
|
let start = parts[index].suffix(10)
|
|
let end = parts[index+1].prefix(10)
|
|
let full = "...\(start)\(searchString)\(end)...".replacingOccurrences(of: "\n", with: "\\n")
|
|
occurrences.append(full)
|
|
}
|
|
return occurrences
|
|
}
|
|
}
|