commit
c0bee7db78
6 changed files with 135 additions and 40 deletions
11
README.md
11
README.md
|
@ -26,7 +26,7 @@ Jotdown supports Rust edition 2021, i.e. Rust 1.56 and above.
|
|||
## Usage
|
||||
|
||||
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
|
||||
|
||||
|
@ -62,9 +62,12 @@ It will be placed in `~/.cargo/bin/jotdown`.
|
|||
|
||||
### Web demo
|
||||
|
||||
A version of Jotdown compiled to WebAssembly and runnable in a web browser is
|
||||
available at <https://hllmn.net/projects/jotdown/demo>. It can also be run
|
||||
locally:
|
||||
The web demo is a version of Jotdown compiled to WebAssembly and runnable in a
|
||||
web browser. It is useful for experimenting with the djot syntax and exploring
|
||||
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
|
||||
|
|
|
@ -7,9 +7,19 @@ ${WASM}: ${SRC}
|
|||
|
||||
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
|
||||
|
||||
clean:
|
||||
rm -rf pkg
|
||||
rm -rf pkg index.html
|
||||
cargo clean
|
||||
|
|
50
examples/jotdown_wasm/demo.html
Normal file
50
examples/jotdown_wasm/demo.html
Normal 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>
|
|
@ -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>
|
|
@ -1,6 +1,7 @@
|
|||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use jotdown::Render;
|
||||
use std::fmt::Write;
|
||||
|
||||
#[must_use]
|
||||
#[wasm_bindgen]
|
||||
|
@ -12,3 +13,52 @@ pub fn jotdown_render(djot: &str) -> String {
|
|||
.unwrap();
|
||||
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
|
||||
}
|
||||
|
|
17
src/attr.rs
17
src/attr.rs
|
@ -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
|
||||
// indirection instead of always 24 bytes.
|
||||
#[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>)>>>);
|
||||
|
||||
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)]
|
||||
enum State {
|
||||
Start,
|
||||
|
|
Loading…
Reference in a new issue