From f2ee06b1d72908f84258e4d626eb8e30fa4523f2 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Sun, 25 Sep 2022 17:19:07 +0200 Subject: [PATCH] Add command for pretty page links --- .../Content/Element+LocalizedMetadata.swift | 10 ++++ Sources/Generator/Content/Element.swift | 42 +++++++++++++++ .../Content/GenericMetadata+Localized.swift | 17 ++++-- .../Generators/MarkdownProcessor.swift | 52 +++++++++++++++++-- .../Generators/ShorthandMarkdownKey.swift | 5 ++ .../Templates/Elements/PageLinkTemplate.swift | 22 ++++++++ .../Generator/Templates/TemplateFactory.swift | 3 ++ 7 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 Sources/Generator/Templates/Elements/PageLinkTemplate.swift diff --git a/Sources/Generator/Content/Element+LocalizedMetadata.swift b/Sources/Generator/Content/Element+LocalizedMetadata.swift index cbe5cd5..63fa09d 100644 --- a/Sources/Generator/Content/Element+LocalizedMetadata.swift +++ b/Sources/Generator/Content/Element+LocalizedMetadata.swift @@ -119,6 +119,13 @@ extension Element { It can also be set to manually write a page. */ let externalUrl: String? + + /** + The text to display for content related to the current page. + + This property is mandatory at root level, and is propagated to child elements. + */ + let relatedContentText: String } } @@ -162,6 +169,8 @@ extension Element.LocalizedMetadata { self.thumbnailSuffix = log.unused(data.thumbnailSuffix, "thumbnailSuffix", source: source) self.cornerText = log.unused(data.cornerText, "cornerText", source: source) self.externalUrl = log.unexpected(data.externalUrl, name: "externalUrl", source: source) + self.relatedContentText = log + .required(data.relatedContentText, name: "relatedContentText", source: source) ?? "" guard isComplete else { return nil @@ -197,6 +206,7 @@ extension Element.LocalizedMetadata { self.thumbnailSuffix = data.thumbnailSuffix self.cornerText = data.cornerText self.externalUrl = data.externalUrl + self.relatedContentText = data.relatedContentText ?? parent.relatedContentText guard isComplete else { return nil diff --git a/Sources/Generator/Content/Element.swift b/Sources/Generator/Content/Element.swift index 4af296e..3a3b4c9 100644 --- a/Sources/Generator/Content/Element.swift +++ b/Sources/Generator/Content/Element.swift @@ -421,6 +421,10 @@ extension Element { return pathRelativeToRootForContainedInputFile(thumbnailFile) } + /** + The full url (relative to root) for the localized page + - Parameter language: The language of the page where the url should point + */ func fullPageUrl(for language: String) -> String { localized(for: language).externalUrl ?? localizedPath(for: language) } @@ -611,4 +615,42 @@ extension Element { } return nil } + + var pathComponents: [String] { + path.components(separatedBy: "/") + } + + var lastPathComponent: String { + pathComponents.last! + } + + func find(elementWithFolder folder: String) -> Element? { + elements.first { $0.lastPathComponent == folder } + } + + func makePath(language: String, from root: Element) -> [String] { + let parts = pathComponents.dropLast() + var result = [String]() + var node = root + for part in parts { + guard let child = node.find(elementWithFolder: part) else { + return result + } + result.append(child.title(for: language)) + node = child + } + return result + } + + func findParent(from root: Element) -> Element? { + let parts = pathComponents.dropLast() + var node = root + for part in pathComponents.dropLast() { + guard let child = node.find(elementWithFolder: part) else { + return node + } + node = child + } + return node + } } diff --git a/Sources/Generator/Content/GenericMetadata+Localized.swift b/Sources/Generator/Content/GenericMetadata+Localized.swift index 25ab2a0..7ea6aac 100644 --- a/Sources/Generator/Content/GenericMetadata+Localized.swift +++ b/Sources/Generator/Content/GenericMetadata+Localized.swift @@ -112,6 +112,13 @@ extension GenericMetadata { It can also be set to manually write a page. */ let externalUrl: String? + + /** + The text to display for content related to the current page. + + This property is mandatory at root level, and is propagated to child elements. + */ + let relatedContentText: String? } } @@ -134,6 +141,7 @@ extension GenericMetadata.LocalizedMetadata: Codable { .thumbnailSuffix, .cornerText, .externalUrl, + .relatedContentText, ] } @@ -163,7 +171,8 @@ extension GenericMetadata.LocalizedMetadata { titleSuffix: nil, thumbnailSuffix: nil, cornerText: nil, - externalUrl: nil) + externalUrl: nil, + relatedContentText: nil) } /** @@ -184,7 +193,8 @@ extension GenericMetadata.LocalizedMetadata { titleSuffix: nil, thumbnailSuffix: nil, cornerText: nil, - externalUrl: nil) + externalUrl: nil, + relatedContentText: "") } static var full: GenericMetadata.LocalizedMetadata { @@ -202,6 +212,7 @@ extension GenericMetadata.LocalizedMetadata { titleSuffix: "", thumbnailSuffix: "", cornerText: "", - externalUrl: "") + externalUrl: "", + relatedContentText: "") } } diff --git a/Sources/Generator/Generators/MarkdownProcessor.swift b/Sources/Generator/Generators/MarkdownProcessor.swift index cb05eeb..8f1ee90 100644 --- a/Sources/Generator/Generators/MarkdownProcessor.swift +++ b/Sources/Generator/Generators/MarkdownProcessor.swift @@ -16,7 +16,7 @@ struct PageContentGenerator { var hasCodeContent = false let imageModifier = Modifier(target: .images) { html, markdown in - processMarkdownImage(markdown: markdown, html: html, page: page) + processMarkdownImage(markdown: markdown, html: html, page: page, language: language) } let codeModifier = Modifier(target: .codeBlocks) { html, markdown in if markdown.starts(with: "```swift") { @@ -70,7 +70,7 @@ struct PageContentGenerator { return html } - private func processMarkdownImage(markdown: Substring, html: String, page: Element) -> String { + private func processMarkdownImage(markdown: Substring, html: String, page: Element, language: String) -> String { // Split the markdown ![alt](file title) // There are several known shorthand commands // For images: ![left_title](file right_title) @@ -78,11 +78,12 @@ struct PageContentGenerator { // For svg with custom area: ![x,y,width,height](file.svg) // For downloads: ![download](file1, text1; file2, text2, ...) // For a simple boxes: ![box](title;body) + // A fancy page link: ![page](page_id) // External pages: ![external](url1, text1; url2, text2, ...) let fileAndTitle = markdown.between("(", and: ")") let alt = markdown.between("[", and: "]").nonEmpty if let alt = alt, let command = ShorthandMarkdownKey(rawValue: alt) { - return handleShortHandCommand(command, page: page, content: fileAndTitle) + return handleShortHandCommand(command, page: page, language: language, content: fileAndTitle) } let file = fileAndTitle.dropAfterFirst(" ") @@ -101,7 +102,7 @@ struct PageContentGenerator { return handleFile(page: page, file: file, fileExtension: fileExtension) } - private func handleShortHandCommand(_ command: ShorthandMarkdownKey, page: Element, content: String) -> String { + private func handleShortHandCommand(_ command: ShorthandMarkdownKey, page: Element, language: String, content: String) -> String { switch command { case .downloadButtons: return handleDownloadButtons(page: page, content: content) @@ -111,6 +112,8 @@ struct PageContentGenerator { return handleExternalHTML(page: page, file: content) case .box: return handleSimpleBox(page: page, content: content) + case .pageLink: + return handlePageLink(page: page, language: language, pageId: content) } } @@ -250,4 +253,45 @@ struct PageContentGenerator { let text = parts.dropFirst().joined(separator: ";") return factory.makePlaceholder(title: title, text: text) } + + private func handlePageLink(page: Element, language: String, pageId: String) -> String { + guard let linkedPage = siteRoot.find(pageId) else { + log.add(warning: "Page id '\(pageId)' not found", source: page.path) + // Remove link since the page can't be found + return "" + } + var content = [PageLinkTemplate.Key: String]() + content[.url] = page.relativePathToOtherSiteElement(file: linkedPage.fullPageUrl(for: language)) + + content[.title] = linkedPage.title(for: language) + + let fullThumbnailPath = linkedPage.thumbnailFilePath(for: language) + let relativeImageUrl = page.relativePathToOtherSiteElement(file: fullThumbnailPath) + let metadata = linkedPage.localized(for: language) + + if linkedPage.state.hasThumbnailLink { + let fullPageUrl = linkedPage.fullPageUrl(for: language) + let relativePageUrl = page.relativePathToOtherSiteElement(file: fullPageUrl) + content[.url] = "href=\"\(relativePageUrl)\"" + } + + content[.image] = relativeImageUrl + if let suffix = metadata.thumbnailSuffix { + content[.title] = factory.html.make(title: metadata.title, suffix: suffix) + } else { + content[.title] = metadata.title + } + content[.image2x] = relativeImageUrl.insert("@2x", beforeLast: ".") + + let path = linkedPage.makePath(language: language, from: siteRoot) + content[.path] = factory.pageLink.makePath(components: path) + + content[.description] = metadata.relatedContentText + if let parent = linkedPage.findParent(from: siteRoot), parent.thumbnailStyle == .large { + content[.className] = " related-page-link-large" + } + + // We assume that the thumbnail images are already required by overview pages. + return factory.pageLink.generate(content) + } } diff --git a/Sources/Generator/Generators/ShorthandMarkdownKey.swift b/Sources/Generator/Generators/ShorthandMarkdownKey.swift index 9a450c6..349ca68 100644 --- a/Sources/Generator/Generators/ShorthandMarkdownKey.swift +++ b/Sources/Generator/Generators/ShorthandMarkdownKey.swift @@ -21,4 +21,9 @@ enum ShorthandMarkdownKey: String { A box with a heading and a text description */ case box = "box" + + /** + A pretty link to another page on the site. + */ + case pageLink = "page" } diff --git a/Sources/Generator/Templates/Elements/PageLinkTemplate.swift b/Sources/Generator/Templates/Elements/PageLinkTemplate.swift new file mode 100644 index 0000000..c0b9149 --- /dev/null +++ b/Sources/Generator/Templates/Elements/PageLinkTemplate.swift @@ -0,0 +1,22 @@ +import Foundation + +struct PageLinkTemplate: Template { + + enum Key: String, CaseIterable { + case url = "URL" + case image = "IMAGE" + case image2x = "IMAGE_2X" + case title = "TITLE" + case path = "PATH" + case description = "DESCRIPTION" + case className = "CLASS" + } + + static let templateName = "page-link.html" + + let raw: String + + func makePath(components: [String]) -> String { + components.joined(separator: " » ") //  » ") + } +} diff --git a/Sources/Generator/Templates/TemplateFactory.swift b/Sources/Generator/Templates/TemplateFactory.swift index 5839d99..a6729dc 100644 --- a/Sources/Generator/Templates/TemplateFactory.swift +++ b/Sources/Generator/Templates/TemplateFactory.swift @@ -16,6 +16,8 @@ final class TemplateFactory { let overviewSectionClean: OverviewSectionCleanTemplate + let pageLink: PageLinkTemplate + let box: BoxTemplate // MARK: Thumbnails @@ -65,6 +67,7 @@ final class TemplateFactory { self.overviewSection = try .init(in: templateFolder) self.overviewSectionClean = try .init(in: templateFolder) self.box = try .init(in: templateFolder) + self.pageLink = try .init(in: templateFolder) self.largeThumbnail = try .init(in: templateFolder) self.squareThumbnail = try .init(in: templateFolder) self.smallThumbnail = try .init(in: templateFolder)