PR #32 wasm demo improvements

Merge branch 'wasm'
This commit is contained in:
Noah Hellman 2023-03-27 20:16:56 +02:00
commit c0bee7db78
6 changed files with 135 additions and 40 deletions

View file

@ -26,7 +26,7 @@ Jotdown supports Rust edition 2021, i.e. Rust 1.56 and above.
## Usage ## Usage
Jotdown is primarily a parsing library but also has a minimal CLI Jotdown is primarily a parsing library but also has a minimal CLI
implementation and a simple online demo version. implementation and a simple web demo version.
### Library ### Library
@ -62,9 +62,12 @@ It will be placed in `~/.cargo/bin/jotdown`.
### Web demo ### Web demo
A version of Jotdown compiled to WebAssembly and runnable in a web browser is The web demo is a version of Jotdown compiled to WebAssembly and runnable in a
available at <https://hllmn.net/projects/jotdown/demo>. It can also be run web browser. It is useful for experimenting with the djot syntax and exploring
locally: what events are emitted or what output is rendered.
An online version is available at <https://hllmn.net/projects/jotdown/demo>. It
can also be run locally:
``` ```
$ cd examples/jotdown_wasm $ cd examples/jotdown_wasm

View file

@ -7,9 +7,19 @@ ${WASM}: ${SRC}
wasm: ${WASM} wasm: ${WASM}
run: ${WASM} index.html: Makefile demo.html
echo '<!DOCTYPE html><html>' > $@
echo '<head>' >> $@
echo '<title>Jotdown Demo</title>' >> $@
echo '</head>' >> $@
echo '<body style="display:flex;flex-direction:column;height:100vh;margin:0">' >> $@
cat demo.html >> $@
echo '</body>' >> $@
echo '</html>' >> $@
run: ${WASM} index.html
python -m http.server python -m http.server
clean: clean:
rm -rf pkg rm -rf pkg index.html
cargo clean cargo clean

View file

@ -0,0 +1,50 @@
<div id="jotdown" style="flex-grow:1;display:flex;flex-direction:column">
<script type="module">
import init, {
jotdown_render,
jotdown_parse,
jotdown_parse_indent,
} from './pkg/jotdown_wasm.js';
await init();
let output = document.getElementById("output");
let input = document.getElementById("input");
let fmt = document.getElementById("fmt");
function render() {
if (fmt.value == "html") {
output.classList.add("verbatim")
output.innerText = jotdown_render(input.innerText);
} else if (fmt.value == "events") {
output.classList.add("verbatim")
output.innerText = jotdown_parse(input.innerText);
} else if (fmt.value == "events_indent") {
output.classList.add("verbatim")
output.innerText = jotdown_parse_indent(input.innerText);
} else if (fmt.value == "preview") {
output.classList.remove("verbatim")
output.innerHTML = jotdown_render(input.innerText);
}
}
render()
input.onkeyup = render;
fmt.onchange = render;
// auto focus on input on load
setTimeout(() => { input.focus(); }, 0);
</script>
<div>
<select id="fmt">
<option value="preview">preview</option>
<option value="html">html</option>
<option value="events">events</option>
<option value="events_indent">events (indented)</option>
</select>
</div>
<div id="panes" style="display:flex;height:100%;gap:1rem">
<pre id="input" contenteditable="true" placeholder="Input djot here" style="width:50%;height:100%;overflow:scroll;resize:none;box-sizing:border-box;margin:0">*Hello world!*</pre>
<pre id="output" readonly style="width:50%;height:100%;overflow:scroll;box-sizing:border-box;margin:0"></pre>
</div>
</div>

View file

@ -1,33 +0,0 @@
<select id="fmt"><option value="preview">preview</option><option value="html">html</option></select>
<div id="jotdown" style="display:flex;">
<script type="module">
import init, { jotdown_render } from './pkg/jotdown_wasm.js';
await init();
let output = document.getElementById("output");
let input = document.getElementById("input");
let fmt = document.getElementById("fmt");
function render() {
let html = jotdown_render(input.innerText);
console.log(fmt.value);
if (fmt.value == "html") {
output.classList.add("verbatim")
output.innerText = html;
} else if (fmt.value == "preview") {
output.classList.remove("verbatim")
output.innerHTML = html;
}
}
render()
input.onkeyup = render;
fmt.onchange = render;
// auto focus on input on load
setTimeout(() => { input.focus(); }, 0);
</script>
<pre id="input" contenteditable="true" placeholder="Input djot here" style="width:50%;height:100%;min-height:8em;max-height:20em;resize:none;margin:0">*Hello world!*</pre>
<pre id="output" readonly style="width:50%;height:100%;margin:0;min-height:8em;max-height:20em"></div></pre>
</div>

View file

@ -1,6 +1,7 @@
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use jotdown::Render; use jotdown::Render;
use std::fmt::Write;
#[must_use] #[must_use]
#[wasm_bindgen] #[wasm_bindgen]
@ -12,3 +13,52 @@ pub fn jotdown_render(djot: &str) -> String {
.unwrap(); .unwrap();
html html
} }
#[must_use]
#[wasm_bindgen]
pub fn jotdown_parse(djot: &str) -> String {
jotdown::Parser::new(djot)
.map(|e| format!("{:?}\n", e))
.collect()
}
#[must_use]
#[wasm_bindgen]
pub fn jotdown_parse_indent(djot: &str) -> String {
let mut level = 0;
let mut out = String::new();
for e in jotdown::Parser::new(djot) {
if !matches!(e, jotdown::Event::End(..)) {
// use non-breaking space for indent because normal spaces gets squeezed by browser
let nbsp = '\u{00a0}';
(0..4 * level).for_each(|_| out.push(nbsp));
}
match e {
jotdown::Event::Start(c, attrs) => {
level += 1;
if c.is_block() {
out.push('[');
} else {
out.push('(');
}
out.write_fmt(format_args!("{:?}", c)).unwrap();
if c.is_block() {
out.push(']');
} else {
out.push(')');
}
if !attrs.is_empty() {
out.write_fmt(format_args!(" {:?}", attrs)).unwrap();
}
out.push('\n');
}
jotdown::Event::End(..) => {
level -= 1;
}
e => {
out.write_fmt(format_args!("{:?}\n", e)).unwrap();
}
};
}
out
}

View file

@ -98,7 +98,7 @@ impl<'s> Iterator for AttributeValueParts<'s> {
// Attributes are relatively rare, we choose to pay 8 bytes always and sometimes an extra // Attributes are relatively rare, we choose to pay 8 bytes always and sometimes an extra
// indirection instead of always 24 bytes. // indirection instead of always 24 bytes.
#[allow(clippy::box_vec)] #[allow(clippy::box_vec)]
#[derive(Debug, Clone, PartialEq, Eq, Default)] #[derive(Clone, PartialEq, Eq, Default)]
pub struct Attributes<'s>(Option<Box<Vec<(&'s str, AttributeValue<'s>)>>>); pub struct Attributes<'s>(Option<Box<Vec<(&'s str, AttributeValue<'s>)>>>);
impl<'s> Attributes<'s> { impl<'s> Attributes<'s> {
@ -202,6 +202,21 @@ impl<'s> FromIterator<(&'s str, &'s str)> for Attributes<'s> {
} }
} }
impl<'s> std::fmt::Debug for Attributes<'s> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{{")?;
let mut first = true;
for (k, v) in self.iter() {
if !first {
write!(f, ", ")?;
}
first = false;
write!(f, "{}=\"{}\"", k, v.raw)?;
}
write!(f, "}}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State { enum State {
Start, Start,