consolidate tags, hide date
This commit is contained in:
parent
93e642c3c9
commit
5ac5a7b000
@ -213,6 +213,7 @@
|
||||
E2FE0F1E2D281AE1002963B7 /* TagOverviewGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F1D2D281ACE002963B7 /* TagOverviewGenerator.swift */; };
|
||||
E2FE0F202D29A70E002963B7 /* Array+Remove.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F1F2D29A709002963B7 /* Array+Remove.swift */; };
|
||||
E2FE0F222D2A84A0002963B7 /* VideoCommandProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F212D2A849B002963B7 /* VideoCommandProcessor.swift */; };
|
||||
E2FE0F242D2A8C21002963B7 /* TagDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F232D2A8C1A002963B7 /* TagDisplayView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@ -417,6 +418,7 @@
|
||||
E2FE0F1D2D281ACE002963B7 /* TagOverviewGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagOverviewGenerator.swift; sourceTree = "<group>"; };
|
||||
E2FE0F1F2D29A709002963B7 /* Array+Remove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Remove.swift"; sourceTree = "<group>"; };
|
||||
E2FE0F212D2A849B002963B7 /* VideoCommandProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCommandProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F232D2A8C1A002963B7 /* TagDisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagDisplayView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -638,6 +640,7 @@
|
||||
E2A21C372CB9A4F10060935B /* Generic */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F232D2A8C1A002963B7 /* TagDisplayView.swift */,
|
||||
E229902F2D0F75CF009F8D77 /* BoolPropertyView.swift */,
|
||||
E22990312D0F7678009F8D77 /* DatePropertyView.swift */,
|
||||
E29D312F2D03A2BD0051B7F4 /* DescriptionField.swift */,
|
||||
@ -998,6 +1001,7 @@
|
||||
E229904E2D13535C009F8D77 /* SecurityBookmark.swift in Sources */,
|
||||
E2A21C082CB17B870060935B /* TagView.swift in Sources */,
|
||||
E29D313D2D047C1B0051B7F4 /* LocalizedPageContentView.swift in Sources */,
|
||||
E2FE0F242D2A8C21002963B7 /* TagDisplayView.swift in Sources */,
|
||||
E2A37D2D2CED2EF10000979F /* OptionalTextField.swift in Sources */,
|
||||
E2A37D2B2CED2CC30000979F /* TagDetailView.swift in Sources */,
|
||||
E2A21C282CB29B2A0060935B /* FeedEntryData.swift in Sources */,
|
||||
|
@ -58,7 +58,7 @@ final class FeedPageGenerator {
|
||||
header: pageHeader,
|
||||
additionalFooter: footer) { content in
|
||||
if showTitle {
|
||||
content += "<h1>\(title)</h1>"
|
||||
content += "<h1 class='separated-headline'>\(title)</h1>"
|
||||
}
|
||||
for post in posts {
|
||||
content += FeedEntry(data: post).content
|
||||
|
@ -80,7 +80,9 @@ final class PageGenerator {
|
||||
additionalFooter: results.requiredFooters.sorted().joined()) { content in
|
||||
content += "<article>"
|
||||
if !localized.hideTitle {
|
||||
content += "<h3>\(page.dateText(in: language))</h3>"
|
||||
if !page.hideDate {
|
||||
content += "<h3>\(page.dateText(in: language))</h3>"
|
||||
}
|
||||
content += "<h1>\(localized.title)</h1>"
|
||||
content += TagList(tags: tags).content
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ final class TagOverviewGenerator {
|
||||
let page = GenericPage(
|
||||
header: pageHeader,
|
||||
additionalFooter: "") { content in
|
||||
content += "<h1>\(header.title)</h1>"
|
||||
content += "<h1 class='separated-headline'>\(header.title)</h1>"
|
||||
for tag in tags {
|
||||
let description = tag.localized.description ?? ""
|
||||
let image = self.makePageImage(item: tag.localized)
|
||||
|
@ -99,6 +99,28 @@ final class PageGenerationResults: ObservableObject {
|
||||
pageIsEmpty = false
|
||||
}
|
||||
|
||||
func reset() {
|
||||
inaccessibleFiles = []
|
||||
unparsableFiles = [:]
|
||||
missingFiles = [:]
|
||||
missingLinkedFiles = [:]
|
||||
missingLinkedTags = [:]
|
||||
missingLinkedPages = [:]
|
||||
requiredHeaders = []
|
||||
requiredFooters = []
|
||||
requiredIcons = []
|
||||
linkedPages = []
|
||||
linkedTags = []
|
||||
externalLinks = []
|
||||
usedFiles = []
|
||||
requiredFiles = []
|
||||
imagesToGenerate = []
|
||||
invalidCommands = []
|
||||
warnings = []
|
||||
unsavedOutputFiles = [:]
|
||||
pageIsEmpty = false
|
||||
}
|
||||
|
||||
// MARK: Adding entries
|
||||
|
||||
func inaccessibleContent(file: FileResource) {
|
||||
|
@ -38,37 +38,40 @@ final class PostListPageGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private func createPostFeedPage(_ pageIndex: Int, pageCount: Int, posts: ArraySlice<Post>) {
|
||||
let posts: [FeedEntryData] = posts.map { post in
|
||||
let localized: LocalizedPost = post.localized(in: language)
|
||||
private func makePostData(post: Post) -> FeedEntryData {
|
||||
let localized: LocalizedPost = post.localized(in: language)
|
||||
|
||||
#warning("Add post link text to settings or to each post")
|
||||
let linkUrl = post.linkedPage.map {
|
||||
FeedEntryData.Link(
|
||||
url: $0.absoluteUrl(in: language),
|
||||
text: language == .english ? "View" : "Anzeigen")
|
||||
}
|
||||
|
||||
let tags: [FeedEntryData.Tag] = post.tags.filter { $0.isVisible }.map { tag in
|
||||
.init(name: tag.localized(in: language).name,
|
||||
url: tag.absoluteUrl(in: language))
|
||||
}
|
||||
|
||||
let images = localized.images.map { image in
|
||||
image.imageSet(width: mainContentMaximumWidth, height: mainContentMaximumWidth, language: language)
|
||||
}
|
||||
images.forEach(source.results.require)
|
||||
|
||||
return FeedEntryData(
|
||||
entryId: post.id,
|
||||
title: localized.title,
|
||||
textAboveTitle: post.dateText(in: language),
|
||||
link: linkUrl,
|
||||
tags: tags,
|
||||
text: localized.text.components(separatedBy: "\n"),
|
||||
images: images)
|
||||
#warning("Add post link text to settings or to each post")
|
||||
let linkUrl = post.linkedPage.map {
|
||||
FeedEntryData.Link(
|
||||
url: $0.absoluteUrl(in: language),
|
||||
text: language == .english ? "View" : "Anzeigen")
|
||||
}
|
||||
|
||||
// Use the tags of the page if one is linked
|
||||
let tags: [FeedEntryData.Tag] = post.usedTags().filter { $0.isVisible }.map { tag in
|
||||
.init(name: tag.localized(in: language).name,
|
||||
url: tag.absoluteUrl(in: language))
|
||||
}
|
||||
|
||||
let images = localized.images.map { image in
|
||||
image.imageSet(width: mainContentMaximumWidth, height: mainContentMaximumWidth, language: language)
|
||||
}
|
||||
images.forEach(source.results.require)
|
||||
|
||||
return FeedEntryData(
|
||||
entryId: post.id,
|
||||
title: localized.title,
|
||||
textAboveTitle: post.dateText(in: language),
|
||||
link: linkUrl,
|
||||
tags: tags,
|
||||
text: localized.text.components(separatedBy: "\n\n"),
|
||||
images: images)
|
||||
}
|
||||
|
||||
private func createPostFeedPage(_ pageIndex: Int, pageCount: Int, posts: ArraySlice<Post>) {
|
||||
let posts: [FeedEntryData] = posts.map(makePostData)
|
||||
|
||||
let feedPageGenerator = FeedPageGenerator(content: source.content, results: source.results)
|
||||
|
||||
let languageButtonUrl = "/" + pageUrl(in: language.next, pageNumber: pageIndex)
|
||||
|
@ -1,29 +1,40 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
#warning("Fix podcast")
|
||||
#warning("Fix endeavor basics (image compare)")
|
||||
#warning("Fix cap mosaic GIF")
|
||||
/**
|
||||
**Content**
|
||||
- Podcast: Fix audio player, preview image
|
||||
- Endeavor Basics: -> image compare command
|
||||
- Article Cap Mosaic: -> GIF feature
|
||||
- iPhone Backgrounds: Add page, html
|
||||
|
||||
#warning("Add custom url string to external files (optional)")
|
||||
#warning("Show all warnings on page content")
|
||||
#warning("Button to delete file, show in finder, replace, mark changed (-> images)")
|
||||
#warning("Transfer images of posts to other language")
|
||||
#warning("Show tag selection view for pages")
|
||||
#warning("Button to replace files")
|
||||
#warning("Replace links to files inside pages when id changes")
|
||||
#warning("Calculate file sizes")
|
||||
#warning("Specify image aspect ratio to prevent page jumps")
|
||||
#warning("Add version and source url properties to file resources")
|
||||
#warning("Show errors during loading of content")
|
||||
#warning("Generate pages for posts")
|
||||
#warning("Clean up mock content")
|
||||
#warning("Show posts linking to a page")
|
||||
#warning("Add author to settings and page headers")
|
||||
#warning("Check for files in output folder not generated by app")
|
||||
#warning("Fix GIFs: Don't rescale, don't use image set")
|
||||
#warning("Add view to browse images")
|
||||
#warning("Show warnings for empty item properties")
|
||||
**UI**
|
||||
- Image search: Add view to see all images and filter
|
||||
- Pages: Show linking posts
|
||||
- Page Content: Show all results of `PageGenerationResults`
|
||||
- Files: Show usages of file
|
||||
- Images: Show list of generated versions
|
||||
|
||||
**Features**
|
||||
- GIF Support (No image set, don't rescale)
|
||||
- Image compare command ``
|
||||
- Files: Optional Property `customFilePath` for external files to place them in another location
|
||||
- Files: Property `version` and `sourceUrl` to track asset files
|
||||
- Posts: Generate separate pages for posts to link to
|
||||
- Settings: Introduce `Authors` (`name`, `image`, `description`)
|
||||
- Page: Property `author`
|
||||
- Video: Specify versions
|
||||
|
||||
**Generation**
|
||||
- ImageSet: Specify image aspect ratio (width, height) to prevent page jumps
|
||||
- Consistency: Check output folder for unused files
|
||||
- Empty properties: Show warnings for empty link previews, etc.
|
||||
|
||||
**Fixes**
|
||||
- Files: Id change: Check all page contents for links to the renamed file and replace occurences
|
||||
- Database: Show errors during loading
|
||||
- Mock content: Clean and improve
|
||||
*/
|
||||
|
||||
@main
|
||||
struct MainView: App {
|
||||
|
@ -34,8 +34,10 @@ extension Content {
|
||||
func check(content: String, of page: Page, for language: ContentLanguage, onComplete: @escaping (PageGenerationResults) -> Void) {
|
||||
performGenerationIfIdle {
|
||||
let results = self.results.makeResults(for: page, in: language)
|
||||
results.reset()
|
||||
let generator = PageContentParser(content: page.content, language: language, results: results)
|
||||
_ = generator.generatePage(from: content)
|
||||
self.results.recalculate()
|
||||
DispatchQueue.main.async {
|
||||
onComplete(results)
|
||||
}
|
||||
@ -227,7 +229,7 @@ extension Content {
|
||||
for language in ContentLanguage.allCases {
|
||||
let results = results.makeResults(for: tag, in: language)
|
||||
|
||||
let posts = posts.filter { $0.tags.contains(tag) }
|
||||
let posts = posts.filter { $0.contains(tag) }
|
||||
guard posts.count > 0 else { continue }
|
||||
|
||||
let source = TagPageGeneratorSource(
|
||||
|
@ -180,6 +180,7 @@ extension Content {
|
||||
externalLink: page.externalLink,
|
||||
isDraft: page.isDraft,
|
||||
createdDate: page.createdDate,
|
||||
hideDate: page.hideDate ?? false,
|
||||
startDate: page.startDate,
|
||||
endDate: page.endDate,
|
||||
german: convert(page.german, images: files),
|
||||
|
@ -59,6 +59,7 @@ private extension Page {
|
||||
.init(isDraft: isDraft,
|
||||
externalLink: externalLink,
|
||||
tags: tags.map { $0.id },
|
||||
hideDate: hideDate ? true : nil,
|
||||
createdDate: createdDate,
|
||||
startDate: startDate,
|
||||
endDate: hasEndDate ? endDate : nil,
|
||||
|
@ -49,6 +49,11 @@ final class LocalizedTag: ObservableObject {
|
||||
content.isValidIdForTagOrPageOrPost(urlComponent) &&
|
||||
!content.containsTag(withUrlComponent: urlComponent)
|
||||
}
|
||||
|
||||
/// The title to display when considering multiple items of this tag
|
||||
var title: String {
|
||||
linkPreviewTitle ?? name
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalizedTag: LinkPreviewItem {
|
||||
|
@ -17,6 +17,9 @@ final class Page: Item {
|
||||
@Published
|
||||
var createdDate: Date
|
||||
|
||||
@Published
|
||||
var hideDate: Bool
|
||||
|
||||
@Published
|
||||
var startDate: Date
|
||||
|
||||
@ -46,6 +49,7 @@ final class Page: Item {
|
||||
externalLink: String?,
|
||||
isDraft: Bool,
|
||||
createdDate: Date,
|
||||
hideDate: Bool,
|
||||
startDate: Date,
|
||||
endDate: Date?,
|
||||
german: LocalizedPage,
|
||||
@ -55,6 +59,7 @@ final class Page: Item {
|
||||
self.externalLink = externalLink
|
||||
self.isDraft = isDraft
|
||||
self.createdDate = createdDate
|
||||
self.hideDate = hideDate
|
||||
self.startDate = startDate
|
||||
self.hasEndDate = endDate != nil
|
||||
self.endDate = endDate ?? startDate
|
||||
@ -86,6 +91,34 @@ final class Page: Item {
|
||||
externalLink != nil
|
||||
}
|
||||
|
||||
// MARK: Tags
|
||||
|
||||
/**
|
||||
Check if a tag is associated with this page
|
||||
*/
|
||||
func contains(_ tag: Tag) -> Bool {
|
||||
tags.contains(tag)
|
||||
}
|
||||
|
||||
func toggle(_ tag: Tag) {
|
||||
guard let index = tags.firstIndex(of: tag) else {
|
||||
tags.append(tag)
|
||||
return
|
||||
}
|
||||
tags.remove(at: index)
|
||||
}
|
||||
|
||||
func remove(_ tag: Tag) {
|
||||
tags.remove(tag)
|
||||
}
|
||||
|
||||
func associate(_ tag: Tag) {
|
||||
guard !tags.contains(tag) else {
|
||||
return
|
||||
}
|
||||
tags.append(tag)
|
||||
}
|
||||
|
||||
// MARK: Paths
|
||||
|
||||
override func absoluteUrl(in language: ContentLanguage) -> String {
|
||||
|
@ -17,6 +17,12 @@ final class Post: Item {
|
||||
@Published
|
||||
var endDate: Date
|
||||
|
||||
/**
|
||||
The tags associated with the post
|
||||
|
||||
This list is only used if ``linkedPage`` is `nil`,
|
||||
otherwise the tag list of the linked page is used.
|
||||
*/
|
||||
@Published
|
||||
var tags: [Tag]
|
||||
|
||||
@ -52,6 +58,55 @@ final class Post: Item {
|
||||
super.init(content: content, id: id)
|
||||
}
|
||||
|
||||
// MARK: Tags
|
||||
|
||||
func usedTags() -> [Tag] {
|
||||
linkedPage?.tags ?? tags
|
||||
}
|
||||
|
||||
/**
|
||||
Check if a tag is associated with this post.
|
||||
|
||||
If the post is linked to a page, then the tags of the page are checked.
|
||||
*/
|
||||
func contains(_ tag: Tag) -> Bool {
|
||||
guard let linkedPage else {
|
||||
return tags.contains(tag)
|
||||
}
|
||||
return linkedPage.tags.contains(tag)
|
||||
}
|
||||
|
||||
func toggle(_ tag: Tag) {
|
||||
if let linkedPage {
|
||||
linkedPage.toggle(tag)
|
||||
return
|
||||
}
|
||||
guard let index = tags.firstIndex(of: tag) else {
|
||||
tags.append(tag)
|
||||
return
|
||||
}
|
||||
tags.remove(at: index)
|
||||
}
|
||||
|
||||
func remove(_ tag: Tag) {
|
||||
if let linkedPage {
|
||||
linkedPage.remove(tag)
|
||||
return
|
||||
}
|
||||
tags.remove(tag)
|
||||
}
|
||||
|
||||
func associate(_ tag: Tag) {
|
||||
if let linkedPage {
|
||||
linkedPage.associate(tag)
|
||||
return
|
||||
}
|
||||
guard !tags.contains(tag) else {
|
||||
return
|
||||
}
|
||||
tags.append(tag)
|
||||
}
|
||||
|
||||
func localized(in language: ContentLanguage) -> LocalizedPost {
|
||||
switch language {
|
||||
case .english: return english
|
||||
|
@ -56,7 +56,7 @@ final class Tag: Item {
|
||||
}
|
||||
|
||||
override func title(in language: ContentLanguage) -> String {
|
||||
localized(in: language).name
|
||||
localized(in: language).title
|
||||
}
|
||||
|
||||
override var itemType: ItemType {
|
||||
|
@ -9,6 +9,7 @@ extension Page {
|
||||
externalLink: nil,
|
||||
isDraft: true,
|
||||
createdDate: Date(),
|
||||
hideDate: false,
|
||||
startDate: Date().addingTimeInterval(-86400),
|
||||
endDate: nil,
|
||||
german: .german,
|
||||
|
@ -8,6 +8,8 @@ struct PageFile {
|
||||
|
||||
let tags: [String]
|
||||
|
||||
let hideDate: Bool?
|
||||
|
||||
let createdDate: Date
|
||||
|
||||
let startDate: Date
|
||||
|
42
CHDataManagement/Views/Generic/TagDisplayView.swift
Normal file
42
CHDataManagement/Views/Generic/TagDisplayView.swift
Normal file
@ -0,0 +1,42 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TagDisplayView: View {
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@Binding
|
||||
var tags: [Tag]
|
||||
|
||||
@State
|
||||
private var showTagPicker = false
|
||||
|
||||
var body: some View {
|
||||
FlowHStack {
|
||||
ForEach(tags, id: \.id) { tag in
|
||||
TagView(text: tag.localized(in: language).name)
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
Button(action: { showTagPicker = true }) {
|
||||
Image(systemSymbol: .squareAndPencilCircleFill)
|
||||
.resizable()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(height: 22)
|
||||
.foregroundColor(Color.gray)
|
||||
.background(Circle()
|
||||
.fill(Color.white)
|
||||
.padding(1))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.sheet(isPresented: $showTagPicker) {
|
||||
TagSelectionView(
|
||||
presented: $showTagPicker,
|
||||
selected: $tags,
|
||||
tags: $content.tags)
|
||||
}
|
||||
}
|
||||
}
|
@ -72,6 +72,7 @@ struct AddPageView: View {
|
||||
externalLink: nil,
|
||||
isDraft: true,
|
||||
createdDate: .now,
|
||||
hideDate: false,
|
||||
startDate: .now,
|
||||
endDate: nil,
|
||||
german: .init(content: content,
|
||||
|
@ -24,9 +24,6 @@ struct PageContentView: View {
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@State
|
||||
private var showTagPicker = false
|
||||
|
||||
init(page: Page) {
|
||||
self.page = page
|
||||
}
|
||||
@ -44,36 +41,13 @@ struct PageContentView: View {
|
||||
}.padding()
|
||||
} else {
|
||||
VStack(alignment: .leading) {
|
||||
FlowHStack {
|
||||
ForEach(page.tags, id: \.id) { tag in
|
||||
TagView(text: tag.localized(in: language).name)
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
Button(action: { showTagPicker = true }) {
|
||||
Image(systemSymbol: .squareAndPencilCircleFill)
|
||||
.resizable()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(height: 22)
|
||||
.foregroundColor(Color.gray)
|
||||
.background(Circle()
|
||||
.fill(Color.white)
|
||||
.padding(1))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
TagDisplayView(tags: $page.tags)
|
||||
LocalizedPageContentView(pageId: page.id, page: page.localized(in: language), language: language)
|
||||
.id(page.id + language.rawValue)
|
||||
}.padding()
|
||||
.sheet(isPresented: $showTagPicker) {
|
||||
TagSelectionView(
|
||||
presented: $showTagPicker,
|
||||
selected: $page.tags,
|
||||
tags: $content.tags)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension PageContentView: MainContentView {
|
||||
|
@ -67,6 +67,18 @@ struct PageDetailView: View {
|
||||
value: $page.isDraft,
|
||||
footer: "Indicate a page as a draft to hide it from the website")
|
||||
.disabled(page.isExternalUrl)
|
||||
.onChange(of: page.externalLink) { _, newValue in
|
||||
// Ensure that external pages are not drafts
|
||||
if newValue != nil && page.isDraft {
|
||||
page.isDraft = false
|
||||
}
|
||||
}
|
||||
|
||||
BoolPropertyView(
|
||||
title: "Hide date",
|
||||
value: $page.hideDate,
|
||||
footer: "Do not show the date string on the page")
|
||||
.disabled(page.isExternalUrl)
|
||||
|
||||
DatePropertyView(
|
||||
title: "Start date",
|
||||
|
@ -68,14 +68,21 @@ private struct LocalizedContentEditor: View {
|
||||
}
|
||||
}
|
||||
|
||||
private struct LinkedPageTagView: View {
|
||||
|
||||
@ObservedObject
|
||||
var page: Page
|
||||
|
||||
var body: some View {
|
||||
TagDisplayView(tags: $page.tags)
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalizedPostContentView: View {
|
||||
|
||||
@ObservedObject
|
||||
var post: Post
|
||||
|
||||
@State
|
||||
private var showTagPicker = false
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@ -95,32 +102,14 @@ struct LocalizedPostContentView: View {
|
||||
}
|
||||
PostImagesView(post: post.localized(in: language))
|
||||
LocalizedTitle(post: post.localized(in: language))
|
||||
FlowHStack {
|
||||
ForEach(post.tags, id: \.id) { tag in
|
||||
TagView(text: tag.localized(in: language).name)
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
Button(action: { showTagPicker = true }) {
|
||||
Image(systemSymbol: .squareAndPencilCircleFill)
|
||||
.resizable()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(height: 22)
|
||||
.foregroundColor(Color.gray)
|
||||
.background(Circle()
|
||||
.fill(Color.white)
|
||||
.padding(1))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
if let page = post.linkedPage {
|
||||
LinkedPageTagView(page: page)
|
||||
} else {
|
||||
TagDisplayView(tags: $post.tags)
|
||||
}
|
||||
LocalizedContentEditor(post: post.localized(in: language))
|
||||
}
|
||||
.padding()
|
||||
.sheet(isPresented: $showTagPicker) {
|
||||
TagSelectionView(
|
||||
presented: $showTagPicker,
|
||||
selected: $post.tags,
|
||||
tags: $content.tags)
|
||||
}
|
||||
}
|
||||
|
||||
private func copyImagesFromOtherLanguage() {
|
||||
|
@ -76,6 +76,16 @@ struct PostDetailView: View {
|
||||
title: "Linked page",
|
||||
selectedPage: $post.linkedPage,
|
||||
footer: "The page to open when clicking on the post")
|
||||
.onChange(of: post.linkedPage) { oldValue, newValue in
|
||||
if newValue != nil {
|
||||
post.tags = []
|
||||
} else {
|
||||
// Link removed, so copy tags from previous link
|
||||
if let oldValue {
|
||||
post.tags = oldValue.tags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LocalizedPostDetailView(post: post.localized(in: language))
|
||||
}
|
||||
|
@ -223,6 +223,7 @@ struct PageIssueView: View {
|
||||
externalLink: nil,
|
||||
isDraft: true,
|
||||
createdDate: .now,
|
||||
hideDate: false,
|
||||
startDate: .now,
|
||||
endDate: nil,
|
||||
german: .init(content: content,
|
||||
|
@ -14,24 +14,16 @@ private struct PageSelectionView: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
let isSelected = page.tags.contains(tag)
|
||||
let isSelected = page.contains(tag)
|
||||
Image(systemSymbol: isSelected ? .checkmarkCircleFill : .circle)
|
||||
.foregroundStyle(Color.blue)
|
||||
Text(page.localized(in: language).title)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
toggleTagAssignment()
|
||||
page.toggle(tag)
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleTagAssignment() {
|
||||
guard let index = page.tags.firstIndex(of: tag) else {
|
||||
page.tags.append(tag)
|
||||
return
|
||||
}
|
||||
page.tags.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
struct PageTagAssignmentView: View {
|
||||
|
@ -14,24 +14,16 @@ private struct PostSelectionView: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
let isSelected = post.tags.contains(tag)
|
||||
let isSelected = post.contains(tag)
|
||||
Image(systemSymbol: isSelected ? .checkmarkCircleFill : .circle)
|
||||
.foregroundStyle(Color.blue)
|
||||
Text(post.localized(in: language).title)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
toggleTagAssignment()
|
||||
post.toggle(tag)
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleTagAssignment() {
|
||||
guard let index = post.tags.firstIndex(of: tag) else {
|
||||
post.tags.append(tag)
|
||||
return
|
||||
}
|
||||
post.tags.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
struct PostTagAssignmentView: View {
|
||||
|
@ -22,7 +22,7 @@ struct TagContentView: View {
|
||||
}
|
||||
|
||||
var selectedPosts: [Post] {
|
||||
content.posts.filter { $0.tags.contains(tag) }
|
||||
content.posts.filter { $0.contains(tag) }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
Loading…
x
Reference in New Issue
Block a user