
Landing.pudu
Pudu130 lines7.0 KB
1/** @Docgen.Site.Landing — hub pages with a banner, highlighted links, and topic lists */2module PuduLangDocgen.Site.Landing34import Std.Map as Map5import PuduLangDocgen as Docgen6import PuduLangDocgen.Markdown.Phrase as Phrase7import PuduLangDocgen.Markdown.Syntax as Syntax8import PuduLangDocgen.Meta as Meta9import PuduLangDocgen.Paths as Paths1011/** @Docgen.Site.LandingPage — the rendered title, body, outline, and findings of a hub page */12export type Rendered = { title: Str, body: Str, headings: Array[Docgen.Heading], links: Array[Docgen.Link], diagnostics: Array[Docgen.Diagnostic] }131415export const MARKER: Str = "YamlMime:Landing"161718const LABELS: Map[Str, Str] = mapOf([19 ("overview", "Overview"), ("get-started", "Get started"), ("quickstart", "Quickstart"), ("concept", "Concept"),20 ("tutorial", "Tutorial"), ("how-to-guide", "How-to guide"), ("reference", "Reference"), ("whats-new", "What's new"),21 ("download", "Download"), ("deploy", "Deploy"), ("architecture", "Architecture"), ("sample", "Sample")22 ])23242526272829export fn render(value: &Docgen.Meta, context: &Phrase.Scope) -> Result[Rendered, Str] {30 let fields = match value {31 case Docgen.Fields(found) => found32 case _ => { return Err("landing page must be an object") }33 }34 let title = Meta.textOr(&fields, "title", "")35 if title.isEmpty() { return Err("landing page needs a title") }36 var out = Phrase.start()37 var headings = [Docgen.Heading{id: Paths.slug(title), title: title, level: 1}]38 let summary = Meta.textOr(&fields, "summary", "")39 let lead = if summary.isEmpty() { "" } else { "<p class=\"landing-summary\">" + Paths.escape(summary) + "</p>" }40 var pieces = ["<section class=\"landing-hero\"><div class=\"landing-inner\"><h1 id=\"" + Paths.escape(Paths.slug(title)) + "\">" + Paths.escape(title) + "</h1>" + lead + "</div></section>"]41 let highlighted = objects(&Meta.fieldsOf(&fields, "highlightedContent"), "items", "highlighted item") ?42 if !highlighted.isEmpty() {43 var cards: Array[Str] = []44 for item in highlighted {45 let kind = Meta.textOr(&item, "itemType", "")46 let label = match Map.get(&LABELS, kind) {47 case Some(found) => found48 case None => { return Err("highlighted item has an unknown itemType: " + kind) }49 }50 let (linked, href) = link(out, &item, "url", context)51 out = linked52 cards = cards.push("<li class=\"landing-highlight\"><a href=\"" + Paths.escape(href) + "\"><span class=\"landing-kind\">" + Paths.escape(label) + "</span><span class=\"landing-link\">" + Paths.escape(required(&item, "title", "highlighted item") ?) + "</span></a></li>")53 }54 pieces = pieces.push("<ul class=\"landing-highlights landing-inner\">" + cards.join("") + "</ul>")55 }56 let conceptual = Meta.fieldsOf(&fields, "conceptualContent")57 let topics = objects(&conceptual, "items", "topic") ?58 if !topics.isEmpty() {59 let heading = Meta.textOr(&conceptual, "title", "")60 var cards: Array[Str] = []61 for topic in topics {62 let name = required(&topic, "title", "topic") ?63 var entries: Array[Str] = []64 for entry in objects(&topic, "links", "topic link") ? {65 let (linked, href) = link(out, &entry, "url", context)66 out = linked67 entries = entries.push("<li><a href=\"" + Paths.escape(href) + "\">" + Paths.escape(required(&entry, "text", "topic link") ?) + "</a></li>")68 }69 var card = ["<article class=\"landing-card\"><h3>" + Paths.escape(name) + "</h3><ul>" + entries.join("") + "</ul>"]70 let footer = Meta.fieldsOf(&topic, "footerLink")71 if !footer.isEmpty() {72 let (linked, href) = link(out, &footer, "url", context)73 out = linked74 card = card.push("<p class=\"landing-more\"><a href=\"" + Paths.escape(href) + "\">" + Paths.escape(required(&footer, "text", "footer link") ?) + "</a></p>")75 }76 cards = cards.push(card.push("</article>").join(""))77 }78 pieces = pieces.push(section(heading, "landing-topics", cards))79 if !heading.isEmpty() { headings = headings.push(Docgen.Heading{id: Paths.slug(heading), title: heading, level: 2}) }80 }81 for block in objects(&Meta.fieldsOf(&fields, "additionalContent"), "sections", "section") ? {82 let heading = Meta.textOr(&block, "title", "")83 var cards: Array[Str] = []84 for item in objects(&block, "items", "related item") ? {85 let (linked, href) = link(out, &item, "url", context)86 out = linked87 let text = Meta.textOr(&item, "summary", "")88 cards = cards.push("<article class=\"landing-card\"><h3><a href=\"" + Paths.escape(href) + "\">" + Paths.escape(required(&item, "title", "related item") ?) + "</a></h3>" + (if text.isEmpty() { "" } else { "<p>" + Paths.escape(text) + "</p>" }) + "</article>")89 }90 pieces = pieces.push(section(heading, "landing-related", cards))91 if !heading.isEmpty() { headings = headings.push(Docgen.Heading{id: Paths.slug(heading), title: heading, level: 2}) }92 }93 Ok(Rendered{title: title, body: pieces.join("\n"), headings: headings, links: out.links, diagnostics: out.diagnostics})94}959697fn section(heading: Str, kind: Str, cards: Array[Str]) -> Str {98 let title = if heading.isEmpty() { "" } else { "<h2 id=\"" + Paths.escape(Paths.slug(heading)) + "\">" + Paths.escape(heading) + "</h2>" }99 "<section class=\"" + kind + " landing-inner\">" + title + "<div class=\"landing-cards\">" + cards.join("") + "</div></section>"100}101102103fn link(out: Phrase.Out, entry: &Array[(Str, Docgen.Meta)], key: Str, context: &Phrase.Scope) -> (Phrase.Out, Str) {104 Phrase.destination(out, &Syntax.Target{destination: Meta.textOr(entry, key, ""), title: "", line: 1}, context, context.source, false)105}106107108fn required(entry: &Array[(Str, Docgen.Meta)], key: Str, noun: Str) -> Result[Str, Str] {109 let found = Meta.textOr(entry, key, "")110 if found.isEmpty() { Err("each " + noun + " needs a " + key) } else { Ok(found) }111}112113114fn objects(fields: &Array[(Str, Docgen.Meta)], key: Str, noun: Str) -> Result[Array[Array[(Str, Docgen.Meta)]], Str] {115 match Meta.get(fields, key) {116 case Some(Docgen.Items(listed)) => {117 var result: Array[Array[(Str, Docgen.Meta)]] = []118 for held in listed {119 match held {120 case Docgen.Fields(found) => { result = result.push(found) }121 case _ => { return Err("each " + noun + " must be an object") }122 }123 }124 Ok(result)125 }126 case None => Ok([])127 case _ => Err(key + " must be a list")128 }129}130