aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJean-Nicolas Veigel <art.jnveigel@gmail.com>2024-03-17 17:09:20 +0100
committerLouis Pilfold <louis@lpil.uk>2024-03-26 10:31:25 +0000
commit3612803ae3efdd897b5af03809d167cef55621f4 (patch)
tree3bf80e5425e2fa9e706ddb588a9416136dbece06 /src
parente3a94ea6f66f4797d2d179edbf820c9efd943a03 (diff)
downloadtour-3612803ae3efdd897b5af03809d167cef55621f4.tar.gz
tour-3612803ae3efdd897b5af03809d167cef55621f4.zip
chore: group functions into 3 modules
Diffstat (limited to 'src')
-rw-r--r--src/tour.gleam257
-rw-r--r--src/tour/components.gleam40
-rw-r--r--src/tour/page.gleam74
-rw-r--r--src/tour/pages/everything.gleam151
-rw-r--r--src/tour/render.gleam (renamed from src/tour/document.gleam)84
-rw-r--r--src/tour/styles.gleam35
-rw-r--r--src/tour/types/lesson.gleam16
-rw-r--r--src/tour/widgets.gleam62
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")], [])
+}