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…
	
	Add table
		Add a link
		
	
		Reference in a new issue