diff options
author | Jean-Nicolas Veigel <art.jnveigel@gmail.com> | 2024-03-17 17:09:20 +0100 |
---|---|---|
committer | Louis Pilfold <louis@lpil.uk> | 2024-03-26 10:31:25 +0000 |
commit | 3612803ae3efdd897b5af03809d167cef55621f4 (patch) | |
tree | 3bf80e5425e2fa9e706ddb588a9416136dbece06 /src | |
parent | e3a94ea6f66f4797d2d179edbf820c9efd943a03 (diff) | |
download | tour-3612803ae3efdd897b5af03809d167cef55621f4.tar.gz tour-3612803ae3efdd897b5af03809d167cef55621f4.zip |
chore: group functions into 3 modules
Diffstat (limited to 'src')
-rw-r--r-- | src/tour.gleam | 257 | ||||
-rw-r--r-- | src/tour/components.gleam | 40 | ||||
-rw-r--r-- | src/tour/page.gleam | 74 | ||||
-rw-r--r-- | src/tour/pages/everything.gleam | 151 | ||||
-rw-r--r-- | src/tour/render.gleam (renamed from src/tour/document.gleam) | 84 | ||||
-rw-r--r-- | src/tour/styles.gleam | 35 | ||||
-rw-r--r-- | src/tour/types/lesson.gleam | 16 | ||||
-rw-r--r-- | src/tour/widgets.gleam | 62 |
8 files changed, 370 insertions, 349 deletions
diff --git a/src/tour.gleam b/src/tour.gleam index c24111a..7bb6f26 100644 --- a/src/tour.gleam +++ b/src/tour.gleam @@ -1,18 +1,15 @@ import filepath import gleam/io import gleam/list -import gleam/option.{None, Some} +import gleam/option.{type Option, None, Some} import gleam/result import gleam/string import gleam/string_builder -import htmb.{h, text} +import htmb.{type Html, h, text} import simplifile import snag -import tour/types/lesson.{type Chapter, type Lesson, Chapter, Lesson} -import tour/document -import tour/page.{PageConfig, ScriptConfig} -import tour/styles -import tour/pages/everything +import tour/render.{PageConfig, ScriptConfig} +import tour/widgets.{Link} const static = "static" @@ -100,17 +97,55 @@ const what_next_html = " </p> " +// page paths + const path_home = "/" const path_table_of_contents = "/table-of-contents" const path_what_next = "/what-next" +const path_everything = "/everything" + // Don't include deprecated stdlib modules const skipped_stdlib_modules = [ "bit_string.gleam", "bit_builder.gleam", "map.gleam", ] +// css stylesheets paths + +const css__gleam_common = "/common.css" + +/// Loads fonts and defines font sizes +const css_fonts = "/css/fonts.css" + +/// Derives app colors for both dark & light themes from common.css variables +const css_theme = "/css/theme.css" + +/// Defines layout unit variables +const css_layout = "/css/layout.css" + +/// Common stylesheet for all tour pages +/// TODO: split into one file per page +const css_page_common = "/style.css" + +/// Sensitive defaults for any page +const css_defaults_page = [css_fonts, css_theme, css__gleam_common, css_layout] + +// Defines code syntax highlighting for highlightJS & CodeFlash +// based on dark / light mode and the currenly loaded color scheme +const css_syntax_highlight = "/css/code/syntax-highlight.css" + +// Color schemes +// TODO: add more color schemes + +/// Atom One Dark & Atom One Light colors +const css_scheme_atom_one = "/css/code/color-schemes/atom-one.css" + +/// Sensitive defaults for any page needing to display Gleam code +/// To be used alonside defaults_page +const css_defaults_code = [css_syntax_highlight, css_scheme_atom_one] + pub fn main() { let result = { use _ <- result.try(reset_output()) @@ -132,6 +167,21 @@ pub fn main() { } } +pub type Chapter { + Chapter(name: String, path: String, lessons: List(Lesson)) +} + +pub type Lesson { + Lesson( + name: String, + text: String, + code: String, + path: String, + previous: Option(String), + next: Option(String), + ) +} + type FileNames { FileNames(path: String, name: String, slug: String) } @@ -280,14 +330,14 @@ fn write_everything_page(chapters: List(Chapter)) -> snag.Result(Nil) { let path = public <> "/everything" use _ <- result.try(ensure_directory(path)) let path = filepath.join(path, "/index.html") - write_text(path, everything.render(chapters)) + write_text(path, everything_page_render(chapters)) } fn write_lesson(lesson: Lesson) -> snag.Result(Nil) { let path = public <> lesson.path use _ <- result.try(ensure_directory(path)) let path = filepath.join(path, "/index.html") - write_text(path, lesson_html(lesson)) + write_text(path, lesson_page_render(lesson)) } fn add_prev_next( @@ -504,7 +554,11 @@ fn file_error( } } -fn lesson_html(lesson: Lesson) -> String { +// Page renders + +/// Renders a Lesson's page +/// Complete with title, lesson, editor and output +fn lesson_page_render(lesson: Lesson) -> String { let navlink = fn(name, link) { case link { None -> h("span", [], [text(name)]) @@ -512,7 +566,7 @@ fn lesson_html(lesson: Lesson) -> String { } } - page.html( + render.render( PageConfig( path: lesson.path, title: lesson.name, @@ -541,21 +595,194 @@ fn lesson_html(lesson: Lesson) -> String { ], scripts: ScriptConfig( body: [ + render.dangerous_inline_script( + widgets.theme_picker_js, + render.ScriptOptions(module: True, defer: False), + [], + ), h("script", [#("type", "gleam"), #("id", "code")], [ htmb.dangerous_unescaped_fragment(string_builder.from_string( lesson.code, )), ]), - document.script( + render.script( "/index.js", - document.ScriptOptions(module: True, defer: False), + render.ScriptOptions(module: True, defer: False), [], ), ], head: [], ), - stylesheets: [styles.page, ..styles.defaults_code], + stylesheets: list.flatten([ + css_defaults_page, + css_defaults_code, + [css_page_common], + ]), + static_content: [ + widgets.navbar(titled: "Gleam Language Tour", links: [ + // TODO: find better label + Link(label: "Tour index", to: "/everything"), + Link(label: "gleam.run", to: "http://gleam.run"), + ]), + ], ), ) - |> page.render +} + +/// Transform a path into a slug +fn slugify_path(path: String) -> String { + string.replace(path, "/", "-") + |> string.drop_left(up_to: 1) +} + +/// Renders a lesson item in the everyting page's list +fn everything_page_lesson_html(lesson: Lesson, index: Int, end_index: Int) { + let snippet_link_title = "Experiment with " <> lesson.name <> " in browser" + + let lesson_content = + h("article", [#("class", "lesson"), #("id", slugify_path(lesson.path))], [ + h("a", [#("href", "#" <> slugify_path(lesson.path)), #("class", "link")], [ + h("h2", [#("class", "lesson-title")], [text(lesson.name)]), + ]), + htmb.dangerous_unescaped_fragment(string_builder.from_string(lesson.text)), + h("pre", [#("class", "lesson-snippet hljs gleam language-gleam")], [ + h("code", [], [text(lesson.code)]), + h( + "a", + [ + #("class", "lesson-snippet-link"), + #("href", lesson.path), + #("title", snippet_link_title), + #("aria-label", snippet_link_title), + ], + [ + h("i", [#("class", "snippet-link-icon")], [text("</>")]), + text("Run code snippet"), + ], + ), + ]), + ]) + + case index { + i if i == end_index -> [lesson_content] + _ -> [lesson_content, widgets.separator("lesson")] + } +} + +/// Renders a list containing all chapters and their lessons +fn everything_page_chapters_html(chapters: List(Chapter)) -> List(Html) { + use #(chapter, index) <- list.flat_map( + list.index_map(chapters, fn(chap, i) { #(chap, i) }), + ) + + let lessons = + list.index_map(chapter.lessons, fn(lesson, index) { + everything_page_lesson_html( + lesson, + index, + list.length(chapter.lessons) - 1, + ) + }) + let chapter_title = + h("h3", [#("id", slugify_path(chapter.path)), #("class", "chapter-title")], [ + text(chapter.name), + ]) + + let chapter_header = case index { + 0 -> [chapter_title, widgets.separator("chapter")] + _ -> [ + widgets.separator("chapter-between"), + chapter_title, + widgets.separator("chapter"), + ] + } + + list.concat([chapter_header, ..lessons]) +} + +/// Renders a link to a lesson in the table of contents +fn everything_page_toc_link(lesson: Lesson) -> Html { + h("li", [], [ + widgets.text_link( + Link(label: lesson.name, to: "#" <> slugify_path(lesson.path)), + [#("class", "link padded")], + ), + ]) +} + +/// Renders the everything pages's table of contents +fn everything_page_toc_html(chapters: List(Chapter)) -> List(Html) { + use chapter <- list.map(chapters) + let links = list.map(chapter.lessons, everything_page_toc_link) + + h("article", [#("class", "chapter"), #("id", "chapter-" <> chapter.name)], [ + h("h3", [], [text(chapter.name)]), + h("ul", [], links), + ]) +} + +/// Renders the /everything's page body content +fn everything_page_html(chapters: List(Chapter)) -> Html { + let chapter_lessons = everything_page_chapters_html(chapters) + let table_of_contents = everything_page_toc_html(chapters) + + h("main", [#("id", "everything")], [ + h( + "aside", + [#("id", "everything-contents"), #("class", "dim-bg")], + table_of_contents, + ), + h("section", [#("id", "everything-lessons")], chapter_lessons), + ]) +} + +// Path to the css specific to the everything page +const css_everything_page = "/css/pages/everything.css" + +/// Renders the /everything page to a string +pub fn everything_page_render(chapters: List(Chapter)) -> String { + // TODO: use proper values for location and such + render.render(PageConfig( + path: path_everything, + title: "Everything!", + scripts: ScriptConfig( + head: [ + render.script( + "/js/highlight/highlight.core.min.js", + render.ScriptOptions(module: True, defer: False), + [], + ), + render.script( + "/js/highlight/regexes.js", + render.ScriptOptions(module: True, defer: True), + [], + ), + ], + body: [ + render.dangerous_inline_script( + widgets.theme_picker_js, + render.ScriptOptions(module: True, defer: False), + [], + ), + render.script( + "/js/highlight/highlight-gleam.js", + render.ScriptOptions(module: True, defer: True), + [], + ), + ], + ), + stylesheets: list.flatten([ + css_defaults_page, + css_defaults_code, + [css_page_common, css_everything_page], + ]), + static_content: [ + widgets.navbar(titled: "Gleam Language Tour", links: [ + // TODO: find better label + Link(label: "Tour index", to: "/everything"), + Link(label: "gleam.run", to: "http://gleam.run"), + ]), + ], + content: [everything_page_html(chapters)], + )) } diff --git a/src/tour/components.gleam b/src/tour/components.gleam deleted file mode 100644 index a1e78c9..0000000 --- a/src/tour/components.gleam +++ /dev/null @@ -1,40 +0,0 @@ -import gleam/list -import htmb.{type Html, h, text} -import tour/document.{type HtmlAttribute} -import tour/widgets - -pub type Link { - Link(label: String, to: String) -} - -/// Renders a styled text link -pub fn text_link( - for link: Link, - attributes attributes: List(HtmlAttribute), -) -> Html { - let link_attributes = [#("class", "link"), ..attributes] - - document.anchor(link.to, link_attributes, [text(link.label)]) -} - -/// Renders the tour's navbar as html -pub fn navbar(titled title: String, links links: List(Link)) -> Html { - let links = list.map(links, fn(l) { text_link(l, []) }) - - let nav_right_items = list.flatten([links, [widgets.theme_picker()]]) - - h("nav", [#("class", "navbar")], [ - document.anchor("/", [#("class", "logo")], [ - h( - "img", - [ - #("src", "https://gleam.run/images/lucy/lucy.svg"), - #("alt", "Lucy the star, Gleam's mascot"), - ], - [], - ), - text(title), - ]), - h("div", [#("class", "nav-right")], nav_right_items), - ]) -} diff --git a/src/tour/page.gleam b/src/tour/page.gleam deleted file mode 100644 index 1724d6b..0000000 --- a/src/tour/page.gleam +++ /dev/null @@ -1,74 +0,0 @@ -import gleam/string -import gleam/string_builder -import gleam/list -import htmb.{type Html} -import tour/document.{BodyConfig, HeadConfig, HtmlConfig, ScriptOptions} -import tour/components.{Link} -import tour/widgets -import tour/styles - -pub type ScriptConfig { - ScriptConfig(head: List(Html), body: List(Html)) -} - -pub type PageConfig { - PageConfig( - path: String, - title: String, - content: List(Html), - stylesheets: List(String), - scripts: ScriptConfig, - ) -} - -/// Renders a page in the language tour -pub fn html(page config: PageConfig) -> Html { - // add path-specific class to body to make styling easier - let body_class = #("id", "page" <> string.replace(config.path, "/", "-")) - - // render html - document.html(HtmlConfig( - head: HeadConfig( - description: "An interactive introduction and reference to the Gleam programming language. Learn Gleam in your browser!", - image: "https://gleam.run/images/og-image.png", - title: config.title <> " - The Gleam Language Tour", - url: "https://tour.gleam.run/" <> config.path, - path: config.path, - meta: [], - stylesheets: list.flatten([styles.defaults_page, config.stylesheets]), - scripts: [ - document.script( - "https://plausible.io/js/script.js", - ScriptOptions(defer: True, module: False), - [#("data-domain", "tour.gleam.run")], - ), - document.dangerous_inline_script( - widgets.theme_picker_js, - ScriptOptions(module: True, defer: False), - [], - ), - ..config.scripts.head - ], - ), - lang: "en-GB", - attributes: [#("class", "theme-light")], - body: BodyConfig( - attributes: [body_class], - scripts: config.scripts.body, - static_content: [ - components.navbar(titled: "Gleam Language Tour", links: [ - // TODO: find better label - Link(label: "Tour index", to: "/everything"), - Link(label: "gleam.run", to: "http://gleam.run"), - ]), - ], - content: config.content, - ), - )) -} - -pub fn render(page page_html: Html) -> String { - page_html - |> htmb.render_page("html") - |> string_builder.to_string -} diff --git a/src/tour/pages/everything.gleam b/src/tour/pages/everything.gleam deleted file mode 100644 index 04f0651..0000000 --- a/src/tour/pages/everything.gleam +++ /dev/null @@ -1,151 +0,0 @@ -//// tour.gleam.run/everyting -//// -//// Displays a list of all chapters and their lessons -//// as well as a table of contents that allows for easy navigation - -import gleam/list -import gleam/string -import gleam/string_builder -import htmb.{type Html, dangerous_unescaped_fragment, h, text} -import tour/types/lesson.{type Chapter, type Lesson} -import tour/components.{Link} -import tour/document -import tour/page.{PageConfig, ScriptConfig} -import tour/styles - -const page_css = "/css/pages/everything.css" - -fn slugify_path(path: String) -> String { - string.replace(path, "/", "-") - |> string.drop_left(up_to: 1) -} - -fn separator(class: String) { - h("hr", [#("class", class <> "-separator")], []) -} - -fn lesson_html(lesson: Lesson, index: Int, end_index: Int) { - let snippet_link_title = "Experiment with " <> lesson.name <> " in browser" - - let lesson_content = - h("article", [#("class", "lesson"), #("id", slugify_path(lesson.path))], [ - h("a", [#("href", "#" <> slugify_path(lesson.path)), #("class", "link")], [ - h("h2", [#("class", "lesson-title")], [text(lesson.name)]), - ]), - dangerous_unescaped_fragment(string_builder.from_string(lesson.text)), - h("pre", [#("class", "lesson-snippet hljs gleam language-gleam")], [ - h("code", [], [text(lesson.code)]), - h( - "a", - [ - #("class", "lesson-snippet-link"), - #("href", lesson.path), - #("title", snippet_link_title), - #("aria-label", snippet_link_title), - ], - [ - h("i", [#("class", "snippet-link-icon")], [text("</>")]), - text("Run code snippet"), - ], - ), - ]), - ]) - - case index { - i if i == end_index -> [lesson_content] - _ -> [lesson_content, separator("lesson")] - } -} - -fn chapters_html(chapters: List(Chapter)) -> List(Html) { - use #(chapter, index) <- list.flat_map( - list.index_map(chapters, fn(chap, i) { #(chap, i) }), - ) - - let lessons = - list.index_map(chapter.lessons, fn(lesson, index) { - lesson_html(lesson, index, list.length(chapter.lessons) - 1) - }) - let chapter_title = - h("h3", [#("id", slugify_path(chapter.path)), #("class", "chapter-title")], [ - text(chapter.name), - ]) - - let chapter_header = case index { - 0 -> [chapter_title, separator("chapter")] - _ -> [separator("chapter-between"), chapter_title, separator("chapter")] - } - - list.concat([chapter_header, ..lessons]) -} - -fn toc_link(lesson: Lesson) -> Html { - h("li", [], [ - components.text_link( - Link(label: lesson.name, to: "#" <> slugify_path(lesson.path)), - [#("class", "link padded")], - ), - ]) -} - -fn toc_html(chapters: List(Chapter)) -> List(Html) { - use chapter <- list.map(chapters) - let links = list.map(chapter.lessons, toc_link) - - h("article", [#("class", "chapter"), #("id", "chapter-" <> chapter.name)], [ - h("h3", [], [text(chapter.name)]), - h("ul", [], links), - ]) -} - -/// Builds the /everything's page body content -fn html(chapters: List(Chapter)) -> Html { - let chapter_lessons = chapters_html(chapters) - let table_of_contents = toc_html(chapters) - - h("main", [#("id", "everything")], [ - h( - "aside", - [#("id", "everything-contents"), #("class", "dim-bg")], - table_of_contents, - ), - h("section", [#("id", "everything-lessons")], chapter_lessons), - ]) -} - -/// Renders the /everything page to a string -pub fn render(chapters: List(Chapter)) -> String { - let content = html(chapters) - - // TODO: use proper values for location and such - page.html( - PageConfig( - path: "everything", - title: "Everything!", - scripts: ScriptConfig( - head: [ - document.script( - "/js/highlight/highlight.core.min.js", - document.ScriptOptions(module: True, defer: False), - [], - ), - document.script( - "/js/highlight/regexes.js", - document.ScriptOptions(module: True, defer: True), - [], - ), - ], - body: [ - document.script( - "/js/highlight/highlight-gleam.js", - document.ScriptOptions(module: True, defer: True), - [], - ), - ], - ), - stylesheets: [styles.page, page_css, ..styles.defaults_code], - content: [content], - ), - ) - |> page.render -} diff --git a/src/tour/document.gleam b/src/tour/render.gleam index 6921950..8aeb29c 100644 --- a/src/tour/document.gleam +++ b/src/tour/render.gleam @@ -1,6 +1,7 @@ import htmb.{type Html, h, text} -import gleam/string_builder import gleam/list +import gleam/string +import gleam/string_builder pub type HtmlAttribute = #(String, String) @@ -90,7 +91,7 @@ pub type HeadConfig { } /// Renders the page head as HTML -pub fn head(with config: HeadConfig) -> htmb.Html { +fn head(with config: HeadConfig) -> htmb.Html { let meta_tags = [ meta_prop("og:type", "website"), meta_prop("og:title", config.title), @@ -126,19 +127,6 @@ pub fn head(with config: HeadConfig) -> htmb.Html { h("head", [], head_content) } -pub type Link { - Link(label: String, to: String) -} - -/// Renders an HTML anchor tag -pub fn anchor( - to href: String, - attrs attributes: List(HtmlAttribute), - with content: List(Html), -) { - h("a", [#("href", href), ..attributes], content) -} - pub type BodyConfig { BodyConfig( content: List(Html), @@ -149,7 +137,7 @@ pub type BodyConfig { } /// Renders an Html body tag -pub fn body(with config: BodyConfig) -> Html { +fn body(with config: BodyConfig) -> Html { let content = list.flatten([config.static_content, config.content, config.scripts]) @@ -166,8 +154,70 @@ pub type HtmlConfig { } /// Renders an HTML tag and its children -pub fn html(with config: HtmlConfig) -> Html { +fn html(with config: HtmlConfig) -> Html { let attributes = [#("lang", config.lang), ..config.attributes] h("html", attributes, [head(config.head), body(config.body)]) } + +pub type ScriptConfig { + ScriptConfig(head: List(Html), body: List(Html)) +} + +pub type PageConfig { + PageConfig( + path: String, + title: String, + content: List(Html), + static_content: List(Html), + stylesheets: List(String), + scripts: ScriptConfig, + ) +} + +/// Renders a page in the language tour +pub fn render_html(page config: PageConfig) -> Html { + // add path-specific class to body to make styling easier + let body_class = #("id", "page" <> string.replace(config.path, "/", "-")) + + // render html + html(HtmlConfig( + head: HeadConfig( + description: "An interactive introduction and reference to the Gleam programming language. Learn Gleam in your browser!", + image: "https://gleam.run/images/og-image.png", + title: config.title <> " - The Gleam Language Tour", + url: "https://tour.gleam.run/" <> config.path, + path: config.path, + meta: [], + stylesheets: config.stylesheets, + scripts: [ + script( + "https://plausible.io/js/script.js", + ScriptOptions(defer: True, module: False), + [#("data-domain", "tour.gleam.run")], + ), + ..config.scripts.head + ], + ), + lang: "en-GB", + attributes: [#("class", "theme-light")], + body: BodyConfig( + attributes: [body_class], + scripts: config.scripts.body, + static_content: config.static_content, + content: config.content, + ), + )) +} + +pub fn render_string(page page_html: Html) -> String { + page_html + |> htmb.render_page("html") + |> string_builder.to_string +} + +pub fn render(page config: PageConfig) -> String { + config + |> render_html + |> render_string +} diff --git a/src/tour/styles.gleam b/src/tour/styles.gleam deleted file mode 100644 index 7660393..0000000 --- a/src/tour/styles.gleam +++ /dev/null @@ -1,35 +0,0 @@ -//// A list of valid paths to static css files -//// We use these to pick and choose what style sheets to include per page - -/// Inherited from code Gleam projects -pub const common = "/common.css" - -/// Loads fonts and defines font sizes -pub const fonts = "/css/fonts.css" - -/// Derives app colors for both dark & light themes from common.css variables -pub const theme = "/css/theme.css" - -/// Defines layout unit variables -pub const layout = "/css/layout.css" - -/// Common stylesheet for all tour pages -/// TODO: split into one file per page -pub const page = "/style.css" - -/// Sensitive defaults for any page -pub const defaults_page = [theme, fonts, common, layout] - -// Defines code syntax highlighting for highlightJS & CodeFlash -// based on dark / light mode and the currenly loaded color scheme -pub const syntax_highlight = "/css/code/syntax-highlight.css" - -// Color schemes -// TODO: add more color schemes - -/// Atom One Dark & Atom One Light colors -pub const scheme_atom_one = "/css/code/color-schemes/atom-one.css" - -/// Sensitive defaults for any page needing to display Gleam code -/// To be used alonside defaults_page -pub const defaults_code = [syntax_highlight, scheme_atom_one] diff --git a/src/tour/types/lesson.gleam b/src/tour/types/lesson.gleam deleted file mode 100644 index 10fbdd9..0000000 --- a/src/tour/types/lesson.gleam +++ /dev/null @@ -1,16 +0,0 @@ -import gleam/option.{type Option} - -pub type Chapter { - Chapter(name: String, path: String, lessons: List(Lesson)) -} - -pub type Lesson { - Lesson( - name: String, - text: String, - code: String, - path: String, - previous: Option(String), - next: Option(String), - ) -} diff --git a/src/tour/widgets.gleam b/src/tour/widgets.gleam index fabcbb1..774c76f 100644 --- a/src/tour/widgets.gleam +++ b/src/tour/widgets.gleam @@ -1,4 +1,6 @@ -import htmb.{type Html, h} +import gleam/list +import htmb.{type Html, h, text} +import tour/render pub fn icon_moon() -> Html { h("svg", [#("id", "icon-moon"), #("viewBox", "0 0 24 24")], [ @@ -132,3 +134,61 @@ document.querySelector('[data-dark-theme-toggle]').addEventListener('click', () selectTheme('dark') }) " + +pub fn theme_picker_script() -> Html { + render.dangerous_inline_script( + theme_picker_js, + render.ScriptOptions(module: True, defer: False), + [], + ) +} + +/// Renders an HTML anhor tag +pub fn anchor( + to href: String, + attrs attributes: List(#(String, String)), + with content: List(Html), +) -> Html { + h("a", [#("href", href), ..attributes], content) +} + +pub type Link { + Link(label: String, to: String) +} + +/// Renders a styled text link +pub fn text_link( + for link: Link, + attributes attributes: List(#(String, String)), +) -> Html { + let link_attributes = [#("class", "link"), ..attributes] + + anchor(link.to, link_attributes, [text(link.label)]) +} + +/// Renders the tour's navbar as html +pub fn navbar(titled title: String, links links: List(Link)) -> Html { + let links = list.map(links, fn(l) { text_link(l, []) }) + + let nav_right_items = list.flatten([links, [theme_picker()]]) + + h("nav", [#("class", "navbar")], [ + anchor("/", [#("class", "logo")], [ + h( + "img", + [ + #("src", "https://gleam.run/images/lucy/lucy.svg"), + #("alt", "Lucy the star, Gleam's mascot"), + ], + [], + ), + text(title), + ]), + h("div", [#("class", "nav-right")], nav_right_items), + ]) +} + +/// Renders a horizontal separator +pub fn separator(class: String) -> Html { + h("hr", [#("class", class <> "-separator")], []) +} |