From 24ecb1511919e033052bf97f002202be15cd9c8a Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 03:19:46 +0900 Subject: [PATCH] Remplement plume_front::editor using web-sys --- Cargo.lock | 135 ++++---- plume-front/Cargo.toml | 19 ++ plume-front/src/editor.rs | 687 +++++++++++++++++++++++--------------- plume-front/src/lib.rs | 11 +- 4 files changed, 526 insertions(+), 326 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63e2c7dc..755e987e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,7 +9,7 @@ dependencies = [ "activitystreams-derive", "activitystreams-traits", "activitystreams-types", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", "serde_json", ] @@ -32,7 +32,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "670ef03168e704b0cae242e7a5d8b40506772b339687e01a3496fc4afe2e8542" dependencies = [ "failure", - "serde 1.0.118", + "serde 1.0.123", "serde_json", ] @@ -46,7 +46,7 @@ dependencies = [ "activitystreams-traits", "chrono", "mime 0.3.16", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", "serde_json", ] @@ -209,7 +209,7 @@ checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -353,7 +353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" dependencies = [ "byteorder 1.3.4", - "serde 1.0.118", + "serde 1.0.123", ] [[package]] @@ -526,7 +526,7 @@ dependencies = [ "libc", "num-integer", "num-traits 0.2.14", - "serde 1.0.118", + "serde 1.0.123", "time", "winapi 0.3.9", ] @@ -573,13 +573,23 @@ dependencies = [ "lazy_static", "nom 5.1.2", "rust-ini", - "serde 1.0.118", + "serde 1.0.123", "serde-hjson", "serde_json", "toml 0.5.8", "yaml-rust", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211" +dependencies = [ + "cfg-if 0.1.10", + "wasm-bindgen", +] + [[package]] name = "const-random" version = "0.1.13" @@ -654,7 +664,7 @@ dependencies = [ "idna 0.1.5", "log 0.4.11", "publicsuffix", - "serde 1.0.118", + "serde 1.0.123", "serde_json", "time", "try_from", @@ -966,7 +976,7 @@ checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -1161,7 +1171,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", "synstructure", ] @@ -1370,7 +1380,7 @@ dependencies = [ "proc-macro-hack 0.5.19", "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -2012,7 +2022,7 @@ dependencies = [ "log 0.4.11", "native-tls", "nom 4.2.3", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", "serde_json", ] @@ -2079,7 +2089,7 @@ dependencies = [ "lindera-dictionary", "lindera-ipadic", "lindera-ipadic-builder", - "serde 1.0.118", + "serde 1.0.123", "serde_json", ] @@ -2092,7 +2102,7 @@ dependencies = [ "bincode", "byteorder 1.3.4", "encoding", - "serde 1.0.118", + "serde 1.0.123", "yada", ] @@ -2225,7 +2235,7 @@ dependencies = [ "log 0.4.11", "phf", "phf_codegen", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", "serde_json", "string_cache", @@ -2312,7 +2322,7 @@ dependencies = [ "migrations_internals", "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -2860,7 +2870,7 @@ checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -2871,7 +2881,7 @@ checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -2908,7 +2918,7 @@ dependencies = [ "chrono", "indexmap", "line-wrap", - "serde 1.0.118", + "serde 1.0.123", "xml-rs", ] @@ -2945,7 +2955,7 @@ dependencies = [ "rsass", "ructe", "scheduled-thread-pool", - "serde 1.0.118", + "serde 1.0.123", "serde_json", "shrinkwraprs 0.2.3", "tracing", @@ -2959,7 +2969,7 @@ dependencies = [ name = "plume-api" version = "0.6.1-dev" dependencies = [ - "serde 1.0.118", + "serde 1.0.123", "serde_derive", ] @@ -2992,7 +3002,7 @@ dependencies = [ "regex-syntax 0.6.21", "reqwest 0.9.24", "rocket", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", "serde_json", "shrinkwraprs 0.3.0", @@ -3005,11 +3015,14 @@ dependencies = [ name = "plume-front" version = "0.6.1-dev" dependencies = [ + "console_error_panic_hook", "gettext", "gettext-macros", "gettext-utils", + "js-sys", "lazy_static", - "serde 1.0.118", + "serde 1.0.123", + "serde_derive", "serde_json", "stdweb", "stdweb-internal-runtime", @@ -3056,7 +3069,7 @@ dependencies = [ "rocket", "rocket_i18n", "scheduled-thread-pool", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", "serde_json", "shrinkwraprs 0.2.3", @@ -3532,7 +3545,7 @@ dependencies = [ "mime 0.3.16", "mime_guess 2.0.3", "native-tls", - "serde 1.0.118", + "serde 1.0.123", "serde_json", "serde_urlencoded 0.5.5", "socks", @@ -3571,7 +3584,7 @@ dependencies = [ "native-tls", "percent-encoding 2.1.0", "pin-project-lite 0.2.0", - "serde 1.0.118", + "serde 1.0.123", "serde_urlencoded 0.7.0", "tokio 0.2.24", "tokio-tls", @@ -3671,7 +3684,7 @@ dependencies = [ "log 0.4.11", "notify", "rocket", - "serde 1.0.118", + "serde 1.0.123", "serde_json", ] @@ -3683,7 +3696,7 @@ dependencies = [ "data-encoding", "ring", "rocket", - "serde 1.0.118", + "serde 1.0.123", "time", ] @@ -3771,7 +3784,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" dependencies = [ - "serde 1.0.118", + "serde 1.0.123", "serde_derive", ] @@ -3882,9 +3895,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.118" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" dependencies = [ "serde_derive", ] @@ -3904,13 +3917,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.118" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -3921,7 +3934,7 @@ checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" dependencies = [ "itoa", "ryu", - "serde 1.0.118", + "serde 1.0.123", ] [[package]] @@ -3941,7 +3954,7 @@ checksum = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" dependencies = [ "dtoa", "itoa", - "serde 1.0.118", + "serde 1.0.123", "url 1.7.2", ] @@ -3954,7 +3967,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.118", + "serde 1.0.123", ] [[package]] @@ -3994,7 +4007,7 @@ dependencies = [ "itertools 0.8.2", "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -4007,7 +4020,7 @@ dependencies = [ "itertools 0.8.2", "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -4129,7 +4142,7 @@ checksum = "a68c0ce28cf7400ed022e18da3c4591e14e1df02c70e93573cc59921b3923aeb" dependencies = [ "discard", "rustc_version", - "serde 1.0.118", + "serde 1.0.123", "serde_json", "stdweb-derive", "stdweb-internal-macros", @@ -4145,7 +4158,7 @@ checksum = "0e21ebd9179de08f2300a65454268a17ea3de204627458588c84319c4def3930" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", "syn 0.15.44", ] @@ -4159,7 +4172,7 @@ dependencies = [ "base-x", "proc-macro2 0.4.30", "quote 0.6.13", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", "serde_json", "sha1", @@ -4191,7 +4204,7 @@ dependencies = [ "new_debug_unreachable", "phf_shared", "precomputed-hash", - "serde 1.0.118", + "serde 1.0.123", "string_cache_codegen", "string_cache_shared", ] @@ -4290,9 +4303,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.56" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", @@ -4316,7 +4329,7 @@ checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", "unicode-xid 0.2.1", ] @@ -4335,7 +4348,7 @@ dependencies = [ "onig", "plist", "regex-syntax 0.6.21", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", "serde_json", "walkdir", @@ -4375,7 +4388,7 @@ dependencies = [ "rayon", "regex", "rust-stemmers", - "serde 1.0.118", + "serde 1.0.123", "serde_json", "smallvec 1.5.1", "snap", @@ -4480,7 +4493,7 @@ checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -4647,7 +4660,7 @@ checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -4795,7 +4808,7 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" dependencies = [ - "serde 1.0.118", + "serde 1.0.123", ] [[package]] @@ -4804,7 +4817,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ - "serde 1.0.118", + "serde 1.0.123", ] [[package]] @@ -4834,7 +4847,7 @@ checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -4873,7 +4886,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" dependencies = [ - "serde 1.0.118", + "serde 1.0.123", "tracing-core", ] @@ -4888,7 +4901,7 @@ dependencies = [ "lazy_static", "matchers", "regex", - "serde 1.0.118", + "serde 1.0.123", "serde_json", "sharded-slab", "smallvec 1.5.1", @@ -5074,7 +5087,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" dependencies = [ "rand 0.7.3", - "serde 1.0.118", + "serde 1.0.123", ] [[package]] @@ -5086,7 +5099,7 @@ dependencies = [ "idna 0.1.5", "lazy_static", "regex", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", "serde_json", "url 1.7.2", @@ -5188,7 +5201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" dependencies = [ "cfg-if 1.0.0", - "serde 1.0.118", + "serde 1.0.123", "serde_json", "wasm-bindgen-macro", ] @@ -5204,7 +5217,7 @@ dependencies = [ "log 0.4.11", "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", "wasm-bindgen-shared", ] @@ -5238,7 +5251,7 @@ checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5266,7 +5279,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec24b1b0700d4b466d280228ed0f62274eedeaa80206820f071fdc8ed787b664" dependencies = [ "reqwest 0.9.24", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", ] diff --git a/plume-front/Cargo.toml b/plume-front/Cargo.toml index 9e913892..9578346f 100644 --- a/plume-front/Cargo.toml +++ b/plume-front/Cargo.toml @@ -17,19 +17,38 @@ lazy_static = "1.3" serde = "1.0" serde_json = "1.0" wasm-bindgen = "0.2.70" +js-sys = "0.3.47" +serde_derive = "1.0.123" +console_error_panic_hook = "0.1.6" [dependencies.web-sys] version = "0.3.47" features = [ + 'console', + 'ClipboardEvent', + 'CssStyleDeclaration', + 'DataTransfer', 'Document', + 'DomStringMap', 'DomTokenList', 'Element', 'EventTarget', + 'FocusEvent', + 'History', + 'HtmlAnchorElement', + 'HtmlDocument', 'HtmlFormElement', 'HtmlInputElement', + 'HtmlSelectElement', + 'HtmlTextAreaElement', + 'KeyboardEvent', + 'Storage', + 'Location', + 'MouseEvent', 'Navigator', 'Node', 'NodeList', + 'Text', 'TouchEvent', 'Window' ] diff --git a/plume-front/src/editor.rs b/plume-front/src/editor.rs index 711e3791..6df999e5 100644 --- a/plume-front/src/editor.rs +++ b/plume-front/src/editor.rs @@ -1,9 +1,12 @@ use crate::CATALOG; -use serde::{Deserialize, Serialize}; -use std::sync::Mutex; -use stdweb::{ - unstable::{TryFrom, TryInto}, - web::{event::*, html_element::*, *}, +use js_sys::{encode_uri_component, Date, RegExp}; +use serde_derive::{Deserialize, Serialize}; +use std::{convert::TryInto, sync::Mutex}; +use wasm_bindgen::{prelude::*, JsCast, JsValue}; +use web_sys::{ + console, window, ClipboardEvent, Document, Element, Event, FocusEvent, HtmlAnchorElement, + HtmlDocument, HtmlElement, HtmlFormElement, HtmlInputElement, HtmlSelectElement, + HtmlTextAreaElement, KeyboardEvent, MouseEvent, Node, }; macro_rules! mv { @@ -15,32 +18,35 @@ macro_rules! mv { } } +fn document() -> Document { + window().unwrap().document().unwrap() +} + fn get_elt_value(id: &'static str) -> String { let elt = document().get_element_by_id(id).unwrap(); - let inp: Result = elt.clone().try_into(); - let textarea: Result = elt.clone().try_into(); - let select: Result = elt.try_into(); - inp.map(|i| i.raw_value()).unwrap_or_else(|_| { + let inp: Option<&HtmlInputElement> = elt.dyn_ref(); + let textarea: Option<&HtmlTextAreaElement> = elt.dyn_ref(); + let select: Option<&HtmlSelectElement> = elt.dyn_ref(); + inp.map(|i| i.value()).unwrap_or_else(|| { textarea .map(|t| t.value()) - .unwrap_or_else(|_| select.unwrap().raw_value()) + .unwrap_or_else(|| select.unwrap().value()) }) } fn set_value>(id: &'static str, val: S) { let elt = document().get_element_by_id(id).unwrap(); - let inp: Result = elt.clone().try_into(); - let textarea: Result = elt.clone().try_into(); - let select: Result = elt.try_into(); - inp.map(|i| i.set_raw_value(val.as_ref())) - .unwrap_or_else(|_| { - textarea - .map(|t| t.set_value(val.as_ref())) - .unwrap_or_else(|_| select.unwrap().set_raw_value(val.as_ref())) - }) + let inp: Option<&HtmlInputElement> = elt.dyn_ref(); + let textarea: Option<&HtmlTextAreaElement> = elt.dyn_ref(); + let select: Option<&HtmlSelectElement> = elt.dyn_ref(); + inp.map(|i| i.set_value(val.as_ref())).unwrap_or_else(|| { + textarea + .map(|t| t.set_value(val.as_ref())) + .unwrap_or_else(|| select.unwrap().set_value(val.as_ref())) + }) } -fn no_return(evt: KeyDownEvent) { +fn no_return(evt: KeyboardEvent) { if evt.key() == "Enter" { evt.prevent_default(); } @@ -50,7 +56,6 @@ fn no_return(evt: KeyDownEvent) { pub enum EditorError { NoneError, DOMError, - TypeError, } impl From for EditorError { @@ -58,22 +63,7 @@ impl From for EditorError { EditorError::NoneError } } -impl From for EditorError { - fn from(_: stdweb::web::error::InvalidCharacterError) -> Self { - EditorError::DOMError - } -} -impl From for EditorError { - fn from(_: stdweb::private::TODO) -> Self { - EditorError::DOMError - } -} -impl From for EditorError { - fn from(_: stdweb::private::ConversionError) -> Self { - EditorError::TypeError - } -} -const AUTOSAVE_DEBOUNCE_TIME: u32 = 5000; +const AUTOSAVE_DEBOUNCE_TIME: i32 = 5000; #[derive(Serialize, Deserialize)] struct AutosaveInformation { contents: String, @@ -84,10 +74,16 @@ struct AutosaveInformation { tags: String, title: String, } -js_serializable!(AutosaveInformation); fn is_basic_editor() -> bool { - if let Some(basic_editor) = window().local_storage().get("basic-editor") { - basic_editor == "true" + if let Some(basic_editor) = window() + .unwrap() + .local_storage() + .unwrap() + .unwrap() + .get("basic-editor") + .unwrap() + { + &basic_editor == "true" } else { false } @@ -96,65 +92,58 @@ fn get_title() -> String { if is_basic_editor() { get_elt_value("title") } else { - let title_field = HtmlElement::try_from( - document() - .query_selector("#plume-editor > h1") - .ok() - .unwrap() - .unwrap(), - ) - .ok() - .unwrap(); - title_field.inner_text() + document() + .query_selector("#plume-editor > h1") + .unwrap() + .unwrap() + .dyn_ref::() + .unwrap() + .inner_text() } } fn get_autosave_id() -> String { format!( "editor_contents={}", - window().location().unwrap().pathname().unwrap() + window().unwrap().location().pathname().unwrap() ) } fn get_editor_contents() -> String { if is_basic_editor() { get_elt_value("editor-content") } else { - let editor = - HtmlElement::try_from(document().query_selector("article").ok().unwrap().unwrap()) - .ok() - .unwrap(); - editor.child_nodes().iter().fold(String::new(), |md, ch| { + let editor = document().query_selector("article").unwrap().unwrap(); + let child_nodes = editor.child_nodes(); + let mut md = String::new(); + for i in 0..child_nodes.length() { + let ch = child_nodes.get(i).unwrap(); let to_append = match ch.node_type() { - NodeType::Element => { - if js! { return @{&ch}.tagName; } == "DIV" { - (js! { return @{&ch}.innerHTML; }) - .try_into() - .unwrap_or_default() + Node::ELEMENT_NODE => { + let elt = ch.dyn_ref::().unwrap(); + if elt.tag_name() == "DIV" { + elt.inner_html() } else { - (js! { return @{&ch}.outerHTML; }) - .try_into() - .unwrap_or_default() + elt.outer_html() } } - NodeType::Text => ch.node_value().unwrap_or_default(), + Node::TEXT_NODE => ch.node_value().unwrap_or_default(), _ => unreachable!(), }; - format!("{}\n\n{}", md, to_append) - }) + md = format!("{}\n\n{}", md, to_append); + } + md } } fn get_subtitle() -> String { if is_basic_editor() { get_elt_value("subtitle") } else { - let subtitle_element = HtmlElement::try_from( - document() - .query_selector("#plume-editor > h2") - .unwrap() - .unwrap(), - ) - .ok() - .unwrap(); - subtitle_element.inner_text() + document() + .query_selector("#plume-editor > h2") + .unwrap() + .unwrap() + .dyn_ref::() + .unwrap() + .inner_text() } } fn autosave() { @@ -169,27 +158,31 @@ fn autosave() { }; let id = get_autosave_id(); match window() + .unwrap() .local_storage() - .insert(&id, &serde_json::to_string(&info).unwrap()) + .unwrap() + .unwrap() + .set(&id, &serde_json::to_string(&info).unwrap()) { Ok(_) => {} - _ => console!(log, "Autosave failed D:"), + _ => console::log_1(&"Autosave failed D:".into()), } } -//This is only necessary until we go to stdweb 4.20 at least -fn confirm(message: &str) -> bool { - let result: bool = js! {return confirm(@{message});} == true; - result -} fn load_autosave() { - if let Some(autosave_str) = window().local_storage().get(&get_autosave_id()) { + if let Ok(Some(autosave_str)) = window() + .unwrap() + .local_storage() + .unwrap() + .unwrap() + .get(&get_autosave_id()) + { let autosave_info: AutosaveInformation = serde_json::from_str(&autosave_str).ok().unwrap(); let message = i18n!( CATALOG, "Do you want to load the local autosave last edited at {}?"; - Date::from_time(autosave_info.last_saved).to_date_string() + Date::new(&JsValue::from_f64(autosave_info.last_saved)).to_date_string().as_string().unwrap() ); - if confirm(&message) { + if let Ok(true) = window().unwrap().confirm_with_message(&message) { set_value("editor-content", &autosave_info.contents); set_value("title", &autosave_info.title); set_value("subtitle", &autosave_info.subtitle); @@ -202,18 +195,33 @@ fn load_autosave() { } } fn clear_autosave() { - window().local_storage().remove(&get_autosave_id()); - console!(log, &format!("Saved to {}", &get_autosave_id())); + window() + .unwrap() + .local_storage() + .unwrap() + .unwrap() + .remove_item(&get_autosave_id()) + .unwrap(); + console::log_1(&&format!("Saved to {}", &get_autosave_id()).into()); } +type TimeoutHandle = i32; lazy_static! { static ref AUTOSAVE_TIMEOUT: Mutex> = Mutex::new(None); } fn autosave_debounce() { + let window = window().unwrap(); let timeout = &mut AUTOSAVE_TIMEOUT.lock().unwrap(); if let Some(timeout) = timeout.take() { - timeout.clear(); + window.clear_timeout_with_handle(timeout); } - **timeout = Some(window().set_clearable_timeout(autosave, AUTOSAVE_DEBOUNCE_TIME)); + let callback = Closure::once(|| autosave()); + **timeout = window + .set_timeout_with_callback_and_timeout_and_arguments_0( + callback.as_ref().unchecked_ref(), + AUTOSAVE_DEBOUNCE_TIME, + ) + .ok(); + callback.forget(); } fn init_widget( parent: &Element, @@ -222,19 +230,33 @@ fn init_widget( content: String, disable_return: bool, ) -> Result { - let widget = placeholder(make_editable(tag).try_into()?, &placeholder_text); + let widget = placeholder( + make_editable(tag).dyn_into::().unwrap(), + &placeholder_text, + ); if !content.is_empty() { - widget.dataset().insert("edited", "true")?; + widget + .dataset() + .set("edited", "true") + .map_err(|_| EditorError::DOMError)?; } - widget.append_child(&document().create_text_node(&content)); + widget + .append_child(&document().create_text_node(&content)) + .map_err(|_| EditorError::DOMError)?; if disable_return { - widget.add_event_listener(no_return); + let callback = Closure::wrap(Box::new(no_return) as Box); + widget + .add_event_listener_with_callback("keydown", callback.as_ref().unchecked_ref()) + .map_err(|_| EditorError::DOMError)?; + callback.forget(); } - parent.append_child(&widget); + parent + .append_child(&widget) + .map_err(|_| EditorError::DOMError)?; // We need to do that to make sure the placeholder is correctly rendered - widget.focus(); - widget.blur(); + widget.focus().map_err(|_| EditorError::DOMError)?; + widget.blur().map_err(|_| EditorError::DOMError)?; filter_paste(&widget); @@ -243,48 +265,88 @@ fn init_widget( fn filter_paste(elt: &HtmlElement) { // Only insert text when pasting something - js! { - @{&elt}.addEventListener("paste", function (evt) { - evt.preventDefault(); - document.execCommand("insertText", false, evt.clipboardData.getData("text")); - }); - }; + let insert_text = Closure::wrap(Box::new(|evt: ClipboardEvent| { + evt.prevent_default(); + if let Some(data) = evt.clipboard_data() { + if let Ok(data) = data.get_data("text") { + document() + .dyn_ref::() + .unwrap() + .exec_command_with_show_ui_and_value("insertText", false, &data) + .unwrap(); + } + } + }) as Box); + elt.add_event_listener_with_callback("paste", insert_text.as_ref().unchecked_ref()) + .unwrap(); + insert_text.forget(); } pub fn init() -> Result<(), EditorError> { if let Some(ed) = document().get_element_by_id("plume-fallback-editor") { load_autosave(); - ed.add_event_listener(|_: SubmitEvent| clear_autosave()); + let callback = Closure::wrap(Box::new(|_| clear_autosave()) as Box); + ed.add_event_listener_with_callback("submit", callback.as_ref().unchecked_ref()) + .map_err(|_| EditorError::DOMError)?; + callback.forget(); } // Check if the user wants to use the basic editor if window() + .unwrap() .local_storage() + .unwrap() + .unwrap() .get("basic-editor") - .map(|x| x == "true") + .map(|x| x.is_some() && x.unwrap() == "true") .unwrap_or(true) { if let Some(editor) = document().get_element_by_id("plume-fallback-editor") { if let Ok(Some(title_label)) = document().query_selector("label[for=title]") { - let editor_button = document().create_element("a")?; - js! { @{&editor_button}.href = "#"; } - editor_button.add_event_listener(|_: ClickEvent| { - if window() + let editor_button = document() + .create_element("a") + .map_err(|_| EditorError::DOMError)?; + editor_button + .dyn_ref::() + .unwrap() + .set_href("#"); + let disable_basic_editor = Closure::wrap(Box::new(|_| { + let window = window().unwrap(); + if window .local_storage() - .insert("basic-editor", "false") + .unwrap() + .unwrap() + .set("basic-editor", "false") .is_err() { - console!(log, "Failed to write into local storage"); + console::log_1(&"Failed to write into local storage".into()); } - window().history().go(0).ok(); // refresh - }); - editor_button.append_child( - &document().create_text_node(&i18n!(CATALOG, "Open the rich text editor")), + window.history().unwrap().go_with_delta(0).ok(); // refresh + }) + as Box); + editor_button + .add_event_listener_with_callback( + "click", + disable_basic_editor.as_ref().unchecked_ref(), + ) + .map_err(|_| EditorError::DOMError)?; + disable_basic_editor.forget(); + editor_button + .append_child( + &document().create_text_node(&i18n!(CATALOG, "Open the rich text editor")), + ) + .map_err(|_| EditorError::DOMError)?; + editor + .insert_before(&editor_button, Some(&title_label)) + .ok(); + let callback = Closure::wrap( + Box::new(|_| autosave_debounce()) as Box ); - editor.insert_before(&editor_button, &title_label).ok(); document() .get_element_by_id("editor-content") .unwrap() - .add_event_listener(|_: KeyDownEvent| autosave_debounce()); + .add_event_listener_with_callback("keydown", callback.as_ref().unchecked_ref()) + .map_err(|_| EditorError::DOMError)?; + callback.forget(); } } @@ -297,14 +359,26 @@ pub fn init() -> Result<(), EditorError> { fn init_editor() -> Result<(), EditorError> { if let Some(ed) = document().get_element_by_id("plume-editor") { // Show the editor - js! { @{&ed}.style.display = "block"; }; + ed.dyn_ref::() + .unwrap() + .style() + .set_property("display", "block") + .map_err(|_| EditorError::DOMError)?; // And hide the HTML-only fallback let old_ed = document().get_element_by_id("plume-fallback-editor")?; let old_title = document().get_element_by_id("plume-editor-title")?; - js! { - @{&old_ed}.style.display = "none"; - @{&old_title}.style.display = "none"; - }; + old_ed + .dyn_ref::() + .unwrap() + .style() + .set_property("display", "none") + .map_err(|_| EditorError::DOMError)?; + old_title + .dyn_ref::() + .unwrap() + .style() + .set_property("display", "none") + .map_err(|_| EditorError::DOMError)?; // Get content from the old editor (when editing an article for instance) let title_val = get_elt_value("title"); @@ -326,35 +400,44 @@ fn init_editor() -> Result<(), EditorError> { content_val.clone(), false, )?; - js! { @{&content}.innerHTML = @{content_val}; }; + content.set_inner_html(&content_val); // character counter - content.add_event_listener(mv!(content => move |_: KeyDownEvent| { - window().set_timeout(mv!(content => move || { + let character_counter = Closure::wrap(Box::new(mv!(content => move |_| { + let update_char_count = Closure::wrap(Box::new(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(); + e.dyn_ref::().map(|e| { + e.set_inner_text(&text); + }).unwrap(); }; - }), 0); + })) as Box); + window().unwrap().set_timeout_with_callback_and_timeout_and_arguments(update_char_count.as_ref().unchecked_ref(), 0, &js_sys::Array::new()).unwrap(); + update_char_count.forget(); autosave_debounce(); - })); + })) as Box); + content + .add_event_listener_with_callback("keydown", character_counter.as_ref().unchecked_ref()) + .map_err(|_| EditorError::DOMError)?; + character_counter.forget(); - document().get_element_by_id("publish")?.add_event_listener( - mv!(title, subtitle, content, old_ed => move |_: ClickEvent| { - let popup = document().get_element_by_id("publish-popup").or_else(|| - init_popup(&title, &subtitle, &content, &old_ed).ok() - ).unwrap(); - let bg = document().get_element_by_id("popup-bg").or_else(|| - init_popup_bg().ok() - ).unwrap(); + let show_popup = Closure::wrap(Box::new(mv!(title, subtitle, content, old_ed => move |_| { + let popup = document().get_element_by_id("publish-popup").or_else(|| + init_popup(&title, &subtitle, &content, &old_ed).ok() + ).unwrap(); + let bg = document().get_element_by_id("popup-bg").or_else(|| + init_popup_bg().ok() + ).unwrap(); - popup.class_list().add("show").unwrap(); - bg.class_list().add("show").unwrap(); - }), - ); + popup.class_list().add_1("show").unwrap(); + bg.class_list().add_1("show").unwrap(); + })) as Box); + document() + .get_element_by_id("publish")? + .add_event_listener_with_callback("click", show_popup.as_ref().unchecked_ref()) + .map_err(|_| EditorError::DOMError)?; + show_popup.forget(); show_errors(); setup_close_button(); @@ -364,32 +447,47 @@ fn init_editor() -> Result<(), EditorError> { fn setup_close_button() { if let Some(button) = document().get_element_by_id("close-editor") { - button.add_event_listener(|_: ClickEvent| { + let close_editor = Closure::wrap(Box::new(|_| { window() + .unwrap() .local_storage() - .insert("basic-editor", "true") + .unwrap() + .unwrap() + .set("basic-editor", "true") .unwrap(); - window().history().go(0).unwrap(); // Refresh the page - }); + window() + .unwrap() + .history() + .unwrap() + .go_with_delta(0) + .unwrap(); // Refresh the page + }) as Box); + button + .add_event_listener_with_callback("click", close_editor.as_ref().unchecked_ref()) + .unwrap(); + close_editor.forget(); } } fn show_errors() { - if let Ok(Some(header)) = document().query_selector("header") { - let list = document().create_element("header").unwrap(); - list.class_list().add("messages").unwrap(); - for error in document().query_selector_all("p.error").unwrap() { + let document = document(); + if let Ok(Some(header)) = document.query_selector("header") { + let list = document.create_element("header").unwrap(); + list.class_list().add_1("messages").unwrap(); + let errors = document.query_selector_all("p.error").unwrap(); + for i in 0..errors.length() { + let error = errors.get(i).unwrap(); error .parent_element() .unwrap() .remove_child(&error) .unwrap(); - list.append_child(&error); + let _ = list.append_child(&error); } header .parent_element() .unwrap() - .insert_before(&list, &header.next_sibling().unwrap()) + .insert_before(&list, header.next_sibling().as_ref()) .unwrap(); } } @@ -400,9 +498,17 @@ fn init_popup( content: &HtmlElement, old_ed: &Element, ) -> Result { - let popup = document().create_element("div")?; - popup.class_list().add("popup")?; - popup.set_attribute("id", "publish-popup")?; + let document = document(); + let popup = document + .create_element("div") + .map_err(|_| EditorError::DOMError)?; + popup + .class_list() + .add_1("popup") + .map_err(|_| EditorError::DOMError)?; + popup + .set_attribute("id", "publish-popup") + .map_err(|_| EditorError::DOMError)?; let tags = get_elt_value("tags") .split(',') @@ -410,112 +516,157 @@ fn init_popup( .map(str::to_string) .collect::>(); let license = get_elt_value("license"); - make_input(&i18n!(CATALOG, "Tags"), "popup-tags", &popup).set_raw_value(&tags.join(", ")); - make_input(&i18n!(CATALOG, "License"), "popup-license", &popup).set_raw_value(&license); + make_input(&i18n!(CATALOG, "Tags"), "popup-tags", &popup).set_value(&tags.join(", ")); + make_input(&i18n!(CATALOG, "License"), "popup-license", &popup).set_value(&license); - let cover_label = document().create_element("label")?; - cover_label.append_child(&document().create_text_node(&i18n!(CATALOG, "Cover"))); - cover_label.set_attribute("for", "cover")?; - let cover = document().get_element_by_id("cover")?; + let cover_label = document + .create_element("label") + .map_err(|_| EditorError::DOMError)?; + cover_label + .append_child(&document.create_text_node(&i18n!(CATALOG, "Cover"))) + .map_err(|_| EditorError::DOMError)?; + cover_label + .set_attribute("for", "cover") + .map_err(|_| EditorError::DOMError)?; + let cover = document.get_element_by_id("cover")?; cover.parent_element()?.remove_child(&cover).ok(); - popup.append_child(&cover_label); - popup.append_child(&cover); + popup + .append_child(&cover_label) + .map_err(|_| EditorError::DOMError)?; + popup + .append_child(&cover) + .map_err(|_| EditorError::DOMError)?; - if let Some(draft_checkbox) = document().get_element_by_id("draft") { - let draft_label = document().create_element("label")?; - draft_label.set_attribute("for", "popup-draft")?; + if let Some(draft_checkbox) = document.get_element_by_id("draft") { + let draft_checkbox = draft_checkbox.dyn_ref::().unwrap(); + let draft_label = document + .create_element("label") + .map_err(|_| EditorError::DOMError)?; + draft_label + .set_attribute("for", "popup-draft") + .map_err(|_| EditorError::DOMError)?; - let draft = document().create_element("input").unwrap(); - js! { - @{&draft}.id = "popup-draft"; - @{&draft}.name = "popup-draft"; - @{&draft}.type = "checkbox"; - @{&draft}.checked = @{&draft_checkbox}.checked; - }; + let draft = document.create_element("input").unwrap(); + draft.set_id("popup-draft"); + let draft = draft.dyn_ref::().unwrap(); + draft.set_name("popup-draft"); + draft.set_type("checkbox"); + draft.set_checked(draft_checkbox.checked()); - draft_label.append_child(&draft); - draft_label.append_child(&document().create_text_node(&i18n!(CATALOG, "This is a draft"))); - popup.append_child(&draft_label); + draft_label + .append_child(&draft) + .map_err(|_| EditorError::DOMError)?; + draft_label + .append_child(&document.create_text_node(&i18n!(CATALOG, "This is a draft"))) + .map_err(|_| EditorError::DOMError)?; + popup + .append_child(&draft_label) + .map_err(|_| EditorError::DOMError)?; } - let button = document().create_element("input")?; - js! { - @{&button}.type = "submit"; - @{&button}.value = @{i18n!(CATALOG, "Publish")}; - }; - button.append_child(&document().create_text_node(&i18n!(CATALOG, "Publish"))); - button.add_event_listener( - mv!(title, subtitle, content, old_ed => move |_: ClickEvent| { - title.focus(); // Remove the placeholder before publishing - set_value("title", title.inner_text()); - subtitle.focus(); - set_value("subtitle", subtitle.inner_text()); - content.focus(); - set_value("editor-content", content.child_nodes().iter().fold(String::new(), |md, ch| { - let to_append = match ch.node_type() { - NodeType::Element => { - if js!{ return @{&ch}.tagName; } == "DIV" { - (js!{ return @{&ch}.innerHTML; }).try_into().unwrap_or_default() - } else { - (js!{ return @{&ch}.outerHTML; }).try_into().unwrap_or_default() - } - }, - NodeType::Text => ch.node_value().unwrap_or_default(), - _ => unreachable!(), - }; - format!("{}\n\n{}", md, to_append) - })); - set_value("tags", get_elt_value("popup-tags")); - if let Some(draft) = document().get_element_by_id("popup-draft") { - js!{ - document.getElementById("draft").checked = @{draft}.checked; - }; - } - let cover = document().get_element_by_id("cover").unwrap(); - cover.parent_element().unwrap().remove_child(&cover).ok(); - old_ed.append_child(&cover); - set_value("license", get_elt_value("popup-license")); - clear_autosave(); - js! { - @{&old_ed}.submit(); + let button = document + .create_element("input") + .map_err(|_| EditorError::DOMError)?; + button + .append_child(&document.create_text_node(&i18n!(CATALOG, "Publish"))) + .map_err(|_| EditorError::DOMError)?; + let button = button.dyn_ref::().unwrap(); + button.set_type("submit"); + button.set_value(&i18n!(CATALOG, "Publish")); + let callback = Closure::wrap(Box::new(mv!(title, subtitle, content, old_ed => move |_| { + let document = self::document(); + title.focus().unwrap(); // Remove the placeholder before publishing + set_value("title", title.inner_text()); + subtitle.focus().unwrap(); + set_value("subtitle", subtitle.inner_text()); + content.focus().unwrap(); + let mut md = String::new(); + let child_nodes = content.child_nodes(); + for i in 0..child_nodes.length() { + let ch = child_nodes.get(i).unwrap(); + let to_append = match ch.node_type() { + Node::ELEMENT_NODE => { + let ch = ch.dyn_ref::().unwrap(); + if ch.tag_name() == "DIV" { + ch.inner_html() + } else { + ch.outer_html() + } + }, + Node::TEXT_NODE => ch.node_value().unwrap_or_default(), + _ => unreachable!(), }; - }), - ); - popup.append_child(&button); + md = format!("{}\n\n{}", md, to_append); + } + set_value("editor-content", md); + set_value("tags", get_elt_value("popup-tags")); + if let Some(draft) = document.get_element_by_id("popup-draft") { + if let Some(draft_checkbox) = document.get_element_by_id("draft") { + let draft_checkbox = draft_checkbox.dyn_ref::().unwrap(); + let draft = draft.dyn_ref::().unwrap(); + draft_checkbox.set_checked(draft.checked()); + } + } + let cover = document.get_element_by_id("cover").unwrap(); + cover.parent_element().unwrap().remove_child(&cover).ok(); + old_ed.append_child(&cover).unwrap(); + set_value("license", get_elt_value("popup-license")); + clear_autosave(); + let old_ed = old_ed.dyn_ref::().unwrap(); + old_ed.submit().unwrap(); + })) as Box); + button + .add_event_listener_with_callback("click", callback.as_ref().unchecked_ref()) + .map_err(|_| EditorError::DOMError)?; + callback.forget(); + popup + .append_child(&button) + .map_err(|_| EditorError::DOMError)?; - document().body()?.append_child(&popup); + document + .body()? + .append_child(&popup) + .map_err(|_| EditorError::DOMError)?; Ok(popup) } fn init_popup_bg() -> Result { - let bg = document().create_element("div")?; - bg.class_list().add("popup-bg")?; - bg.set_attribute("id", "popup-bg")?; + let bg = document() + .create_element("div") + .map_err(|_| EditorError::DOMError)?; + bg.class_list() + .add_1("popup-bg") + .map_err(|_| EditorError::DOMError)?; + bg.set_attribute("id", "popup-bg") + .map_err(|_| EditorError::DOMError)?; - document().body()?.append_child(&bg); - bg.add_event_listener(|_: ClickEvent| close_popup()); + document() + .body()? + .append_child(&bg) + .map_err(|_| EditorError::DOMError)?; + let callback = Closure::wrap(Box::new(|_| close_popup()) as Box); + bg.add_event_listener_with_callback("click", callback.as_ref().unchecked_ref()) + .unwrap(); + callback.forget(); Ok(bg) } fn chars_left(selector: &str, content: &HtmlElement) -> Option { match document().query_selector(selector) { - Ok(Some(form)) => HtmlElement::try_from(form).ok().and_then(|form| { + Ok(Some(form)) => form.dyn_ref::().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() + (encode_uri_component(&content.inner_html()) + .replace("%20", "+") + .replace("%0A", "%0D0A") + .replace_by_pattern(&RegExp::new("[!'*()]", "g"), "XXX") + .length() + + 2_u32) + .try_into() + .map(|c: i32| len - c) + .ok() } else { None } @@ -525,26 +676,26 @@ fn chars_left(selector: &str, content: &HtmlElement) -> Option { } fn close_popup() { - let hide = |x: Element| x.class_list().remove("show"); + let hide = |x: Element| x.class_list().remove_1("show"); document().get_element_by_id("publish-popup").map(hide); document().get_element_by_id("popup-bg").map(hide); } -fn make_input(label_text: &str, name: &'static str, form: &Element) -> InputElement { - let label = document().create_element("label").unwrap(); - label.append_child(&document().create_text_node(label_text)); +fn make_input(label_text: &str, name: &'static str, form: &Element) -> HtmlInputElement { + let document = document(); + let label = document.create_element("label").unwrap(); + label + .append_child(&document.create_text_node(label_text)) + .unwrap(); label.set_attribute("for", name).unwrap(); - let inp: InputElement = document() - .create_element("input") - .unwrap() - .try_into() - .unwrap(); + let inp = document.create_element("input").unwrap(); + let inp = inp.dyn_into::().unwrap(); inp.set_attribute("name", name).unwrap(); inp.set_attribute("id", name).unwrap(); - form.append_child(&label); - form.append_child(&inp); + form.append_child(&label).unwrap(); + form.append_child(&inp).unwrap(); inp } @@ -558,36 +709,46 @@ fn make_editable(tag: &'static str) -> Element { } fn placeholder(elt: HtmlElement, text: &str) -> HtmlElement { - elt.dataset().insert("placeholder", text).unwrap(); - elt.dataset().insert("edited", "false").unwrap(); + elt.dataset().set("placeholder", text).unwrap(); + elt.dataset().set("edited", "false").unwrap(); - elt.add_event_listener(mv!(elt => move |_: FocusEvent| { + let callback = Closure::wrap(Box::new(mv!(elt => move |_: FocusEvent| { if elt.dataset().get("edited").unwrap().as_str() != "true" { clear_children(&elt); } - })); - elt.add_event_listener(mv!(elt => move |_: BlurEvent| { + })) as Box); + elt.add_event_listener_with_callback("focus", callback.as_ref().unchecked_ref()) + .unwrap(); + callback.forget(); + let callback = Closure::wrap(Box::new(mv!(elt => move |_: Event| { if elt.dataset().get("edited").unwrap().as_str() != "true" { clear_children(&elt); let ph = document().create_element("span").expect("Couldn't create placeholder"); - ph.class_list().add("placeholder").expect("Couldn't add class"); - ph.append_child(&document().create_text_node(&elt.dataset().get("placeholder").unwrap_or_default())); - elt.append_child(&ph); + ph.class_list().add_1("placeholder").expect("Couldn't add class"); + ph.append_child(&document().create_text_node(&elt.dataset().get("placeholder").unwrap_or_default())).unwrap(); + elt.append_child(&ph).unwrap(); } - })); - elt.add_event_listener(mv!(elt => move |_: KeyUpEvent| { - elt.dataset().insert("edited", if elt.inner_text().trim_matches('\n').is_empty() { + })) as Box); + elt.add_event_listener_with_callback("blur", callback.as_ref().unchecked_ref()) + .unwrap(); + callback.forget(); + let callback = Closure::wrap(Box::new(mv!(elt => move |_: KeyboardEvent| { + elt.dataset().set("edited", if elt.inner_text().trim_matches('\n').is_empty() { "false" } else { "true" }).expect("Couldn't update edition state"); - })); + })) as Box); + elt.add_event_listener_with_callback("keyup", callback.as_ref().unchecked_ref()) + .unwrap(); + callback.forget(); elt } fn clear_children(elt: &HtmlElement) { - for child in elt.child_nodes() { - elt.remove_child(&child).unwrap(); + let child_nodes = elt.child_nodes(); + for _ in 0..child_nodes.length() { + elt.remove_child(&child_nodes.get(0).unwrap()).unwrap(); } } diff --git a/plume-front/src/lib.rs b/plume-front/src/lib.rs index 60cd4b8f..94bf5d26 100755 --- a/plume-front/src/lib.rs +++ b/plume-front/src/lib.rs @@ -7,7 +7,7 @@ extern crate gettext_macros; extern crate lazy_static; use wasm_bindgen::{prelude::*, JsCast}; -use web_sys::{window, Element, Event, HtmlInputElement, TouchEvent}; +use web_sys::{console, window, Element, Event, HtmlInputElement, TouchEvent}; init_i18n!( "plume-front", @@ -53,7 +53,7 @@ init_i18n!( zh ); -// mod editor; +mod editor; compile_i18n!(); @@ -78,8 +78,15 @@ lazy_static! { #[wasm_bindgen(start)] pub fn main() -> Result<(), JsValue> { + extern crate console_error_panic_hook; + use std::panic; + panic::set_hook(Box::new(console_error_panic_hook::hook)); + menu(); search(); + editor::init() + .map_err(|e| console::error_1(&&format!("Editor error: {:?}", e).into())) + .ok(); Ok(()) }