External files, improve page generation
This commit is contained in:
@ -73,9 +73,11 @@ struct AddPageView: View {
|
||||
createdDate: .now,
|
||||
startDate: .now,
|
||||
endDate: nil,
|
||||
german: .init(urlString: "seite",
|
||||
german: .init(content: content,
|
||||
urlString: "seite",
|
||||
title: "Ein Titel"),
|
||||
english: .init(urlString: "page",
|
||||
english: .init(content: content,
|
||||
urlString: "page",
|
||||
title: "A Title"),
|
||||
tags: [])
|
||||
content.pages.insert(page, at: 0)
|
||||
|
@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
import HighlightedTextEditor
|
||||
|
||||
struct LocalizedPageContentView: View {
|
||||
@ -11,17 +12,21 @@ struct LocalizedPageContentView: View {
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@State
|
||||
private var isGeneratingWebsite = false
|
||||
|
||||
@State
|
||||
private var loadedPageContentLanguage: ContentLanguage?
|
||||
|
||||
@State
|
||||
private var pageContent: String = ""
|
||||
|
||||
@State
|
||||
private var didLoadContent = false
|
||||
private var pageContentUsedForGeneration: String = ""
|
||||
|
||||
@State
|
||||
private var generationResults = PageGenerationResults()
|
||||
|
||||
|
||||
init(pageId: String, page: LocalizedPage) {
|
||||
self.pageId = pageId
|
||||
@ -41,8 +46,12 @@ struct LocalizedPageContentView: View {
|
||||
Button(action: saveContent) {
|
||||
Text("Save")
|
||||
}
|
||||
Button(action: checkContent) {
|
||||
Text("Check")
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
PageContentResultsView(results: generationResults)
|
||||
HighlightedTextEditor(
|
||||
text: $pageContent,
|
||||
highlightRules: .markdown)
|
||||
@ -53,32 +62,50 @@ struct LocalizedPageContentView: View {
|
||||
}
|
||||
|
||||
private func loadContent() {
|
||||
let language = language
|
||||
do {
|
||||
let content = try content.storage.pageContent(for: pageId, language: language)
|
||||
let content = try page.content.storage.pageContent(for: pageId, language: language)
|
||||
|
||||
guard content != "" else {
|
||||
pageContent = "New file"
|
||||
didLoadContent = false
|
||||
loadedPageContentLanguage = nil
|
||||
return
|
||||
}
|
||||
pageContent = content
|
||||
didLoadContent = true
|
||||
|
||||
loadedPageContentLanguage = language
|
||||
checkContent()
|
||||
} catch {
|
||||
print("Failed to load page content: \(error)")
|
||||
pageContent = "Failed to load"
|
||||
loadedPageContentLanguage = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func saveContent() {
|
||||
guard didLoadContent else {
|
||||
guard let loadedPageContentLanguage else {
|
||||
return
|
||||
}
|
||||
do {
|
||||
try content.storage.save(pageContent: pageContent, for: pageId, language: language)
|
||||
try page.content.storage.save(pageContent: pageContent, for: pageId, language: loadedPageContentLanguage)
|
||||
} catch {
|
||||
print("Failed to save content: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func checkContent() {
|
||||
let content = self.pageContent
|
||||
guard content != pageContentUsedForGeneration else {
|
||||
return
|
||||
}
|
||||
isGeneratingWebsite = true
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
let generator = PageContentParser(content: page.content, language: language)
|
||||
_ = generator.generatePage(from: content)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.generationResults = generator.results
|
||||
isGeneratingWebsite = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,22 +4,47 @@ import SFSafeSymbols
|
||||
struct LocalizedPageDetailView: View {
|
||||
|
||||
@ObservedObject
|
||||
private var item: LocalizedPage
|
||||
private var page: LocalizedPage
|
||||
|
||||
init(page: LocalizedPage, showImagePicker: Bool = false) {
|
||||
self.item = page
|
||||
self.page = page
|
||||
self.showImagePicker = showImagePicker
|
||||
self.newUrlString = page.urlString
|
||||
}
|
||||
|
||||
@State
|
||||
private var showImagePicker = false
|
||||
|
||||
@State
|
||||
private var newUrlString: String
|
||||
|
||||
private let allowedCharactersInPostId = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")).inverted
|
||||
|
||||
private var idExists: Bool {
|
||||
page.content.pages.contains {
|
||||
$0.german.urlString == newUrlString
|
||||
|| $0.english.urlString == newUrlString
|
||||
}
|
||||
}
|
||||
|
||||
private var containsInvalidCharacters: Bool {
|
||||
newUrlString.rangeOfCharacter(from: allowedCharactersInPostId) != nil
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
TextField("", text: $newUrlString)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
Button("Update", action: setNewId)
|
||||
.disabled(newUrlString.isEmpty || containsInvalidCharacters || idExists)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
Text("Link Preview Title")
|
||||
.font(.headline)
|
||||
OptionalTextField("", text: $item.linkPreviewTitle,
|
||||
prompt: item.title)
|
||||
OptionalTextField("", text: $page.linkPreviewTitle,
|
||||
prompt: page.title)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.bottom)
|
||||
|
||||
@ -35,13 +60,13 @@ struct LocalizedPageDetailView: View {
|
||||
IconButton(symbol: .trashCircleFill,
|
||||
size: 22,
|
||||
color: .red) {
|
||||
item.linkPreviewImage = nil
|
||||
}.disabled(item.linkPreviewImage == nil)
|
||||
page.linkPreviewImage = nil
|
||||
}.disabled(page.linkPreviewImage == nil)
|
||||
Spacer()
|
||||
}
|
||||
|
||||
.buttonStyle(.plain)
|
||||
if let image = item.linkPreviewImage {
|
||||
if let image = page.linkPreviewImage {
|
||||
image.imageToDisplay
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
@ -52,16 +77,20 @@ struct LocalizedPageDetailView: View {
|
||||
Text("Link Preview Description")
|
||||
.font(.headline)
|
||||
.padding(.top)
|
||||
OptionalDescriptionField(text: $item.linkPreviewDescription)
|
||||
OptionalDescriptionField(text: $page.linkPreviewDescription)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.bottom)
|
||||
}
|
||||
.sheet(isPresented: $showImagePicker) {
|
||||
ImagePickerView(showImagePicker: $showImagePicker) { image in
|
||||
item.linkPreviewImage = image
|
||||
page.linkPreviewImage = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setNewId() {
|
||||
page.urlString = newUrlString
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
118
CHDataManagement/Views/Pages/PageContentResultsView.swift
Normal file
118
CHDataManagement/Views/Pages/PageContentResultsView.swift
Normal file
@ -0,0 +1,118 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
private struct ListPopup: View {
|
||||
|
||||
@Environment(\.dismiss)
|
||||
var dismiss
|
||||
|
||||
let items: [String]
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
ForEach(items, id: \.self) { page in
|
||||
Text(page)
|
||||
}
|
||||
}
|
||||
.frame(minHeight: min(CGFloat(items.count) * 31, 500))
|
||||
Button("Dismiss") { dismiss() }
|
||||
}
|
||||
.padding(.vertical)
|
||||
.onTapGesture {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TextWithPopup: View {
|
||||
|
||||
let symbol: SFSymbol
|
||||
|
||||
let text: LocalizedStringKey
|
||||
|
||||
let items: [String]
|
||||
|
||||
@State
|
||||
private var isHovering = false
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Image(systemSymbol: symbol)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 16, height: 16)
|
||||
Text(text)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
if items.count > 0 {
|
||||
isHovering.toggle()
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $isHovering) {
|
||||
ListPopup(items: items)
|
||||
.onTapGesture {
|
||||
isHovering.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PageContentResultsView: View {
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@ObservedObject
|
||||
var results: PageGenerationResults
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
TextWithPopup(
|
||||
symbol: .photoOnRectangleAngled,
|
||||
text: "\(results.files.count + results.missingFiles.count) images and files",
|
||||
items: results.files.sorted().map { $0.id })
|
||||
.foregroundStyle(.secondary)
|
||||
TextWithPopup(
|
||||
symbol: .docBadgePlus,
|
||||
text: "\(results.linkedPages.count + results.missingPages.count) page links",
|
||||
items: results.linkedPages.sorted().map { $0.localized(in: language).title })
|
||||
.foregroundStyle(.secondary)
|
||||
if !results.missingPages.isEmpty {
|
||||
TextWithPopup(
|
||||
symbol: .exclamationmarkTriangleFill,
|
||||
text: "\(results.missingPages.count) missing pages",
|
||||
items: results.missingPages.sorted())
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
if !results.missingFiles.isEmpty {
|
||||
TextWithPopup(
|
||||
symbol: .exclamationmarkTriangleFill,
|
||||
text: "\(results.missingFiles.count) missing files",
|
||||
items: results.missingFiles.sorted())
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
if !results.warnings.isEmpty {
|
||||
TextWithPopup(
|
||||
symbol: .exclamationmarkTriangleFill,
|
||||
text: "\(results.warnings.count) errors",
|
||||
items: results.warnings.sorted())
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
if !results.invalidCommandArguments.isEmpty {
|
||||
TextWithPopup(
|
||||
symbol: .exclamationmarkTriangleFill,
|
||||
text: "\(results.invalidCommandArguments.count) errors",
|
||||
items: results.invalidCommandArguments.map {
|
||||
"\($0.command.rawValue): \($0.arguments.joined(separator: ";"))"
|
||||
})
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
PageContentResultsView(results: .init())
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
struct PageDetailView: View {
|
||||
|
||||
@ -17,6 +18,9 @@ struct PageDetailView: View {
|
||||
@State
|
||||
private var newId: String
|
||||
|
||||
@State
|
||||
private var didGenerateWebsite: Bool?
|
||||
|
||||
init(page: Page) {
|
||||
self.page = page
|
||||
self.newId = page.id
|
||||
@ -35,10 +39,21 @@ struct PageDetailView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
Button(action: generate) {
|
||||
Text("Generate")
|
||||
HStack {
|
||||
Button(action: generate) {
|
||||
Text("Generate")
|
||||
}
|
||||
.disabled(isGeneratingWebsite)
|
||||
if let didGenerateWebsite {
|
||||
if didGenerateWebsite {
|
||||
Image(systemSymbol: .checkmarkCircleFill)
|
||||
.foregroundStyle(.green)
|
||||
} else {
|
||||
Image(systemSymbol: .xmarkCircleFill)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(isGeneratingWebsite)
|
||||
HStack {
|
||||
TextField("", text: $newId)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
@ -86,6 +101,7 @@ struct PageDetailView: View {
|
||||
}
|
||||
|
||||
LocalizedPageDetailView(page: page.localized(in: language))
|
||||
.id(page.id + language.rawValue)
|
||||
|
||||
}
|
||||
.padding()
|
||||
@ -106,11 +122,13 @@ struct PageDetailView: View {
|
||||
isGeneratingWebsite = true
|
||||
print("Generating page")
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let generator = WebsiteGenerator(
|
||||
content: content,
|
||||
language: language)
|
||||
if !generator.generate(page: page) {
|
||||
print("Generation failed")
|
||||
for language in ContentLanguage.allCases {
|
||||
let generator = LocalizedWebsiteGenerator(
|
||||
content: content,
|
||||
language: language)
|
||||
if !generator.generate(page: page) {
|
||||
print("Generation failed")
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
isGeneratingWebsite = false
|
||||
|
Reference in New Issue
Block a user