rcl/website/index.html
2025-03-02 20:51:55 +01:00

814 lines
27 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The RCL Configuration Language</title>
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Red+Hat+Display:ital,wght@0,300..900;1,300..900&family=Red+Hat+Mono:ital,wght@0,300..700;1,300..700&family=Red+Hat+Text:ital,wght@0,300..700;1,300..700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
border-spacing: 0;
}
html {
background-color: var(--oxford-blue);
color: #fff;
font-family: 'Red Hat Text', sans-serif;
font-optical-sizing: auto;
font-size: 18px;
line-height: 1.55;
--margin-small: 1.3rem;
--margin: 2.5rem;
--margin-large: 3.2rem;
--bullet-distance: -1.0rem;
--outset: -0.5rem;
--hsep: 1px;
--vsep: 0px;
/* Colors: https://coolors.co/000f29-033563-f5e5b8-7b0d1e-9f2042 */
--oxford-blue: #000f29ff;
/* --berkeley-blue: #033563ff; */
--berkeley-blue: #0e3259;
--dutch-white: #f5e5b8ff;
--burgundy: #7b0d1eff;
--amaranth-purple: #9f2042ff;
--off-white: #fafaf9;
--text-light: #335184;
--text-lighter: #36d;
--syn-error: #f25a0b;
--syn-accent: #ffd07e;
--syn-red: #ff7e7e;
--syn-blue: #4ccae5;
--syn-light-blue: #81baf7;
--link-blue: #4fbdec78;
--outline: #ffffff44;
}
.grid {
display: grid;
grid-template-columns: var(--margin) calc(100% - 2 * var(--margin)) var(--margin);
grid-template-areas: "lmargin content rmargin";
}
.span-full, .span-l2, .span-r2 { grid-column: content }
.wide { display: none; }
@media (min-width: 500px) {
.xnarrow {
display: none;
}
}
@media (min-width: 600px) {
html {
--margin: 3.2rem;
--margin-large: 4rem;
--bullet-distance: -1.2rem;
--outset: -1.2rem;
}
.narrow, .wxnarrow {
display: none;
}
.wide {
display: inline;
}
}
@media (min-width: 710px) {
.wnarrow {
display: none;
}
}
@media (min-width: 820px) {
html {
/* The separator rotates 90 degrees now. */
--hsep: 0px;
--vsep: 1px;
}
.xnarrow {
display: inline;
}
.grid {
grid-template-columns: var(--margin) 1fr 1fr 1fr 1fr var(--margin);
grid-template-areas: "lmargin c1 c2 c3 c4 rmargin";
}
.span-full {
grid-column: c1-start / c4-end
}
.span-l2 {
grid-column: c1-start / c2-end;
padding-right: var(--margin);
}
.span-r2 {
grid-column: c3-start / c4-end;
padding-left: var(--margin);
}
}
@media (min-width: 910px) {
.xnarrow {
display: none;
}
}
@media (min-width: 1000px) {
.grid {
grid-template-columns: auto 12rem 12rem 12rem 12rem auto;
}
}
@media (min-width: 1200px) {
html {
font-size: 20px;
}
}
h1 {
font-family: 'Red Hat Display', sans-serif;
font-size: 1rem;
}
a {
text-decoration: none;
color: inherit;
}
header {
padding-top: var(--margin-small);
padding-bottom: var(--margin-small);
word-spacing: 1rem;
}
header h1 {
font-weight: 900;
display: inline;
}
footer .sep {
margin-left: 0.3rem;
margin-right: 0.3rem;
}
section {
overflow: hidden;
background: var(--berkeley-blue);
color: #eef;
padding-top: var(--margin-large);
}
section.alt {
background: var(--off-white);
color: var(--text-light);
}
h1.tagline {
color: #fff;
font-size: 2.5rem;
line-height: 2.4rem;
font-weight: 700;
margin-bottom: 2rem;
}
.p-ul {
margin-top: var(--margin);
margin-bottom: 1em;
}
ul li {
position: relative;
list-style-type: none;
}
ul li:before {
/* color: #ffffff66; */
position: absolute;
left: var(--bullet-distance);
content: '•';
opacity: 0.4;
}
h2 {
font-size: 1.3rem;
font-weight: 400;
margin-bottom: 0.5rem;
}
p, pre, ul {
margin-bottom: var(--margin-large);
}
section a, footer a {
text-decoration-line: underline;
text-decoration-skip-ink: none;
text-decoration-color: var(--link-blue);
text-decoration-thickness: 3pt;
}
h2 a {
text-decoration: none;
}
section.alt h2 {
color: var(--text-lighter);
}
strong {
font-weight: 700;
}
p.warning strong {
margin-right: 0.2rem;
}
span.cc {
font-weight: 600;
}
code, pre {
font-family: 'Red Hat Mono', monospace;
font-size: 0.8rem;
}
.demo {
margin-left: var(--outset);
margin-right: var(--outset);
margin-top: -1rem;
margin-bottom: 0;
display: grid;
grid-template-columns: subgrid;
background-color: #00000066;
border: 1px solid var(--outline);
border-radius: 0.2rem;
overflow: hidden;
}
.demo h3 {
color: #ffffff99;
font-weight: inherit;
font-size: 0.8rem;
padding: 0.5rem;
padding-left: 0.8rem;
border-bottom: 1px solid var(--outline);
}
.demo > div {
position: relative;
}
.demo h3.label {
color: #ffffff77;
border-bottom: none;
position: absolute;
}
.demo .span-l2, .demo .span-r2, .demo .span-full {
padding: 0;
overflow-x: auto;
}
.demo .span-r2 {
/* This switches between top and left depending on the viewport width,
see the media query elsewhere. */
border-top: var(--hsep) solid var(--outline);
border-left: var(--vsep) solid var(--outline);
}
.focus-marker {
border: 3px solid transparent;
}
.focus-marker:has(code:focus-visible), .focus-marker:has(span:focus-visible) {
border: 3px solid var(--outline);
}
.focus-marker code:focus-visible, .focus-marker span:focus-visible {
outline: none;
}
.focus-marker:has(#in2) {
margin-left: -0.7em;
margin-right: 0.5rem;
border-radius: 0.25rem;
padding: 0.4rem;
padding-top: 0.2rem;
padding-bottom: 0.2rem;
}
#in2:before, #in2:after {
content: "'";
display: inline-block;
}
.demo pre {
min-width: min-content;
/* The regular margin is a bit much for the snippet,
instead apply the small margin twice. */
padding: var(--margin-small);
margin-bottom: var(--margin-small);
}
.demo pre code {
display: block;
padding-top: var(--margin-small);
padding-left: var(--margin-small);
}
code span.highlight { font-weight: 600; }
code span.error { font-weight: 600; color: var(--syn-error); }
code span.warning { font-weight: 600; color: var(--syn-accent); }
code span.keyword { font-weight: 600; color: var(--syn-light-blue); }
code span.comment { font-style: italic; opacity: 0.6; }
code span.type { color: var(--syn-accent); }
code span.string { color: var(--syn-red); }
code span.field { color: var(--syn-accent); }
code span.number { color: var(--syn-blue); }
.edit-contain {
position: relative;
}
.edit-front {
position: absolute;
top: 0;
caret-color: #fff;
color: transparent;
}
footer {
padding-top: var(--margin-large);
opacity: 0.6;
font-size: 0.8rem;
}
</style>
<script src="/rcl.js" defer></script>
</head>
<body>
<header class="grid">
<nav class="span-full">
<a href="/"><h1>RCL</h1></a>
<a href="#try-it-yourself" class="narrow">Play</a>
<a href="#try-it-yourself" class="wide">Playground</a>
<a href="https://docs.ruuda.nl/rcl">Docs</a>
<a href="https://github.com/ruuda/rcl">GitHub</a>
<a href="https://codeberg.org/ruuda/rcl" class="wide">Codeberg</a>
</nav>
</header>
<section class="grid">
<div class="span-l2">
<h1 class="tagline">Make<br>configuration<br>boring again</h1>
<p>RCL is a domain-specific language
for generating configuration files and querying json documents.
It extends json into a simple, gradually typed,
functional programming language that resembles Python and Nix.</p>
</div>
<div class="span-r2">
<pre><code>{
<span class="comment">// A silly snippet to show some</span>
<span class="comment">// features in a limited space.</span>
<span class="keyword">let</span> data: <span class="type">List</span>[<span class="type">String</span>] = <span class="narrow">
</span><span class="keyword">import</span> <span class="string">"data.rcl"</span>;
<span class="keyword">assert</span>
data.contains(<span class="string">"Assertions"</span>),
<span class="string">"Assertions are supported"</span>;
<span class="keyword">let</span> f = () => [<span class="xnarrow">
</span><span class="string">"List"</span>, <span class="xnarrow">
</span><span class="string">"Dict"</span>, <span class="xnarrow">
</span><span class="string">"Set"</span><span class="xnarrow">,
</span>];
<span class="field">features</span> = [
<span class="keyword">for</span> d <span class="keyword">in</span> data: d,
<span class="keyword">for</span> collection <span class="keyword">in</span> f():
<span class="string">f"{</span>collection<span class="string">} comprehensions"</span>,
],
<span class="field">export-to</span> = <span class="string">"json, yaml, toml, ..."</span>,
}</code></pre>
</div>
</section>
<section class="grid alt">
<div class="feature-block span-l2">
<h2>Connect anything</h2>
<p>Generate json, yaml, toml, or other text-based configuration
for tools that do not natively talk to each other,
all from a single source of truth.</p>
</div>
<div class="feature-block span-r2">
<h2>Remove repetition</h2>
<p>Stop copy-pasting.
Use variables, loops, imports, and functions to avoid duplication.
Bump <span class="cc">ubuntu-20.04</span> to <span class="cc">24.04</span>
in <em>one</em> place.</p>
</div>
<div class="feature-block span-l2">
<h2>Fight complexity, not tools</h2>
<p>Spend time building your infrastructure,
not debugging whitespace and escaping issues in your string templates.
Generate correct yaml, json, and toml.
</p>
</div>
<div class="feature-block span-r2">
<h2>Familiar syntax</h2>
<p>Have you used Python, TypeScript or Rust before?
Then you will find RCL obvious to read and natural to write.
</p>
</div>
<div class="feature-block span-l2">
<h2>Built-in json queries</h2>
<p>Need to drill down into a json document,
but cant figure out the <span class="cc">jq</span> syntax?
Try <a href="https://docs.ruuda.nl/rcl/rcl_query/"><span class="cc">rcl query</span></a> instead.
Scroll down for an example.
</p>
</div>
<div class="feature-block span-r2">
<h2>Python module</h2>
<p>You can always export json from the command line
and load it into your program.
For Python we can skip that intermediate stage:
<a href="https://docs.ruuda.nl/rcl/python_bindings/#loads"><span class="cc">rcl.loads</span></a>
is a drop-in replacement for <span class="cc">json.loads</span>.
</div>
</section>
<section class="grid">
<div class="span-full">
<h2><a href="#try-it-yourself" id="try-it-yourself">Try it yourself</a></h2>
<p>Suppose we run a webserver that serves both
<span class="cc">www.example.com</span> and
<span class="cc">docs.example.com</span>.
We have to create two very similar DNS records pointing to that servers address.
One way to manage those is with Terraform/OpenTofu.
The output below is a
<a href="https://registry.terraform.io/providers/cloudflare/cloudflare/4.49.1/docs/resources/record">simplified form</a>
of a
<a href="https://opentofu.org/docs/language/syntax/json/">json config</a>
that defines the records.</p>
</div>
<div class="span-full demo">
<div class="span-full">
<h3>rcl evaluate --format=json</h3>
</div>
<div class="span-l2 focus-marker edit-contain">
<h3 class="label">input</h3>
<pre class="edit-back" id="shadow1"><code>{
<span class="keyword">let</span> <span class="field">tld</span> = <span class="string">"com"</span>;
<span class="comment">// for tld in ["com", "net"]:</span>
<span class="keyword">for</span> <span class="field">subdomain</span> <span class="keyword">in</span> [<span class="string">"docs"</span>, <span class="string">"www"</span>]:
<span class="string">f"a_record_</span><span class="escape">{</span><span class="field">subdomain</span><span class="escape">}</span><span class="string">_example_</span><span class="escape">{</span><span class="field">tld</span><span class="escape">}</span><span class="string">"</span>:
{
<span class="field">domain</span> = <span class="string">"example.com"</span>,
<span class="field">name</span> = <span class="field">subdomain</span>,
<span class="field">type</span> = <span class="string">"A"</span>,
<span class="field">value</span> = <span class="string">"93.184.216.34"</span>,
}
}</code></pre>
<pre class="edit-front"><code id="in1" spellcheck="false">{
let tld = "com";
// for tld in ["com", "net"]:
for subdomain in ["docs", "www"]:
f"a_record_{subdomain}_example_{tld}":
{
domain = "example.com",
name = subdomain,
type = "A",
value = "93.184.216.34",
}
}</code></pre>
</div>
<div class="span-r2">
<h3 class="label">output</h3>
<pre id="out1"><code>{
<span class="field">"a_record_docs_example_com"</span>: {
<span class="field">"domain"</span>: <span class="string">"example.com"</span>,
<span class="field">"name"</span>: <span class="string">"docs"</span>,
<span class="field">"type"</span>: <span class="string">"A"</span>,
<span class="field">"value"</span>: <span class="string">"93.184.216.34"</span>
},
<span class="field">"a_record_www_example_com"</span>: {
<span class="field">"domain"</span>: <span class="string">"example.com"</span>,
<span class="field">"name"</span>: <span class="string">"www"</span>,
<span class="field">"type"</span>: <span class="string">"A"</span>,
<span class="field">"value"</span>: <span class="string">"93.184.216.34"</span>
}
}
</code></pre>
</div></div>
<div class="span-full">
<p class="p-ul"><strong>The input above is editable.</strong>
How would you change it to emit records for
<span class="cc">www.example.net</span> and
<span class="cc">docs.example.net</span>
in addition to <span class="cc">example.com</span>?
Try uncommenting the <span class="cc">tld</span> loop.
<noscript>
<br><br>
You need to enable Javascript to use the live demo.
It runs a WebAssembly version of RCL in your browser.
No server ever sees your keystrokes.
This is the only script on the page,
there are no trackers or other malware.
The RCL code is open source,
and the scripts on this page are not obfuscated
so you can verify them yourself.
</noscript>
</p>
</div>
</section>
<section class="grid">
<div class="span-full">
<h2><a href="#intuitive-json-queries" id="intuitive-json-queries">Intuitive json queries</a></h2>
<p>Suppose a cloud provider has an API that returns the regions it
supports, and you want to know the details of the region with id
<span class="cc">nrt</span>. Below is a query that does this.</p>
</div>
<div class="span-full demo">
<div class="span-full"><pre><code><span class="field">$</span> curl --silent <br class="wxnarrow"
>https://api.vultr.com/v2/regions <br class="wnarrow"
><span class="field">|</span> rcl query
<div class="focus-marker"><span id="in2" spellcheck="false" class="highlight">input.regions.key_by(r =&gt; r.id).nrt</span></div></code><div id="out2"><code>{
<span class="field">city</span> = <span class="string">"Tokyo"</span>,
<span class="field">continent</span> = <span class="string">"Asia"</span>,
<span class="field">country</span> = <span class="string">"JP"</span>,
<span class="field">id</span> = <span class="string">"nrt"</span>,
<span class="field">options</span> = [
<span class="string">"ddos_protection"</span>,
<span class="string">"block_storage_high_perf"</span>,
<span class="string">"block_storage_storage_opt"</span>,
<span class="string">"load_balancers"</span>,
<span class="string">"kubernetes"</span>,
],
}</code></div></pre>
</div>
</div>
<div class="span-full">
<p class="p-ul">The above query is editable.
Can you change the query to return the ids of all regions where the
<span class="cc">block_storage_high_perf</span> option is available?
Some ideas to get you started:</p>
<ul>
<li>Change the query to just <span class="cc">input</span>.
What does the input look like?</li>
<li>Wrap <span class="cc">input.regions</span> in a
<a href="https://docs.ruuda.nl/rcl/syntax/#comprehensions">list comprehension</a>.</li>
<li>Use <span class="cc">if</span> to filter elements.</li>
<li>Use <a href="https://docs.ruuda.nl/rcl/type_list/#contains">the <span class="cc">List.contains</span> method</a>
to check whether a list contains a particular element.</li>
<li>Alternatively, try
the <a href="https://docs.ruuda.nl/rcl/type_list/#map"><span class="cc">map</span></a>
and <a href="https://docs.ruuda.nl/rcl/type_list/#filter"><span class="cc">filter</span></a>
methods.</li>
</ul>
</div>
</section>
<section class="grid alt">
<div class="span-full">
<h2><a href="#installation" id="installation">Installation</a></h2>
<p>
RCL is written in Rust and has few dependencies,
so it is quick and easy to build from source with <a href="https://doc.rust-lang.org/cargo/index.html">Cargo</a>.
There is no need to pipe arbitrary code from the web into your shell.
<br><br>
<code>
<span class="number">$</span>
git clone https://github.com/ruuda/rcl && cd rcl<br>
<span class="number">$</span>
cargo build --release<br>
<span class="number">$</span>
target/release/rcl --help
</code>
<br><br>
See <a href="https://docs.ruuda.nl/rcl/installation/">the manual</a> for alternative installation methods.
</p>
</div>
<div class="feature-block span-l2">
<h2><a href="#further-resources" id="further-resources">Further resources</a></h2>
<ul>
<li><a href="https://docs.ruuda.nl/rcl/syntax_highlighting/">Set up syntax highlighting</a></li>
<li><a href="https://docs.ruuda.nl/rcl/tutorial/">Check out the tutorial</a></li>
<li><a href="https://docs.ruuda.nl/rcl/">Check out the documentation</a></li>
<li><a href="https://ruudvanasseldonk.com/2024/a-reasonable-configuration-language">Read the background story</a></li>
</ul>
</div>
<div class="feature-block span-r2">
<h2><a href="#floss" id="floss">Free &amp; open source</a></h2>
<p>RCL is free/libre software,
licensed under the Apache 2.0 license.
The project is developed in the open
and the source code is available on
<a href="https://github.com/ruuda/rcl">GitHub</a> and
<a href="https://codeberg.org/ruuda/rcl">Codeberg</a>.</p>
</div>
<div class="feature-block span-l2">
<h2><a href="#get-in-touch" id="get-in-touch">Get in touch</a></h2>
<p>
If you want to share your thoughts,
you can <a href="https://ruudvanasseldonk.com/contact">send an email</a>
or reach out <a href="https://fosstodon.org/@ruuda">on Mastodon</a>.
To report a technical defect you can open an issue
<a href="https://codeberg.org/ruuda/rcl/issues">on Codeberg</a> or
<a href="https://github.com/ruuda/rcl/issues">on GitHub</a>.
</p>
</div>
<div class="feature-block span-r2">
<h2><a href="#status" id="status">Status</a></h2>
<p>RCL is a hobby project without stability promise.
It is usable and useful, well-tested, and well-documented,
but also still experimental, and it may have breaking changes.
Syntax highlighting is available for major editors
like Vim, Emacs, Helix, and Zed.
</p>
</div>
<div class="span-full">
<h2><a href="#support-rcl" id="support-rcl">Support RCL</a></h2>
<p>One thing that holds RCL back from being useful to more people
is the lack of widespread support for syntax highlighting,
in particular on platforms such as GitHub.
If RCL is useful to you,
you can help by using RCL publicly in a GitHub repository
<a href="https://github.com/github-linguist/linguist/blob/4ac734c15a96f9e16fd12330d0cb8de82274f700/CONTRIBUTING.md#adding-a-language">to demonstrate traction</a>.
Use it seriously of course, please dont game the metric.
Other things you can help with are getting RCL packaged for your favorite package manager,
and developing syntax highlighting for your favorite editor if it is not yet supported.
</p>
</div>
</section>
<footer class="grid">
<div class="span-full">
<p>Copyright 2025 <a href="https://ruudvanasseldonk.com">Ruud van Asseldonk</a>
<span class="sep">·</span> <a href="https://github.com/ruuda/rcl">GitHub</a>
<span class="sep">·</span> <a href="https://codeberg.org/ruuda/rcl">Codeberg</a>
</p>
</div>
</footer>
</body>
<script>
window.onload = function() {
const {
rcl_evaluate_json,
rcl_evaluate_query_value,
rcl_evaluate_query,
rcl_highlight,
} = wasm_bindgen;
var wasmPromise = null;
var wasmLoaded = false;
var jsonPromise = null;
var jsonValue = null;
const maxLen = 4000;
// TODO: Infer from window width.
const printWidth = 40;
async function ensureWasmInitialized() {
if (wasmLoaded) return;
if (wasmPromise === null) wasmPromise = wasm_bindgen();
await wasmPromise;
wasmLoaded = true;
}
async function ensureJsonValue() {
if (jsonValue !== null) return jsonValue;
if (jsonPromise === null) jsonPromise = fetch("/vultr.json").then(r => r.text());
const wasm = ensureWasmInitialized();
const json = await jsonPromise;
await wasm;
// We have to check again that the value is still null, because we may
// not be the first task to wait for the json response.
if (jsonValue === null) {
jsonValue = rcl_evaluate_query_value(json);
}
return jsonValue;
}
async function onInputEvaluate(inBox, outBox) {
// If the user started typing but the wasm module is still loading,
// give some feedback about that.
if (!wasmLoaded) {
const msgNode = document.createElement("code");
msgNode.textContent = "Loading WASM module ...";
outBox.replaceChild(msgNode, outBox.firstChild);
}
const outNode = document.createElement("code");
await ensureWasmInitialized();
rcl_evaluate_json(inBox.textContent, outNode, printWidth, maxLen);
outBox.replaceChild(outNode, outBox.firstChild);
}
async function onInputQuery(inBox, outBox, evt) {
// If the user started typing but the wasm module or data is still loading,
// give some feedback about that.
if (jsonValue === null) {
const msgNode = document.createElement("code");
msgNode.textContent = wasmLoaded
? "Loading JSON data ..."
: "Loading WASM module ...";
outBox.replaceChild(msgNode, outBox.firstChild);
}
const query = evt.target.value;
const outNode = document.createElement("code");
const input = await ensureJsonValue();
rcl_evaluate_query(input, inBox.textContent, outNode, printWidth, maxLen);
outBox.replaceChild(outNode, outBox.firstChild);
}
var goodInput = in1.textContent;
async function onInputHighlight(inBox, outBox) {
const outNode = document.createElement("code");
if (!wasmLoaded) {
// If the user started typing but the wasm module is still loading, then
// we don't highlight for now. Instant feedback is more important.
outNode.textContent = inBox.textContent;
} else {
const newInput = inBox.textContent;
const wasGood = rcl_highlight(newInput, goodInput, outNode);
if (wasGood) goodInput = newInput;
}
outBox.replaceChild(outNode, outBox.firstChild);
}
function onInputStripHtml(inBox) {
inBox.normalize();
const text = inBox.textContent;
var didEdit = false;
inBox.childNodes.forEach((child) => {
if (child.nodeType !== Node.TEXT_NODE) {
inBox.removeChild(child);
didEdit = true;
}
});
if (didEdit) {
inBox.textContent = text;
}
}
// By default, the tab key moves focus, but after observing others try to
// use the webpage, every single one of them tried to press tab to indent
// and got confused by the single jump. So hijack the tab to act as indent
// instead.
function onKeyDown(evt) {
if (evt.code === "Tab") {
// Don't move focus.
evt.preventDefault();
// Insert two spaces. If the selection has a length, we could indent
// every line, but that starts to get fancy, we are not building a text
// editor here!
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const node = document.createTextNode(" ");
range.insertNode(node);
selection.collapseToEnd();
// After editing the selection, the `input` even does not trigger,
// do that manually.
const inputEvent = new Event("input");
evt.target.dispatchEvent(inputEvent);
}
}
// Note, in1, in2, out1, out2 are all available as local variables because
// they have an id in the DOM. No need for getElementById any more.
// Don't allow editing the examples before the page is fully loaded, because
// then we miss edit events. In Chromium we can prevent markup but Firefox
// does not yet support it.
try {
in1.contentEditable = "plaintext-only";
in2.contentEditable = "plaintext-only";
} catch {
in1.contentEditable = true;
in2.contentEditable = true;
in1.addEventListener("input", (evt) => onInputStripHtml(in1));
in2.addEventListener("input", (evt) => onInputStripHtml(in2));
}
// We don't load the wasm module instantly. If somebody opens the page but
// doesn't try to edit the example, we don't have to load the module. The
// flip side is that there is a small delay when you start editing. We can
// mitigate that somewhat by starting the load already when focus lands in
// the editable area.
in1.addEventListener("focus", () => ensureWasmInitialized());
// As for typing, we first handle the input event for highlighting, and only
// after that we compute the result. Evaluate takes ~3ms but highlighting
// only ~0.3ms, and the delay in when text appears seems noticeable.
in1.addEventListener("input", (evt) => onInputHighlight(in1, shadow1));
in1.addEventListener("input", (evt) => onInputEvaluate(in1, out1));
in1.addEventListener("keydown", onKeyDown);
in2.addEventListener("focus", () => ensureJsonValue());
in2.addEventListener("input", (evt) => onInputQuery(in2, out2, evt));
in2.addEventListener("keydown", onKeyDown);
}
</script>
</html>