diff --git a/Cargo.lock b/Cargo.lock index 9cbaf914..111cdcca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,7 +243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bitflags" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -370,7 +370,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -382,7 +382,7 @@ name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -659,7 +659,7 @@ name = "devise_core" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", @@ -670,7 +670,7 @@ name = "diesel" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "diesel_derives 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -923,7 +923,7 @@ name = "fsevent" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "fsevent-sys 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -953,7 +953,7 @@ name = "fuchsia-zircon" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1236,7 +1236,7 @@ name = "inotify" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1630,7 +1630,7 @@ name = "nix" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1656,7 +1656,7 @@ name = "notify" version = "4.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "filetime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "fsevent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "fsevent-sys 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1722,7 +1722,7 @@ name = "openssl" version = "0.10.22" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1966,6 +1966,7 @@ dependencies = [ "gettext-utils 0.1.0 (git+https://github.com/Plume-org/gettext-macros/?rev=a7c605f7edd6bfbfbfe7778026bfefd88d82db10)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "plume-api 0.3.0", + "pulldown-cmark 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "stdweb 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", "stdweb-internal-runtime 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2082,7 +2083,17 @@ name = "pulldown-cmark" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pulldown-cmark" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2686,7 +2697,7 @@ name = "shrinkwraprs" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3536,7 +3547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" "checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" -"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum bitpacking 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "667f3f518358b2cf64891b46a6dd2eb794e9f80d39f7eb5974f4784bcda9a61b" "checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" "checksum blowfish 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aeb80d00f2688459b8542068abd974cfb101e7a82182414a99b5026c0d85cc3" @@ -3730,6 +3741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5afecba86dcf1e4fd610246f89899d1924fe12e1e89f555eb7c7f710f3c5ad1d" "checksum pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15" +"checksum pulldown-cmark 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "77043da1282374688ee212dc44b3f37ff929431de9c9adc3053bd3cee5630357" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quick-xml 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1d8065cbb01701c11cc195cde85cbf39d1c6a80705b67a157ebb3042e0e5777f" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" diff --git a/plume-front/Cargo.toml b/plume-front/Cargo.toml index 42d53cf9..479f503a 100644 --- a/plume-front/Cargo.toml +++ b/plume-front/Cargo.toml @@ -12,3 +12,7 @@ gettext-utils = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a lazy_static = "1.3" plume-api = { path = "../plume-api" } serde_json = "1.0" + +[dependencies.pulldown-cmark] +default-features = false +version = "0.5" diff --git a/plume-front/src/editor.rs b/plume-front/src/editor.rs index dbf90d29..9436dea9 100644 --- a/plume-front/src/editor.rs +++ b/plume-front/src/editor.rs @@ -1,15 +1,180 @@ +use pulldown_cmark::{Event, Options, Parser, Tag}; use stdweb::{ unstable::{TryFrom, TryInto}, web::{event::*, html_element::*, *}, }; use CATALOG; -macro_rules! mv { - ( $( $var:ident ),* => $exp:expr ) => { - { - $( let $var = $var.clone(); )* - $exp +fn from_md(md: &str) { + let md_parser = Parser::new_ext(md, Options::all()); + md_parser.fold( + document().get_element_by_id("editor-main").unwrap(), + |last_elt, event| { + match event { + Event::Start(tag) => { + let new = match tag { + Tag::Paragraph => document().create_element("p").unwrap(), + Tag::Rule => document().create_element("hr").unwrap(), + Tag::Header(level) => { + document().create_element(&format!("h{}", level)).unwrap() + } + Tag::BlockQuote => document().create_element("blockquote").unwrap(), + Tag::CodeBlock(code) => { + let pre = document().create_element("pre").unwrap(); + let code_elt = document().create_element("code").unwrap(); + code_elt.append_child(&document().create_text_node(&code)); + pre.append_child(&code_elt); + pre + } + Tag::List(None) => document().create_element("ul").unwrap(), + Tag::List(Some(_start_index)) => document().create_element("ol").unwrap(), // TODO: handle start_index + Tag::Item => document().create_element("li").unwrap(), + Tag::FootnoteDefinition(def) => { + let note = document().create_element("div").unwrap(); + note.class_list().add("footnote"); + note.append_child(&document().create_text_node(&def)); + note + } + Tag::HtmlBlock => document().create_element("div").unwrap(), + Tag::Table(_alignements) => document().create_element("table").unwrap(), // TODO: handle alignements + Tag::TableHead => document().create_element("th").unwrap(), + Tag::TableRow => document().create_element("tr").unwrap(), + Tag::TableCell => document().create_element("td").unwrap(), + Tag::Emphasis => document().create_element("em").unwrap(), + Tag::Strong => document().create_element("strong").unwrap(), + Tag::Strikethrough => document().create_element("s").unwrap(), + Tag::Link(_link_type, url, text) => { + let url: &str = &url; + let text: &str = &text; + let link = document().create_element("a").unwrap(); + js! { + @{&link}.href = @{url}; + @{&link}.title = @{text}; + }; + link + } + Tag::Image(_link_type, url, text) => { + let url: &str = &url; + let text: &str = &text; + let img = document().create_element("img").unwrap(); + js! { + @{&img}.src = @{url}; + @{&img}.title = @{text}; + @{&img}.alt = @{text}; + }; + img + } + }; + last_elt.append_child(&new); + new + } + Event::End(_) => last_elt.parent_element().unwrap(), + Event::Text(text) => { + let node = document().create_text_node(&text); + last_elt.append_child(&node); + last_elt + } + Event::Code(code) => { + let elt = document().create_element("code").unwrap(); + let content = document().create_text_node(&code); + elt.append_child(&content); + last_elt.append_child(&elt); + last_elt + } + Event::Html(html) => { + // TODO: sanitize it? + last_elt.set_attribute("innerHtml", &html); + last_elt + } + Event::InlineHtml(html) => { + let elt = document().create_element("span").unwrap(); + elt.set_attribute("innerHtml", &html); + last_elt.append_child(&elt); + last_elt + } + Event::FootnoteReference(reference) => { + last_elt // TODO + } + Event::SoftBreak => { + last_elt.append_child(&document().create_element("br").unwrap()); + last_elt + } + Event::HardBreak => { + last_elt // TODO + } + Event::TaskListMarker(done) => { + last_elt // TODO + } + } + }, + ); +} + +fn to_md() -> String { + let root = document().get_element_by_id("editor-main").unwrap(); + fold_children(&root).join("") +} + +fn fold_children(elt: &Element) -> Vec { + elt.child_nodes().iter().fold(vec![], |mut blocks, node| { + blocks.push(html_to_md(&node)); + blocks + }) +} + +fn html_to_md(node: &Node) -> String { + console!(log, node); + if let Ok(elt) = Element::try_from(node.clone()) { + console!(log, elt.node_name().to_lowercase()); + match elt.node_name().to_lowercase().as_ref() { + "hr" => "---".into(), + "h1" => format!("# {}\n\n", fold_children(&elt).join("")), + "h2" => format!("## {}\n\n", fold_children(&elt).join("")), + "h3" => format!("### {}\n\n", fold_children(&elt).join("")), + "h4" => format!("#### {}\n\n", fold_children(&elt).join("")), + "h5" => format!("##### {}\n\n", fold_children(&elt).join("")), + "h6" => format!("###### {}\n\n", fold_children(&elt).join("")), + "blockquote" => format!("> {}\n\n", fold_children(&elt).join("> ")), + "pre" => format!("```\n{}\n```\n\n", node.text_content().unwrap_or_default()), + "li" => match elt + .parent_element() + .unwrap() + .node_name() + .to_lowercase() + .as_ref() + { + "ol" => format!( + "{}. {}\n", + elt.parent_element() + .unwrap() + .child_nodes() + .iter() + .position(|n| Element::try_from(n).unwrap() == elt) + .unwrap_or_default(), + fold_children(&elt).join(""), + ), + _ => format!("- {}\n", fold_children(&elt).join("")), + }, + "em" => format!("_{}_", fold_children(&elt).join("")), + "strong" => format!("**{}**", fold_children(&elt).join("")), + "s" => format!("~~{}~~", fold_children(&elt).join("")), + "a" => format!( + "[{}]({})", + fold_children(&elt).join(""), + String::try_from(js! { return @{&elt}.href }).unwrap() + ), + "img" => format!( + "![{}]({})", + String::try_from(js! { return @{&elt}.alt }).unwrap(), + String::try_from(js! { return @{&elt}.src }).unwrap() + ), + other => { + console!(log, "Warning: unhandled element:", other); + String::new() + } // TODO: refs, tables, raw html } + } else { + node.text_content().unwrap_or_default() } } @@ -116,27 +281,16 @@ fn init_editor() -> Result<(), EditorError> { // And pre-fill the new editor with this values let title = document().get_element_by_id("editor-title")?; let subtitle = document().get_element_by_id("editor-subtitle")?; - let content = document().get_element_by_id("editor-default-paragraph")?; + let source = get_elt_value("editor-content"); + + from_md(&source); title.add_event_listener(no_return); subtitle.add_event_listener(no_return); filter_paste(&title); filter_paste(&subtitle); - filter_paste(&content); - - // character counter - content.add_event_listener(mv!(content => move |_: KeyDownEvent| { - window().set_timeout(mv!(content => move || { - if let Some(e) = document().get_element_by_id("char-count") { - let count = chars_left("#plume-fallback-editor", &content).unwrap_or_default(); - let text = i18n!(CATALOG, "Around {} characters left"; count); - HtmlElement::try_from(e).map(|e| { - js!{@{e}.innerText = @{text}}; - }).ok(); - }; - }), 0); - })); + // TODO: filter_paste(&content); document() .get_element_by_id("publish")? @@ -224,6 +378,7 @@ fn save(is_draft: bool) { .ok(); } }); + console!(log, to_md()); let data = plume_api::posts::NewPostData { title: HtmlElement::try_from(document().get_element_by_id("editor-title").unwrap()) .unwrap() @@ -231,13 +386,7 @@ fn save(is_draft: bool) { subtitle: document() .get_element_by_id("editor-subtitle") .map(|s| HtmlElement::try_from(s).unwrap().inner_text()), - source: HtmlElement::try_from( - document() - .get_element_by_id("editor-default-paragraph") - .unwrap(), - ) - .unwrap() - .inner_text(), + source: to_md(), author: String::new(), // it is ignored anyway (TODO: remove it ??) blog_id: i32::try_from(js! { return window.blog_id }).ok(), published: Some(!is_draft), @@ -287,30 +436,3 @@ fn show_errors() { .unwrap(); } } - -fn chars_left(selector: &str, content: &Element) -> Option { - match document().query_selector(selector) { - Ok(Some(form)) => HtmlElement::try_from(form).ok().and_then(|form| { - if let Some(len) = form - .get_attribute("content-size") - .and_then(|s| s.parse::().ok()) - { - (js! { - let x = encodeURIComponent(@{content}.innerHTML) - .replace(/%20/g, "+") - .replace(/%0A/g, "%0D%0A") - .replace(new RegExp("[!'*()]", "g"), "XXX") // replace exceptions of encodeURIComponent with placeholder - .length + 2; - console.log(x); - return x; - }) - .try_into() - .map(|c: i32| len - c) - .ok() - } else { - None - } - }), - _ => None, - } -} diff --git a/plume-front/src/main.rs b/plume-front/src/main.rs index 32067f44..f5336a08 100644 --- a/plume-front/src/main.rs +++ b/plume-front/src/main.rs @@ -6,6 +6,7 @@ extern crate gettext; extern crate gettext_macros; #[macro_use] extern crate lazy_static; +extern crate pulldown_cmark; #[macro_use] extern crate stdweb; extern crate serde_json; diff --git a/static/css/_article.scss b/static/css/_article.scss index 6e679489..f5b4b362 100644 --- a/static/css/_article.scss +++ b/static/css/_article.scss @@ -411,7 +411,7 @@ main .article-meta { margin-top: 110px; } - #edition-area > *[contenteditable] { + #editor-title, #editor-subtitle, #editor-main > * { padding-left: 18px; border-left: 2px solid transparent; transition: border-left-color 0.1s ease-in; diff --git a/templates/posts/new.rs.html b/templates/posts/new.rs.html index 84e1aafa..d29ba296 100644 --- a/templates/posts/new.rs.html +++ b/templates/posts/new.rs.html @@ -25,12 +25,11 @@