goframework-io-www

105 lines · 4.4 KiB Raw
1/* goframework.io: helpers the page's scripts share, as window.GF. */
2(function () {
3 "use strict";
4
5 // el builds an element: el("a", {href: "#x", class: "y"}, "text", child).
6 // Text is always text: nothing here parses markup.
7 function el(tag, attributes, ...children) {
8 const node = document.createElement(tag);
9 for (const [key, value] of Object.entries(attributes || {})) {
10 if (value === undefined || value === null || value === false) continue;
11 if (key === "class") node.className = value;
12 else if (key === "dataset") Object.assign(node.dataset, value);
13 else if (key.startsWith("on") && typeof value === "function") node.addEventListener(key.slice(2), value);
14 else node.setAttribute(key, value === true ? "" : String(value));
15 }
16 append(node, children);
17 return node;
18 }
19
20 function append(node, children) {
21 for (const child of children.flat(Infinity)) {
22 if (child === undefined || child === null || child === false) continue;
23 node.append(child instanceof Node ? child : document.createTextNode(String(child)));
24 }
25 return node;
26 }
27
28 function svg(tag, attributes, ...children) {
29 const node = document.createElementNS("http://www.w3.org/2000/svg", tag);
30 for (const [key, value] of Object.entries(attributes || {})) {
31 if (value !== undefined && value !== null) node.setAttribute(key, String(value));
32 }
33 append(node, children);
34 return node;
35 }
36
37 // store reads and writes preferences; any failure (private windows,
38 // blocked storage) leaves the page working without them.
39 const store = {
40 get(key) { try { return window.localStorage.getItem(key); } catch (error) { return null; } },
41 set(key, value) { try { if (value === null) window.localStorage.removeItem(key); else window.localStorage.setItem(key, value); } catch (error) { /* not kept */ } },
42 };
43
44 // copy puts text on the clipboard; where that is refused it selects a
45 // field holding it, so the reader can copy by hand.
46 async function copy(text, button) {
47 let done = false;
48 try {
49 if (navigator.clipboard && window.isSecureContext) {
50 await navigator.clipboard.writeText(text);
51 done = true;
52 }
53 } catch (error) { /* fall back below */ }
54 if (!done) {
55 const field = el("textarea", { class: "visually-hidden", "aria-hidden": "true" }, text);
56 document.body.append(field);
57 field.select();
58 try { done = document.execCommand("copy"); } catch (error) { done = false; }
59 field.remove();
60 }
61 if (button) {
62 const before = button.textContent;
63 button.textContent = done ? "Copied" : "Press Ctrl+C";
64 button.classList.toggle("done", done);
65 setTimeout(() => { button.textContent = before; button.classList.remove("done"); }, 1600);
66 }
67 return done;
68 }
69
70 function copyButton(text, label) {
71 const button = el("button", { class: "copy", type: "button", "aria-label": "Copy " + (label || text) }, "Copy");
72 button.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); copy(text, button); });
73 return button;
74 }
75
76 // highlight colours Go: keywords, strings, comments, numbers, calls. A
77 // small tokenizer, enough for the page's snippets.
78 const keywords = new Set(("break case chan const continue default defer else fallthrough for func go goto if import interface map package range " +
79 "return select struct switch type var nil true false iota any error string bool int int64 uint64 float64 byte rune").split(" "));
80 function highlight(code) {
81 const out = document.createDocumentFragment();
82 const pattern = /(\/\/[^\n]*|\/\*[\s\S]*?\*\/)|("(?:[^"\\\n]|\\.)*"|`[^`]*`|'(?:[^'\\\n]|\\.)*')|\b(\d+(?:\.\d+)?(?:e[+-]?\d+)?)\b|\b([A-Za-z_][A-Za-z0-9_]*)(?=\s*[(\[])|\b([A-Za-z_][A-Za-z0-9_]*)\b/g;
83 let at = 0;
84 for (const match of code.matchAll(pattern)) {
85 if (match.index > at) out.append(code.slice(at, match.index));
86 const [text, comment, string, number, call, word] = match;
87 let kind = "";
88 if (comment) kind = "tok-c";
89 else if (string) kind = "tok-s";
90 else if (number) kind = "tok-n";
91 else if (call) kind = keywords.has(call) ? "tok-k" : "tok-f";
92 else if (word && keywords.has(word)) kind = "tok-k";
93 out.append(kind ? el("span", { class: kind }, text) : text);
94 at = match.index + text.length;
95 }
96 if (at < code.length) out.append(code.slice(at));
97 return out;
98 }
99
100 function codeBlock(code) {
101 return el("pre", {}, el("code", {}, highlight(code)));
102 }
103
104 window.GF = { el, svg, append, store, copy, copyButton, highlight, codeBlock };
105})();