aboutsummaryrefslogtreecommitdiff
path: root/docs/src/markdown.ffi.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'docs/src/markdown.ffi.mjs')
-rw-r--r--docs/src/markdown.ffi.mjs164
1 files changed, 164 insertions, 0 deletions
diff --git a/docs/src/markdown.ffi.mjs b/docs/src/markdown.ffi.mjs
new file mode 100644
index 0000000..7267c82
--- /dev/null
+++ b/docs/src/markdown.ffi.mjs
@@ -0,0 +1,164 @@
+import { attribute } from "../lustre/lustre/attribute.mjs";
+import { element, text } from "../lustre/lustre/element.mjs";
+import { List, Empty, NonEmpty } from "./gleam.mjs";
+import { fromMarkdown } from "mdast-util-from-markdown";
+import { highlight_element } from "./highlight.ffi.mjs";
+import * as Markdown from "./app/ui/markdown.mjs";
+
+const empty = new Empty();
+const singleton = (val) => new NonEmpty(val, empty);
+const fold_into_list = (arr, f) =>
+ arr.reduceRight((acc, val) => new NonEmpty(f(val), acc), empty);
+
+function compute_hash(str) {
+ let hash = 0;
+ for (let i = 0, len = str.length; i < len; i++) {
+ let chr = str.charCodeAt(i);
+ hash = (hash << 5) - hash + chr;
+ hash |= 0; // Convert to 32bit integer
+ }
+ return `${~hash}`;
+}
+
+function linkify(el) {
+ for (const [t, url] of Object.entries(links)) {
+ el.innerHTML = el.innerHTML.replace(
+ new RegExp(`\\b${t}\\b`, "g"),
+ `<a href="${url}" class="hover:underline">${t}</a>`
+ );
+ }
+}
+
+const base = import.meta.env.BASE_URL;
+const stdlib = "https://hexdocs.pm/gleam_stdlib/gleam/";
+const links = {
+ App: `${base}api/lustre#app-type`,
+ Attribute: `${base}api/lustre/attribute#attribute-type`,
+ Bool: `${stdlib}bool.html`,
+ Decoder: `${stdlib}dynamic.html#Decoder`,
+ Dynamic: `${stdlib}dynamic.html#Dynamic`,
+ Effect: `${base}api/lustre/effect#effect-type`,
+ Element: `${base}api/lustre/element#element-type`,
+ Error: `${base}api/lustre#error-type`,
+ Float: `${stdlib}float.html`,
+ Int: `${stdlib}int.html`,
+ List: `${stdlib}list.html`,
+ Map: `${stdlib}map.html#Map`,
+ Option: `${stdlib}option.html#Option`,
+ Result: `${stdlib}result.html`,
+ String: `${stdlib}string.html`,
+ StringBuilder: `${stdlib}string_builder.html#StringBuilder`,
+};
+
+const cashe = new Map();
+
+export function parse_markdown(md) {
+ window.requestAnimationFrame(() => {
+ const selector = `[data-hash]:not(.hljs)`;
+
+ for (const code of document.querySelectorAll(selector)) {
+ highlight_element(code, code.dataset.lang).then(() => {
+ linkify(code);
+ });
+ }
+ });
+
+ if (cashe.has(md)) return cashe.get(md);
+
+ const ast = fromMarkdown(md);
+ const summary = [];
+ const content = fold_into_list(
+ ast.children,
+ function to_lustre_element(node) {
+ switch (node.type) {
+ case "code":
+ return Markdown.code(node.value, compute_hash(node.value), node.lang);
+
+ case "emphasis":
+ return Markdown.emphasis(
+ fold_into_list(node.children, to_lustre_element)
+ );
+
+ case "heading": {
+ const [title, rest] = node.children[0].value.split("|");
+ const tags = List.fromArray(rest ? rest.trim().split(" ") : []);
+ const id =
+ /^[A-Z]/.test(title.trim()) &&
+ node.depth === 3 &&
+ window.location.pathname.includes("/api/")
+ ? `${title
+ .toLowerCase()
+ .trim()
+ .replace(/\s/g, "-")
+ .replace(/[^a-zA-Z0-9-_]/g, "")}-type`
+ : `${title
+ .toLowerCase()
+ .trim()
+ .replace(/\s/g, "-")
+ .replace(/[^a-zA-Z0-9-_]/g, "")}`;
+
+ if (node.depth > 1) {
+ summary.unshift(
+ element(
+ "a",
+ List.fromArray([
+ attribute("href", `#${id}`),
+ attribute("class", "text-sm text-gray-400 no-underline"),
+ attribute("class", "hover:text-gray-700 hover:underline"),
+ attribute(
+ "class",
+ node.depth === 2 ? `mt-4 first:mt-0` : `ml-2`
+ ),
+ ]),
+ singleton(text(title.trim()))
+ )
+ );
+ }
+
+ return Markdown.heading(node.depth, title.trim(), tags, id);
+ }
+
+ case "inlineCode":
+ return Markdown.inline_code(node.value);
+
+ case "link":
+ return Markdown.link(
+ node.url.startsWith("/")
+ ? import.meta.BASE_URL + node.url.slice(1)
+ : node.url,
+ fold_into_list(node.children, to_lustre_element)
+ );
+
+ case "list":
+ return Markdown.list(
+ !!node.ordered,
+ fold_into_list(node.children, to_lustre_element)
+ );
+
+ case "listItem":
+ return Markdown.list_item(
+ fold_into_list(node.children, to_lustre_element)
+ );
+
+ case "paragraph":
+ return Markdown.paragraph(
+ fold_into_list(node.children, to_lustre_element)
+ );
+
+ case "strong":
+ return Markdown.strong(
+ fold_into_list(node.children, to_lustre_element)
+ );
+
+ case "text":
+ return Markdown.text(node.value);
+
+ default:
+ return Markdown.text("");
+ }
+ }
+ );
+
+ cashe.set(md, [content, List.fromArray(summary)]);
+ return [content, List.fromArray(summary)];
+}