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
|
## 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
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 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
|
||||||
|
}
|
||||||
|
|
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
|
// 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,
|
||||||
|
|
Loading…
Reference in a new issue