lib.rs 5.31 KB
mod data;

use js_sys::JsString;
use log::Level;
use mogwai::prelude::*;
use web_sys::{RequestInit, RequestMode, Request, Response};
use serde::{Deserialize, Serialize};
use std::panic;
use wasm_bindgen::prelude::*;

#[derive(Clone)]
enum AppLogic {
    Update,
    Toggle,
}

fn md_to_html(source: &str) -> String {
    use pulldown_cmark::{Parser, Options, html};

    let parser = Parser::new_ext(source, Options::all());
    let mut html_output = String::new();
    html::push_html(&mut html_output, parser);

    html_output
}

#[derive(Debug, Serialize, Deserialize)]
pub struct MarkdownJson {
    pub name: String,
    pub content: String,
    pub number_of_versions: i32,
}

pub type MarkdownsJson = Vec<MarkdownJson>;

async fn md_from_db() -> String {
    let window = web_sys::window().unwrap();
    let mut opts = RequestInit::new();
    opts.method("GET").mode(RequestMode::Cors);

    let request = Request::new_with_str_and_init("/api/v0/markdowns", &opts).unwrap();
    request.headers().set("Accept", "application/json").unwrap();

    let response = JsFuture::from(window.fetch_with_request(&request))
                 . await.unwrap()
                 . dyn_into::<Response>().unwrap();
    let data = String::from( JsFuture::from(response.text().unwrap())
                           . await.unwrap()
                           . dyn_into::<JsString>().unwrap() );
    let data :MarkdownsJson = serde_json::from_str(data.as_str()).unwrap();

    String::from(&data[0].content)
}

async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic>
                     , tx_view: broadcast::Sender<String>
                     , mut rx_dom: broadcast::Receiver<Dom> ) {
    let dom = rx_dom.next().await.unwrap();
    let mut show_edit = false;

    fn get_md(dom: &Dom) -> String {
        match dom.inner_read() {
            Either::Left(dom_js) => dom_js . to_owned()
                                           . dyn_into::<Node>().unwrap()
                                           . first_child().unwrap()
                                           . dyn_into::<HtmlElement>().unwrap()
                                           . inner_text(),
            _ => String::from(""),
        }
    }

    fn update(dom: &Dom) {
        if let Either::Left(dom_js) = dom.inner_read() {
            dom_js . to_owned()
                . dyn_into::<Node>().unwrap()
                . child_nodes().get(1).unwrap()
                . child_nodes().get(1).unwrap()
                . dyn_into::<HtmlElement>().unwrap()
                . set_inner_html(md_to_html(get_md(dom).as_str()).as_str())
        };
    }

    update(&dom);

    while let Some(msg) = rx_logic.next().await {
        match msg {
            AppLogic::Update => update(&dom),
            AppLogic::Toggle => {
                show_edit = ! show_edit;
                match show_edit {
                    true => tx_view . broadcast(String::from("block"))
                                    . await.unwrap(),
                    false => tx_view . broadcast(String::from("none"))
                                     . await.unwrap(),
                };
            },
        }
    }
}

fn editor_view( tx_logic: broadcast::Sender<AppLogic>
              , rx_view: broadcast::Receiver<String>
              , tx_dom: broadcast::Sender<Dom>
              , init_data: &str
) -> ViewBuilder<Dom> {
    let ns = "http://www.w3.org/2000/svg";

    builder! {
        <div class="input"
             style:width="33%"
             on:input=tx_logic.sink().contra_map(|_| AppLogic::Update)
             capture:view=tx_dom.sink()>
            <div contenteditable="true"
                 style:cursor="text"
                 style:display=("none", rx_view)>
                <pre>{init_data}</pre>
            </div>
            <div>
                <button on:click=tx_logic . sink()
                                          . contra_map(|_| AppLogic::Toggle)>
                    <svg version="1.1" id="Capa_1" xmlns=ns
                         x="0px" y="0px" viewBox="0 0 220.001 220.001"
                         style:width="1.5em" style:height="1.5em">
                        <g xmlns=ns>
                            <polygon xmlns=ns points="0,220 59.34,213.86 6.143,160.661"></polygon>
                            <path xmlns=ns d="M132.018,34.787l53.197,53.197L69.568,203.631L16.37,
                                     150.434L132.018,34.787z M212.696,60.502c9.738-9.738,9.742-25.527,
                                     0-35.268l-17.93-17.93c-9.738-9.74-25.529-9.738-35.268,0l-17.346,
                                     17.347l53.199,53.196L212.696,60.502z"></path>
                        </g>
                    </svg>
                </button>
                <div></div>
            </div>
        </div>
    }
}

#[wasm_bindgen(start)]
pub async fn main() -> Result<(), JsValue> {
    panic::set_hook(Box::new(console_error_panic_hook::hook));
    console_log::init_with_level(Level::Trace).unwrap();

    let data = md_from_db().await;

    let (tx_dom, rx_dom) = broadcast::bounded(1);
    let (tx_logic, rx_logic) = broadcast::bounded(1);
    let (tx_view, rx_view) = broadcast::bounded(1);
    let comp = Component::from( editor_view(tx_logic, rx_view, tx_dom, data.as_str()) )
             . with_logic( editor_logic(rx_logic, tx_view, rx_dom) );

    let page = Component::from(builder! {{comp}});
    page.build()?.run()
}