Add draft indicator, filter drafts, show issue count

This commit is contained in:
Christoph Hagen 2025-01-07 14:03:07 +01:00
parent 9d95e7d210
commit 508483071a
10 changed files with 124 additions and 32 deletions

View File

@ -247,6 +247,7 @@
E2FE0F6A2D2D2D55002963B7 /* LocalizedPageSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F692D2D2D4F002963B7 /* LocalizedPageSettingsFile.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 */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -485,6 +486,7 @@
E2FE0F692D2D2D4F002963B7 /* LocalizedPageSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageSettingsFile.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>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -699,6 +701,7 @@
E2A21C372CB9A4F10060935B /* Generic */ = {
isa = PBXGroup;
children = (
E2FE0F6F2D2D5231002963B7 /* DraftIndicator.swift */,
E2FE0F232D2A8C1A002963B7 /* TagDisplayView.swift */,
E229902F2D0F75CF009F8D77 /* BoolPropertyView.swift */,
E22990312D0F7678009F8D77 /* DatePropertyView.swift */,
@ -1151,6 +1154,7 @@
E29D313D2D047C1B0051B7F4 /* LocalizedPageContentView.swift in Sources */,
E2FE0F242D2A8C21002963B7 /* TagDisplayView.swift in Sources */,
E2A37D2D2CED2EF10000979F /* OptionalTextField.swift in Sources */,
E2FE0F702D2D5235002963B7 /* DraftIndicator.swift in Sources */,
E2A37D2B2CED2CC30000979F /* TagDetailView.swift in Sources */,
E2A21C282CB29B2A0060935B /* FeedEntryData.swift in Sources */,
E29D31942D0B7D280051B7F4 /* SimpleImage.swift in Sources */,

View File

@ -63,6 +63,15 @@ extension String {
return components(separatedBy: separator).dropLast().joined(separator: separator)
}
/**
Remove everything before the last separator.
Also removes the separator itself. If the separator is not contained in the string, then the full string is returned.
*/
func dropBeforeLast<T>(_ separator: T) -> String where T: StringProtocol {
components(separatedBy: separator).last!
}
func dropBeforeFirst(_ separator: String) -> String {
guard contains(separator) else {
return self

View File

@ -72,7 +72,7 @@ struct HtmlCommand: CommandProcessor {
}
for src in srcAttributes {
results.warning("Found image in html: \(src)")
findFile(path: src, source: "src of <img>")
}
}
@ -93,7 +93,7 @@ struct HtmlCommand: CommandProcessor {
if url.hasPrefix("http://") || url.hasPrefix("https://") {
results.externalLink(to: url)
} else {
results.warning("Relative link in HTML: \(url)")
findFile(path: url, source: "href of <a>")
}
}
}
@ -122,7 +122,7 @@ struct HtmlCommand: CommandProcessor {
}
for src in srcSets {
results.warning("Found source set in html: \(src)")
findFile(path: src, source: "srcset of <source>")
}
}
@ -138,17 +138,57 @@ struct HtmlCommand: CommandProcessor {
}
for src in srcAttributes {
guard content.isValidIdForFile(src) else {
results.warning("Found source in html: \(src)")
continue
}
guard let file = content.file(src) else {
results.warning("Found source in html: \(src)")
continue
}
#warning("Either find files by their full path, or replace file id with full path")
results.require(file: file)
findFile(path: src, source: "src of <source>")
}
}
private func findFile(path: String, source: String) {
let type = FileType(fileExtension: path.fileExtension)
guard path.hasPrefix("/") else {
findFileWith(relativePath: path, type: type, source: source)
return
}
guard !type.generatesImageVersions else {
// Try to determine image version needed
findImageVersion(path: path, type: type, source: source)
return
}
if findFile(withAbsolutePath: path) {
return
}
let fileId = path.dropBeforeLast("/")
if content.isValidIdForFile(fileId) {
results.missing(file: fileId, source: "HTML: \(source)")
} else {
results.warning("Could not find file '\(path)' for \(source)")
}
}
private func findFile(withAbsolutePath absolutePath: String) -> Bool {
guard let file = content.file(withOutputPath: absolutePath) else {
return false
}
results.require(file: file)
return true
}
private func findImageVersion(path: String, type: FileType, source: String) {
// First check if image original should be used
if findFile(withAbsolutePath: path) {
return
}
let fileId = path.dropAfterLast("/").dropBeforeLast("/")
guard let file = content.file(fileId) else {
results.missing(file: fileId, source: "HTML: \(source)")
return
}
results.warning("Could not determine image version for file '\(file.id)' for \(source)")
}
private func findFileWith(relativePath: String, type: FileType, source: String) {
results.warning("Could not determine relative file '\(relativePath)' for \(source)")
}
}

View File

@ -21,8 +21,11 @@ final class PostListPageGenerator {
}
func createPages(for posts: [Post]) {
// Sort by newest first
let posts = posts.sorted(ascending: false) { $0.startDate }
// Sort by newest first, filter drafts
let posts = posts
.filter { !$0.isDraft }
.sorted(ascending: false) { $0.startDate }
let totalCount = posts.count
guard totalCount > 0 else {
// Create one empty page

View File

@ -246,7 +246,7 @@ final class FileResource: Item {
/**
Get the url path to a file in the output folder.
The result is an absolute path from the output folder for use in HTML.
The result is an absolute path from the output folder for use in HTML, including a leading slash
*/
var absoluteUrl: String {
if let customOutputPath {

View File

@ -180,6 +180,15 @@ enum FileType: String {
category == .image
}
var generatesImageVersions: Bool {
switch self {
case .jpg, .png, .avif, .webp, .tiff:
return true
default:
return false
}
}
var isVideo: Bool {
category == .video
}

View File

@ -0,0 +1,15 @@
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)
)
}
}

View File

@ -31,7 +31,13 @@ struct PageListView: View {
.textFieldStyle(.roundedBorder)
.padding(.horizontal, 8)
List(filteredPages, selection: $selectedPage) { page in
Text(page.localized(in: language).title).tag(page)
HStack {
Text(page.title(in: language))
Spacer()
if page.isDraft {
DraftIndicator()
}
}.tag(page)
}
}
.onAppear {

View File

@ -31,7 +31,13 @@ struct PostListView: View {
.textFieldStyle(.roundedBorder)
.padding(.horizontal, 8)
List(filteredPosts, selection: $selectedPost) { post in
Text(post.title(in: language)).tag(post)
HStack {
Text(post.title(in: language))
Spacer()
if post.isDraft {
DraftIndicator()
}
}.tag(post)
}
}.onAppear {
if selectedPost == nil {

View File

@ -61,56 +61,56 @@ struct GenerationContentView: View {
Text("\(content.results.requiredFiles.count) files")
}
List {
Section("Empty pages") {
ForEach(content.results.emptyPages.sorted()) { id in
Text("\(id.pageId) (\(id.language))")
}
}
Section("Inaccessible files") {
Section("Inaccessible files (\(content.results.inaccessibleFiles.count))") {
ForEach(content.results.inaccessibleFiles.sorted()) { file in
Text(file.id)
}
}
Section("Unparsable files") {
Section("Unparsable files (\(content.results.unparsableFiles.count))") {
ForEach(content.results.unparsableFiles.sorted()) { file in
Text(file.id)
}
}
Section("Missing files") {
Section("Missing files (\(content.results.missingFiles.count))") {
ForEach(content.results.missingFiles.sorted(), id: \.self) { file in
Text(file)
}
}
Section("Missing tags") {
Section("Missing tags (\(content.results.missingTags.count))") {
ForEach(content.results.missingTags.sorted(), id: \.self) { tag in
Text(tag)
}
}
Section("Missing pages") {
Section("Missing pages (\(content.results.missingPages.count))") {
ForEach(content.results.missingPages.sorted(), id: \.self) { page in
Text(page)
}
}
Section("Invalid commands") {
Section("Invalid commands (\(content.results.invalidCommands.count))") {
ForEach(content.results.invalidCommands.sorted(), id: \.self) { markdown in
Text(markdown)
}
}
Section("Invalid blocks") {
Section("Invalid blocks (\(content.results.invalidBlocks.count))") {
ForEach(content.results.invalidBlocks.sorted(), id: \.self) { markdown in
Text(markdown)
}
}
Section("Warnings") {
Section("Warnings (\(content.results.warnings.count))") {
ForEach(content.results.warnings.sorted(), id: \.self) { warning in
Text(warning)
}
}
Section("Unsaved output files") {
Section("Unsaved output files (\(content.results.unsavedOutputFiles.count))") {
ForEach(content.results.unsavedOutputFiles.sorted(), id: \.self) { file in
Text(file)
}
}
Section("Empty pages (\(content.results.emptyPages.count))") {
ForEach(content.results.emptyPages.sorted()) { id in
Text("\(id.pageId) (\(id.language))")
}
}
}
}.padding()
}