From 27b72e37d308a9bb0af7fe9654ea092a0c0fdd82 Mon Sep 17 00:00:00 2001 From: graphiteisaac Date: Thu, 8 Oct 2026 11:55:06 +1100 Subject: [PATCH 1/4] add line numbers and error marking --- static/css/code/syntax-highlight.css | 66 ++++++- static/index.js | 286 ++++++++++++++------------- 2 files changed, 210 insertions(+), 142 deletions(-) diff --git a/static/css/code/syntax-highlight.css b/static/css/code/syntax-highlight.css index af9df72..16b393b 100644 --- a/static/css/code/syntax-highlight.css +++ b/static/css/code/syntax-highlight.css @@ -66,57 +66,73 @@ highlightJS mappings pre.hljs { background: var(--code-background); } + .hljs { color: var(--color-token-base); } + .hljs-punctuation { /* and operators */ color: var(--code-token-punctuation); } + .hljs-variable, .hljs-name { color: var(--code-token-base); } + .hljs-function-param { font-weight: bold; font-style: italic; color: var(--color-token-base); } + .hljs-operator { color: var(--code-token-operator); } + .hljs-keyword { color: var(--code-token-keyword); } + .hljs-boolean { color: var(--code-token-boolean); } + .hljs-number { color: var(--code-token-number); } + .hljs-type { color: var(--code-token-type); } + .hljs-function.function-name { color: var(--code-token-function-name); } + .hljs-function.function-call { font-style: italic; } + .hljs-function.function-params { color: var(--code-token-function); } + .hljs-attribute { color: var(--code-token-attribute); font-style: italic; } + .hljs-string { color: var(--code-token-string); } + .hljs-comment { color: var(--code-token-comment); font-style: italic; } + /* .hljs-symbol, .hljs-bullet, @@ -134,9 +150,11 @@ pre.hljs { .hljs-emphasis { font-style: italic; } + .hljs-strong { font-weight: bold; } + .hljs-link { text-decoration: underline; } @@ -148,49 +166,89 @@ CodeFlask mappings */ .codeflask .codeflask__textarea { - color: var(--code-background); /* Prevents rendering artifacts in dark mode */ - caret-color: var( - --code-token-base - ); /* Makes the text input cursor visible in dark mode */ + color: var(--code-background); + /* Prevents rendering artifacts in dark mode */ + caret-color: var(--code-token-base); + /* Makes the text input cursor visible in dark mode */ +} + +.codeflask .codeflask__lines { + background: var(--code-background); + color: var(--code-token-punctuation); + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 13px; + line-height: 20px; + padding: var(--gap) 4px; +} + +.codeflask__lines__line { + position: relative; +} + +.codeflask__lines__line.line_has_error { + color: var(--brand-error); +} + +.codeflask__lines__line.line_has_error:before { + content: ''; + display: block; + position: absolute; + left: 0; + top: 5px; + bottom: 5px; + width: 3px; + background: var(--brand-error); } .codeflask { background: var(--code-background); color: var(--code-token-base); } + .codeflask .token.punctuation { color: var(--code-token-punctuation); } + .codeflask .token.keyword { color: var(--code-token-keyword); } + .codeflask .token.operator { color: var(--code-token-operator); } + .codeflask .token.string { color: var(--code-token-string); } + .codeflask .token.comment { color: var(--code-token-comment); } + .codeflask .token.function { color: var(--code-token-function); } + .codeflask .token.boolean { color: var(--code-token-boolean); } + .codeflask .token.number { color: var(--code-token-number); } + .codeflask .token.selector { color: var(--code-token-selector); } + .codeflask .token.property { color: var(--code-token-property); } + .codeflask .token.tag { color: var(--code-token-tag); } + .codeflask .token.attr-value { color: var(--code-token-attr-value); } diff --git a/static/index.js b/static/index.js index 90b99c2..cb26900 100644 --- a/static/index.js +++ b/static/index.js @@ -16,95 +16,96 @@ const compiledErlangEl = document.querySelector("#compiled-erlang"); const initialCode = document.querySelector("#code").innerHTML; const prismGrammar = { - comment: { - pattern: /\/\/.*/, - greedy: true, - }, - function: /([a-z_][a-z0-9_]+)(?=\()/, - keyword: - /\b(use|case|if|@external|@deprecated|fn|import|let|assert|try|pub|type|opaque|const|panic|todo|as|echo)\b/, - symbol: { - pattern: /([A-Z][A-Za-z0-9_]+)/, - greedy: true, - }, - operator: { - pattern: - /(<<|>>|<-|->|\|>|<>|\.\.|<=\.?|>=\.?|==\.?|!=\.?|<\.?|>\.?|&&|\|\||\+\.?|-\.?|\/\.?|\*\.?|%\.?|=)/, - greedy: true, - }, - string: { - pattern: /"((?:[^"\\]|\\.)*)"/, - greedy: true, - }, - module: { - pattern: /([a-z][a-z0-9_]*)\./, - inside: { - punctuation: /\./, - }, - alias: "keyword", - }, - punctuation: /[.\\:,{}()]/, - number: - /\b(?:0b[0-1]+|0o[0-7]+|[[:digit:]][[:digit:]_]*(\\.[[:digit:]]*)?|0x[[:xdigit:]]+)\b/, + comment: { + pattern: /\/\/.*/, + greedy: true, + }, + function: /([a-z_][a-z0-9_]+)(?=\()/, + keyword: + /\b(use|case|if|@external|@deprecated|fn|import|let|assert|try|pub|type|opaque|const|panic|todo|as|echo)\b/, + symbol: { + pattern: /([A-Z][A-Za-z0-9_]+)/, + greedy: true, + }, + operator: { + pattern: + /(<<|>>|<-|->|\|>|<>|\.\.|<=\.?|>=\.?|==\.?|!=\.?|<\.?|>\.?|&&|\|\||\+\.?|-\.?|\/\.?|\*\.?|%\.?|=)/, + greedy: true, + }, + string: { + pattern: /"((?:[^"\\]|\\.)*)"/, + greedy: true, + }, + module: { + pattern: /([a-z][a-z0-9_]*)\./, + inside: { + punctuation: /\./, + }, + alias: "keyword", + }, + punctuation: /[.\\:,{}()]/, + number: + /\b(?:0b[0-1]+|0o[0-7]+|[[:digit:]][[:digit:]_]*(\\.[[:digit:]]*)?|0x[[:xdigit:]]+)\b/, }; function clearElement(target) { - while (target.firstChild) { - target.removeChild(target.firstChild); - } + while (target.firstChild) { + target.removeChild(target.firstChild); + } } function appendCode(target, content, className) { - if (!content) return; - const escapedContent = escapeHtml(content); - const formattedContent = highlightEchoPath(escapedContent); - const element = document.createElement("pre"); - const code = document.createElement("code"); - code.innerHTML = formattedContent; - element.appendChild(code); - element.className = className; - target.appendChild(element); + if (!content) return; + const escapedContent = escapeHtml(content); + const formattedContent = highlightEchoPath(escapedContent); + const element = document.createElement("pre"); + const code = document.createElement("code"); + code.innerHTML = formattedContent; + element.appendChild(code); + element.className = className; + target.appendChild(element); } function escapeHtml(content) { - const div = document.createElement("div"); - div.textContent = content; - return div.innerHTML; + const div = document.createElement("div"); + div.textContent = content; + return div.innerHTML; } function highlightEchoPath(content) { - if (typeof content !== "string") return content; - const pathRegex = /(^src\/main.gleam:\d+$)/gm; - const newContent = content.replace( - pathRegex, - '$1', - ); - return newContent; + if (typeof content !== "string") return content; + const pathRegex = /(^src\/main.gleam:\d+$)/gm; + const newContent = content.replace( + pathRegex, + '$1', + ); + return newContent; } function highlightOutput(target, childClassName) { - // Disable annoying warnings from hljs - const warn = console.warn; - console.warn = () => {}; - target.querySelectorAll(`.${childClassName}`).forEach((element) => { - hljs.highlightElement(element); - }); - console.warn = warn; + // Disable annoying warnings from hljs + const warn = console.warn; + console.warn = () => {}; + target.querySelectorAll(`.${childClassName}`).forEach((element) => { + hljs.highlightElement(element); + }); + console.warn = warn; } const editor = new CodeFlask("#editor-target", { - language: "gleam", - defaultTheme: false, + language: "gleam", + defaultTheme: false, + lineNumbers: true, }); editor.addLanguage("gleam", prismGrammar); editor.updateCode(initialCode); function debounce(fn, delay) { - let timer = null; - return (...args) => { - clearTimeout(timer); - timer = setTimeout(() => fn(...args), delay); - }; + let timer = null; + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => fn(...args), delay); + }; } // Whether the worker is currently working or not, used to avoid sending @@ -116,43 +117,52 @@ let queuedWork = undefined; const worker = new Worker("worker.js", { type: "module" }); function sendToWorker(code) { - if (workerWorking) { - queuedWork = code; - return; - } - workerWorking = true; - worker.postMessage(code); + if (workerWorking) { + queuedWork = code; + return; + } + workerWorking = true; + worker.postMessage(code); } worker.onmessage = (event) => { - // Handle the result of the compilation and execution - const result = event.data; - clearElement(outputEl); - clearElement(compiledJavascriptEl); - clearElement(compiledErlangEl); - if (result.log) { - appendCode(outputEl, result.log, "log"); - } - if (result.error) { - appendCode(outputEl, result.error, "error"); - } - if (result.js) { - appendCode(compiledJavascriptEl, result.js, "javascript"); - } - if (result.erlang) { - appendCode(compiledErlangEl, result.erlang, "erlang"); - } - for (const warning of result.warnings || []) { - appendCode(outputEl, warning, "warning"); - } - - highlightOutput(compiledJavascriptEl, "javascript"); - highlightOutput(compiledErlangEl, "erlang"); - - // Deal with any queued work - workerWorking = false; - if (queuedWork) sendToWorker(queuedWork); - queuedWork = undefined; + // Handle the result of the compilation and execution + const result = event.data; + clearElement(outputEl); + clearElement(compiledJavascriptEl); + clearElement(compiledErlangEl); + if (result.log) { + appendCode(outputEl, result.log, "log"); + } + if (result.error) { + const lines = [...result.error.matchAll(/\.gleam:(\d+)/gi)].map( + (x) => x[1], + ); + for (const line of lines) { + document + .querySelector(`.codeflask__lines__line:nth-of-type(${line})`) + .classList.add("line_has_error"); + console.log(line); + } + appendCode(outputEl, result.error, "error"); + } + if (result.js) { + appendCode(compiledJavascriptEl, result.js, "javascript"); + } + if (result.erlang) { + appendCode(compiledErlangEl, result.erlang, "erlang"); + } + for (const warning of result.warnings || []) { + appendCode(outputEl, warning, "warning"); + } + + highlightOutput(compiledJavascriptEl, "javascript"); + highlightOutput(compiledErlangEl, "erlang"); + + // Deal with any queued work + workerWorking = false; + if (queuedWork) sendToWorker(queuedWork); + queuedWork = undefined; }; editor.onUpdate(debounce((code) => sendToWorker(code), 200)); @@ -165,57 +175,57 @@ editor.onUpdate(debounce((code) => sendToWorker(code), 200)); * } */ function makeV1Hash(code) { - return lz.compressToBase64( - JSON.stringify({ - version: 1, - content: code, - }), - ); + return lz.compressToBase64( + JSON.stringify({ + version: 1, + content: code, + }), + ); } function parseV1Hash(obj) { - if (obj.version !== 1) { - throw new Error("Unsupported version"); - } - return obj.content; + if (obj.version !== 1) { + throw new Error("Unsupported version"); + } + return obj.content; } function parseHash(hash) { - let obj; - try { - obj = JSON.parse(lz.decompressFromBase64(hash)); - } catch (e) { - return null; - } - if (!obj) { - return null; - } - switch (obj.version) { - case 1: - return parseV1Hash(obj); - } - return null; + let obj; + try { + obj = JSON.parse(lz.decompressFromBase64(hash)); + } catch (e) { + return null; + } + if (!obj) { + return null; + } + switch (obj.version) { + case 1: + return parseV1Hash(obj); + } + return null; } if (window.location.hash) { - const hash = window.location.hash.slice(1); - const code = parseHash(hash); - if (code) { - editor.updateCode(code); - } + const hash = window.location.hash.slice(1); + const code = parseHash(hash); + if (code) { + editor.updateCode(code); + } } const shareButton = document.querySelector("#share-button"); function share() { - const code = editor.getCode(); - const compressed = makeV1Hash(code); - const url = `${window.location.origin}${window.location.pathname}#${compressed}`; - navigator.clipboard.writeText(url); - const before = shareButton.textContent; - shareButton.textContent = "Link copied!"; - setTimeout(() => { - shareButton.textContent = before; - }, 1000); + const code = editor.getCode(); + const compressed = makeV1Hash(code); + const url = `${window.location.origin}${window.location.pathname}#${compressed}`; + navigator.clipboard.writeText(url); + const before = shareButton.textContent; + shareButton.textContent = "Link copied!"; + setTimeout(() => { + shareButton.textContent = before; + }, 1000); } shareButton.addEventListener("click", share); From 7011169bb8f1ccb176c38fd5f66d65b819278704 Mon Sep 17 00:00:00 2001 From: graphiteisaac Date: Fri, 9 Oct 2026 11:08:39 +1100 Subject: [PATCH 2/4] undo auto-format, whoops! --- static/css/code/syntax-highlight.css | 39 +--- static/index.js | 292 +++++++++++++-------------- 2 files changed, 150 insertions(+), 181 deletions(-) diff --git a/static/css/code/syntax-highlight.css b/static/css/code/syntax-highlight.css index 16b393b..3b6fe08 100644 --- a/static/css/code/syntax-highlight.css +++ b/static/css/code/syntax-highlight.css @@ -66,73 +66,57 @@ highlightJS mappings pre.hljs { background: var(--code-background); } - .hljs { color: var(--color-token-base); } - .hljs-punctuation { /* and operators */ color: var(--code-token-punctuation); } - .hljs-variable, .hljs-name { color: var(--code-token-base); } - .hljs-function-param { font-weight: bold; font-style: italic; color: var(--color-token-base); } - .hljs-operator { color: var(--code-token-operator); } - .hljs-keyword { color: var(--code-token-keyword); } - .hljs-boolean { color: var(--code-token-boolean); } - .hljs-number { color: var(--code-token-number); } - .hljs-type { color: var(--code-token-type); } - .hljs-function.function-name { color: var(--code-token-function-name); } - .hljs-function.function-call { font-style: italic; } - .hljs-function.function-params { color: var(--code-token-function); } - .hljs-attribute { color: var(--code-token-attribute); font-style: italic; } - .hljs-string { color: var(--code-token-string); } - .hljs-comment { color: var(--code-token-comment); font-style: italic; } - /* .hljs-symbol, .hljs-bullet, @@ -150,11 +134,9 @@ pre.hljs { .hljs-emphasis { font-style: italic; } - .hljs-strong { font-weight: bold; } - .hljs-link { text-decoration: underline; } @@ -166,10 +148,10 @@ CodeFlask mappings */ .codeflask .codeflask__textarea { - color: var(--code-background); - /* Prevents rendering artifacts in dark mode */ - caret-color: var(--code-token-base); - /* Makes the text input cursor visible in dark mode */ + color: var(--code-background); /* Prevents rendering artifacts in dark mode */ + caret-color: var( + --code-token-base + ); /* Makes the text input cursor visible in dark mode */ } .codeflask .codeflask__lines { @@ -197,6 +179,7 @@ CodeFlask mappings top: 5px; bottom: 5px; width: 3px; + border-radius: 2px; background: var(--brand-error); } @@ -204,51 +187,39 @@ CodeFlask mappings background: var(--code-background); color: var(--code-token-base); } - .codeflask .token.punctuation { color: var(--code-token-punctuation); } - .codeflask .token.keyword { color: var(--code-token-keyword); } - .codeflask .token.operator { color: var(--code-token-operator); } - .codeflask .token.string { color: var(--code-token-string); } - .codeflask .token.comment { color: var(--code-token-comment); } - .codeflask .token.function { color: var(--code-token-function); } - .codeflask .token.boolean { color: var(--code-token-boolean); } - .codeflask .token.number { color: var(--code-token-number); } - .codeflask .token.selector { color: var(--code-token-selector); } - .codeflask .token.property { color: var(--code-token-property); } - .codeflask .token.tag { color: var(--code-token-tag); } - .codeflask .token.attr-value { color: var(--code-token-attr-value); } diff --git a/static/index.js b/static/index.js index cb26900..2738b17 100644 --- a/static/index.js +++ b/static/index.js @@ -16,96 +16,95 @@ const compiledErlangEl = document.querySelector("#compiled-erlang"); const initialCode = document.querySelector("#code").innerHTML; const prismGrammar = { - comment: { - pattern: /\/\/.*/, - greedy: true, - }, - function: /([a-z_][a-z0-9_]+)(?=\()/, - keyword: - /\b(use|case|if|@external|@deprecated|fn|import|let|assert|try|pub|type|opaque|const|panic|todo|as|echo)\b/, - symbol: { - pattern: /([A-Z][A-Za-z0-9_]+)/, - greedy: true, - }, - operator: { - pattern: - /(<<|>>|<-|->|\|>|<>|\.\.|<=\.?|>=\.?|==\.?|!=\.?|<\.?|>\.?|&&|\|\||\+\.?|-\.?|\/\.?|\*\.?|%\.?|=)/, - greedy: true, - }, - string: { - pattern: /"((?:[^"\\]|\\.)*)"/, - greedy: true, - }, - module: { - pattern: /([a-z][a-z0-9_]*)\./, - inside: { - punctuation: /\./, - }, - alias: "keyword", - }, - punctuation: /[.\\:,{}()]/, - number: - /\b(?:0b[0-1]+|0o[0-7]+|[[:digit:]][[:digit:]_]*(\\.[[:digit:]]*)?|0x[[:xdigit:]]+)\b/, + comment: { + pattern: /\/\/.*/, + greedy: true, + }, + function: /([a-z_][a-z0-9_]+)(?=\()/, + keyword: + /\b(use|case|if|@external|@deprecated|fn|import|let|assert|try|pub|type|opaque|const|panic|todo|as|echo)\b/, + symbol: { + pattern: /([A-Z][A-Za-z0-9_]+)/, + greedy: true, + }, + operator: { + pattern: + /(<<|>>|<-|->|\|>|<>|\.\.|<=\.?|>=\.?|==\.?|!=\.?|<\.?|>\.?|&&|\|\||\+\.?|-\.?|\/\.?|\*\.?|%\.?|=)/, + greedy: true, + }, + string: { + pattern: /"((?:[^"\\]|\\.)*)"/, + greedy: true, + }, + module: { + pattern: /([a-z][a-z0-9_]*)\./, + inside: { + punctuation: /\./, + }, + alias: "keyword", + }, + punctuation: /[.\\:,{}()]/, + number: + /\b(?:0b[0-1]+|0o[0-7]+|[[:digit:]][[:digit:]_]*(\\.[[:digit:]]*)?|0x[[:xdigit:]]+)\b/, }; function clearElement(target) { - while (target.firstChild) { - target.removeChild(target.firstChild); - } + while (target.firstChild) { + target.removeChild(target.firstChild); + } } function appendCode(target, content, className) { - if (!content) return; - const escapedContent = escapeHtml(content); - const formattedContent = highlightEchoPath(escapedContent); - const element = document.createElement("pre"); - const code = document.createElement("code"); - code.innerHTML = formattedContent; - element.appendChild(code); - element.className = className; - target.appendChild(element); + if (!content) return; + const escapedContent = escapeHtml(content); + const formattedContent = highlightEchoPath(escapedContent); + const element = document.createElement("pre"); + const code = document.createElement("code"); + code.innerHTML = formattedContent; + element.appendChild(code); + element.className = className; + target.appendChild(element); } function escapeHtml(content) { - const div = document.createElement("div"); - div.textContent = content; - return div.innerHTML; + const div = document.createElement("div"); + div.textContent = content; + return div.innerHTML; } function highlightEchoPath(content) { - if (typeof content !== "string") return content; - const pathRegex = /(^src\/main.gleam:\d+$)/gm; - const newContent = content.replace( - pathRegex, - '$1', - ); - return newContent; + if (typeof content !== "string") return content; + const pathRegex = /(^src\/main.gleam:\d+$)/gm; + const newContent = content.replace( + pathRegex, + '$1', + ); + return newContent; } function highlightOutput(target, childClassName) { - // Disable annoying warnings from hljs - const warn = console.warn; - console.warn = () => {}; - target.querySelectorAll(`.${childClassName}`).forEach((element) => { - hljs.highlightElement(element); - }); - console.warn = warn; + // Disable annoying warnings from hljs + const warn = console.warn; + console.warn = () => {}; + target.querySelectorAll(`.${childClassName}`).forEach((element) => { + hljs.highlightElement(element); + }); + console.warn = warn; } const editor = new CodeFlask("#editor-target", { - language: "gleam", - defaultTheme: false, - lineNumbers: true, + language: "gleam", + defaultTheme: false, }); editor.addLanguage("gleam", prismGrammar); editor.updateCode(initialCode); function debounce(fn, delay) { - let timer = null; - return (...args) => { - clearTimeout(timer); - timer = setTimeout(() => fn(...args), delay); - }; + let timer = null; + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => fn(...args), delay); + }; } // Whether the worker is currently working or not, used to avoid sending @@ -117,52 +116,51 @@ let queuedWork = undefined; const worker = new Worker("worker.js", { type: "module" }); function sendToWorker(code) { - if (workerWorking) { - queuedWork = code; - return; - } - workerWorking = true; - worker.postMessage(code); + if (workerWorking) { + queuedWork = code; + return; + } + workerWorking = true; + worker.postMessage(code); } worker.onmessage = (event) => { - // Handle the result of the compilation and execution - const result = event.data; - clearElement(outputEl); - clearElement(compiledJavascriptEl); - clearElement(compiledErlangEl); - if (result.log) { - appendCode(outputEl, result.log, "log"); - } - if (result.error) { - const lines = [...result.error.matchAll(/\.gleam:(\d+)/gi)].map( - (x) => x[1], - ); - for (const line of lines) { - document - .querySelector(`.codeflask__lines__line:nth-of-type(${line})`) - .classList.add("line_has_error"); - console.log(line); - } - appendCode(outputEl, result.error, "error"); - } - if (result.js) { - appendCode(compiledJavascriptEl, result.js, "javascript"); - } - if (result.erlang) { - appendCode(compiledErlangEl, result.erlang, "erlang"); - } - for (const warning of result.warnings || []) { - appendCode(outputEl, warning, "warning"); + // Handle the result of the compilation and execution + const result = event.data; + clearElement(outputEl); + clearElement(compiledJavascriptEl); + clearElement(compiledErlangEl); + if (result.log) { + appendCode(outputEl, result.log, "log"); + } + if (result.error) { + const lines = [...result.error.matchAll(/\.gleam:(\d+)/gi)].map( + (x) => x[1], + ); + for (const line of lines) { + document + .querySelector(`.codeflask__lines__line:nth-of-type(${line})`) + .classList.add("line_has_error"); } - - highlightOutput(compiledJavascriptEl, "javascript"); - highlightOutput(compiledErlangEl, "erlang"); - - // Deal with any queued work - workerWorking = false; - if (queuedWork) sendToWorker(queuedWork); - queuedWork = undefined; + appendCode(outputEl, result.error, "error"); + } + if (result.js) { + appendCode(compiledJavascriptEl, result.js, "javascript"); + } + if (result.erlang) { + appendCode(compiledErlangEl, result.erlang, "erlang"); + } + for (const warning of result.warnings || []) { + appendCode(outputEl, warning, "warning"); + } + + highlightOutput(compiledJavascriptEl, "javascript"); + highlightOutput(compiledErlangEl, "erlang"); + + // Deal with any queued work + workerWorking = false; + if (queuedWork) sendToWorker(queuedWork); + queuedWork = undefined; }; editor.onUpdate(debounce((code) => sendToWorker(code), 200)); @@ -175,57 +173,57 @@ editor.onUpdate(debounce((code) => sendToWorker(code), 200)); * } */ function makeV1Hash(code) { - return lz.compressToBase64( - JSON.stringify({ - version: 1, - content: code, - }), - ); + return lz.compressToBase64( + JSON.stringify({ + version: 1, + content: code, + }), + ); } function parseV1Hash(obj) { - if (obj.version !== 1) { - throw new Error("Unsupported version"); - } - return obj.content; + if (obj.version !== 1) { + throw new Error("Unsupported version"); + } + return obj.content; } function parseHash(hash) { - let obj; - try { - obj = JSON.parse(lz.decompressFromBase64(hash)); - } catch (e) { - return null; - } - if (!obj) { - return null; - } - switch (obj.version) { - case 1: - return parseV1Hash(obj); - } - return null; + let obj; + try { + obj = JSON.parse(lz.decompressFromBase64(hash)); + } catch (e) { + return null; + } + if (!obj) { + return null; + } + switch (obj.version) { + case 1: + return parseV1Hash(obj); + } + return null; } if (window.location.hash) { - const hash = window.location.hash.slice(1); - const code = parseHash(hash); - if (code) { - editor.updateCode(code); - } + const hash = window.location.hash.slice(1); + const code = parseHash(hash); + if (code) { + editor.updateCode(code); + } } const shareButton = document.querySelector("#share-button"); function share() { - const code = editor.getCode(); - const compressed = makeV1Hash(code); - const url = `${window.location.origin}${window.location.pathname}#${compressed}`; - navigator.clipboard.writeText(url); - const before = shareButton.textContent; - shareButton.textContent = "Link copied!"; - setTimeout(() => { - shareButton.textContent = before; - }, 1000); + const code = editor.getCode(); + const compressed = makeV1Hash(code); + const url = `${window.location.origin}${window.location.pathname}#${compressed}`; + navigator.clipboard.writeText(url); + const before = shareButton.textContent; + shareButton.textContent = "Link copied!"; + setTimeout(() => { + shareButton.textContent = before; + }, 1000); } shareButton.addEventListener("click", share); From 287389e6afa0e1a5602459fa55a4bac79d603bed Mon Sep 17 00:00:00 2001 From: graphiteisaac Date: Sun, 11 Oct 2026 18:11:29 +1100 Subject: [PATCH 3/4] actually configure line numbers for codeflask.... silly me --- static/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/index.js b/static/index.js index 2738b17..ad74652 100644 --- a/static/index.js +++ b/static/index.js @@ -94,6 +94,7 @@ function highlightOutput(target, childClassName) { const editor = new CodeFlask("#editor-target", { language: "gleam", + lineNumbers: true, defaultTheme: false, }); editor.addLanguage("gleam", prismGrammar); From eb6b4147ed702d6585b753dd7180cd9ee91e9106 Mon Sep 17 00:00:00 2001 From: graphiteisaac Date: Sat, 17 Oct 2026 15:06:43 +1100 Subject: [PATCH 4/4] fix CSS formatting and patch line number background bug --- static/css/code/syntax-highlight.css | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/static/css/code/syntax-highlight.css b/static/css/code/syntax-highlight.css index 3b6fe08..4362788 100644 --- a/static/css/code/syntax-highlight.css +++ b/static/css/code/syntax-highlight.css @@ -153,7 +153,6 @@ CodeFlask mappings --code-token-base ); /* Makes the text input cursor visible in dark mode */ } - .codeflask .codeflask__lines { background: var(--code-background); color: var(--code-token-punctuation); @@ -162,15 +161,16 @@ CodeFlask mappings line-height: 20px; padding: var(--gap) 4px; } - +.codeflask.codeflask--has-line-numbers:before { + /* Codeflask inserts this inline, so it's necessary to override it. */ + background: var(--code-background) !important; +} .codeflask__lines__line { position: relative; } - .codeflask__lines__line.line_has_error { color: var(--brand-error); } - .codeflask__lines__line.line_has_error:before { content: ''; display: block; @@ -182,7 +182,6 @@ CodeFlask mappings border-radius: 2px; background: var(--brand-error); } - .codeflask { background: var(--code-background); color: var(--code-token-base);