Demo
use "quillo-ui.qwe"route GET / = <html> <head><style>{ui_css}</style></head> <body class="ui-root"> <Button href="/start">Get started</Button> </body> </html>curl -O https://quillolang.org/components/quillo-ui.qwe[server]port = 8080workers = 4 # one per core is plenty for most sites; the event loop does the rest, so raise it only when profiling says so[database]url = "sqlite://app.db"SELECT id, title FROM posts WHERE published ORDER BY created DESC;Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/code_block.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/code_block.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/code_block.qwe
let code_block_demo_source = `use "quillo-ui.qwe"route GET / = <html> <head><style>{ui_css}</style></head> <body class="ui-root"> <Button href="/start">Get started</Button> </body> </html>`let code_block_demo_config = `[server]port = 8080workers = 4 # one per core is plenty for most sites; the event loop does the rest, so raise it only when profiling says so[database]url = "sqlite://app.db"`view CodeBlockDemo() = <div.demo-stack> <CodeBlock filename="main.qwe" language="quillo" line_numbers={true} highlight="1,7" code={code_block_demo_source}/> <CodeBlock code="curl -O https://quillolang.org/components/quillo-ui.qwe"/> <CodeBlock filename="quillo.toml" wrap={true} line_numbers={true} start={10} highlight={[12]} code={code_block_demo_config}/> <CodeBlock language="sql" copy={false} code={"SELECT id, title\n FROM posts\n WHERE published\n ORDER BY created DESC;"}/> </div>Source
ui/code_block.qwe
// CodeBlock: code to read and copy, in a <pre>, with line numbers, lines picked// out and a header naming the file.//// <CodeBlock filename="main.qwe" line_numbers={true} highlight="3,5-7" code={source}/>// <CodeBlock language="sh" code="quillo run app.qwe --watch"/>//// code: the text, shown as it is. filename: a header above it; language: a small// label in the header ("sh", "quillo"). line_numbers: numbers down the left, which// are not copied with the text; start: the first one's number (1). highlight: the// lines to pick out, by their shown number: "3,5-7" or [3, 5, 6, 7] (each a <mark>,// so screen readers say so). copy (true): a Copy button that copies the code; it says// "Copied" for a moment, or "Could not copy" if the browser refuses (not https).// wrap: long lines wrap instead of scrolling sideways. max_height: a CSS height past// which it scrolls. label: what screen readers call it (the filename by default).import ui/theme.qwe use ui_clslet code_block_css = css`.ui-cb { min-width: 0; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-subtle); color: var(--ui-fg); font: 14px/1.4 var(--ui-font); overflow: hidden; }.ui-cb-head { display: flex; align-items: center; gap: 10px; min-height: 38px; padding: 0 6px 0 14px; border-bottom: 1px solid var(--ui-border); background: color-mix(in srgb, var(--ui-subtle) 60%, var(--ui-surface)); }.ui-cb-file { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 500 13px/1.3 var(--ui-mono); color: var(--ui-fg); }.ui-cb-lang { flex: none; padding: 2px 7px; border-radius: 4px; background: var(--ui-surface); color: var(--ui-muted); font: 600 11px/1.4 var(--ui-font); letter-spacing: .04em; text-transform: uppercase; }.ui-cb-copy { display: inline-flex; align-items: center; gap: 6px; height: 28px; margin-left: auto; padding: 0 9px; border: 1px solid transparent; border-radius: var(--ui-radius-sm); background: transparent; color: var(--ui-muted); font: 500 12px/1 var(--ui-font); cursor: pointer; }.ui-cb-copy:hover { border-color: var(--ui-border); background: var(--ui-surface); color: var(--ui-fg); }.ui-cb-copy:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-cb-copy svg { width: 14px; height: 14px; flex: none; }.ui-cb-copy-ok { color: var(--ui-success); }.ui-cb-copy-bad { color: var(--ui-danger); }.ui-cb-body { display: flex; align-items: flex-start; }.ui-cb-float { order: 2; flex: none; margin: 7px 6px 0 0; }.ui-cb-pre { flex: 1; min-width: 0; margin: 0; padding: 12px 0; overflow: auto; background: transparent; color: var(--ui-fg); font: 13px/1.6 var(--ui-mono); tab-size: 4; }.ui-cb-pre:focus-visible { outline: none; box-shadow: inset var(--ui-focus); }.ui-cb-pre code { display: block; min-width: max-content; font: inherit; }.ui-cb-wrap .ui-cb-pre code { min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; }.ui-cb-line { display: block; min-height: 1.6em; padding: 0 16px; background: none; color: inherit; }.ui-cb-hl { background: color-mix(in srgb, var(--ui-accent) 14%, transparent); box-shadow: inset 3px 0 0 var(--ui-accent); }.ui-cb-num { display: inline-block; min-width: 2.5ch; margin-right: 16px; color: var(--ui-muted); opacity: .75; text-align: right; user-select: none; -webkit-user-select: none; }.ui-cb-hl .ui-cb-num { color: var(--ui-accent); opacity: 1; }.ui-cb-wrap .ui-cb-numbered .ui-cb-line { padding-left: calc(16px + 2.5ch + 16px); text-indent: calc(-2.5ch - 16px); }`// The line numbers in "3,5-7" (or a list of them), as a list of ints.fn code_block_lines(spec) -> list { if spec == null { return [] } if type_of(spec) == "list" { return spec } flatten(split(str(spec), ",") | map((part) => { let p = split(trim(part), "-") let a = int(trim(p[0])) ?? null let b = len(p) > 1 ? (int(trim(p[1])) ?? null) : a a == null || b == null || b < a ? [] : range(a, b + 1) }))}// The code's lines, without the empty one after a final line break.fn code_block_split(code: str) -> list { let ls = split(replace(code, "\r\n", "\n"), "\n") len(ls) > 1 && last(ls) == "" ? take(ls, len(ls) - 1) : ls}component CodeBlock(code: str, filename: str? = null, language: str? = null, line_numbers: bool = false, start: int = 1, highlight = null, copy: bool = true, wrap: bool = false, max_height: str? = null, label: str? = null, class: str? = null) { style global code_block_css state result = "" // "", "ok" or "bad" if result != "" { every(1500, () => { result = "" }) } let lines = code_block_split(code) let marked = code_block_lines(highlight) let head = filename != null || language != null let copy_button = copy && <button type="button" class={ui_cls(["ui-cb-copy", !head && "ui-cb-float", result != "" && `ui-cb-copy-${result}`])} aria-label={filename != null ? `Copy ${filename}` : "Copy code"} @click={() => { if js("isSecureContext") != true { result = "bad" } else { js("navigator.clipboard.writeText", code).then((_ = null) => { result = "ok" }, (_ = null) => { result = "bad" }) } }}> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <path d={result == "ok" ? "M20 6 9 17l-5-5" : "M10 8h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2ZM4 16a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2"}/> </svg> <span aria-hidden="true">{result == "ok" ? "Copied" : result == "bad" ? "Could not copy" : "Copy"}</span> </button> <figure class={ui_cls(["ui-cb", wrap && "ui-cb-wrap", class])} style="margin: 0"> {head && <figcaption.ui-cb-head> {filename != null ? <span.ui-cb-file>{filename}</span> : <span.ui-cb-file></span>} {language != null && <span.ui-cb-lang>{language}</span>} {copy_button} </figcaption>} <div.ui-cb-body> {!head && copy_button} <pre.ui-cb-pre tabindex="0" aria-label={label ?? filename ?? "Code"} style={max_height != null ? `max-height: ${max_height}` : null}><code class={line_numbers ? "ui-cb-numbered" : null}> {for [i, l] in enumerate(lines) => { let n = start + i let gutter = line_numbers && <span.ui-cb-num aria-hidden="true">{n}</span> index_of(marked, n) != null ? <mark.ui-cb-line.ui-cb-hl>{gutter}{l}</mark> : <span.ui-cb-line>{gutter}{l}</span> }} </code></pre> </div> <span.ui-sr role="status">{result == "ok" ? "Copied" : result == "bad" ? "Could not copy" : ""}</span> </figure>}Related
InlineCodeA code span in running text, and a variant that copies its text when clicked.4 views · 8 downloadsMarkdownViewA small Markdown subset (headings, emphasis, links, lists, code, quotes) rendered safely as markup, in Quillo, with no HTML let through.4 views · 9 downloadsDiffViewThe lines added and removed between two texts, unified or side by side, with line numbers.7 views · 5 downloadsJsonTableJSON as a table: open nested objects and arrays in place or as the table, search at every depth, export as JSON or CSV, thousands of lines kept fast.9 views · 7 downloadsLogViewerLog lines with each level in its colour, buttons to show or hide levels, a search that marks matches, wrapping and following the end.8 views · 7 downloadsMatchHighlightMarks where the words of a search appear in a text, in any case, every time they do.7 views · 6 downloads
Thread
Thread 0 posts
On CodeBlock: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.