From 6387a1334bc3c5c0ae37e48b7f4422bc9b079d52 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 10 Feb 2021 21:51:57 +0900 Subject: [PATCH 01/33] Add wasm-bindgen to plume-front's dependencies --- plume-front/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/plume-front/Cargo.toml b/plume-front/Cargo.toml index d84b4320..1fb00240 100644 --- a/plume-front/Cargo.toml +++ b/plume-front/Cargo.toml @@ -13,3 +13,4 @@ gettext-utils = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a lazy_static = "1.3" serde = "1.0" serde_json = "1.0" +wasm-bindgen = "0.2.70" From faab5f6b3b581de994b6d01e5b182d5c2ed26d5d Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 10 Feb 2021 21:53:59 +0900 Subject: [PATCH 02/33] Make plume-front's type cdylib --- plume-front/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plume-front/Cargo.toml b/plume-front/Cargo.toml index 1fb00240..2ebe9258 100644 --- a/plume-front/Cargo.toml +++ b/plume-front/Cargo.toml @@ -4,6 +4,9 @@ version = "0.6.1-dev" authors = ["Plume contributors"] edition = "2018" +[lib] +crate-type = ["cdylib"] + [dependencies] stdweb = "=0.4.18" stdweb-internal-runtime = "=0.1.4" From 0b8b1bf25fd61b1c48c4cbb48778e185f2af1c99 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 10 Feb 2021 23:50:41 +0900 Subject: [PATCH 03/33] Load new JavaScript file --- templates/base.rs.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/templates/base.rs.html b/templates/base.rs.html index 7dfc783a..801cd670 100644 --- a/templates/base.rs.html +++ b/templates/base.rs.html @@ -96,6 +96,9 @@ @i18n!(ctx.1, "Matrix room") - + From db581a955bbb37aabaaee9c7a828931e214354f1 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 10 Feb 2021 23:51:09 +0900 Subject: [PATCH 04/33] Reimplement menu() in web-sys --- Cargo.lock | 30 +++++----- plume-front/Cargo.toml | 12 ++++ plume-front/src/lib.rs | 121 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 14 deletions(-) create mode 100755 plume-front/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 6a22928a..63e2c7dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1929,9 +1929,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "js-sys" -version = "0.3.46" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175" +checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65" dependencies = [ "wasm-bindgen", ] @@ -3013,6 +3013,8 @@ dependencies = [ "serde_json", "stdweb", "stdweb-internal-runtime", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -5181,9 +5183,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" +checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" dependencies = [ "cfg-if 1.0.0", "serde 1.0.118", @@ -5193,9 +5195,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62" +checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7" dependencies = [ "bumpalo", "lazy_static", @@ -5220,9 +5222,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" +checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c" dependencies = [ "quote 1.0.8", "wasm-bindgen-macro-support", @@ -5230,9 +5232,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" +checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", @@ -5243,15 +5245,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" +checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64" [[package]] name = "web-sys" -version = "0.3.46" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3" +checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/plume-front/Cargo.toml b/plume-front/Cargo.toml index 2ebe9258..23c0f0ff 100644 --- a/plume-front/Cargo.toml +++ b/plume-front/Cargo.toml @@ -17,3 +17,15 @@ lazy_static = "1.3" serde = "1.0" serde_json = "1.0" wasm-bindgen = "0.2.70" + +[dependencies.web-sys] +version = "0.3.47" +features = [ + 'Document', + 'DomTokenList', + 'Element', + 'EventTarget', + 'Navigator', + 'TouchEvent', + 'Window' +] diff --git a/plume-front/src/lib.rs b/plume-front/src/lib.rs new file mode 100755 index 00000000..110c04ff --- /dev/null +++ b/plume-front/src/lib.rs @@ -0,0 +1,121 @@ +#![recursion_limit = "128"] +#![feature(decl_macro, proc_macro_hygiene, try_trait)] + +#[macro_use] +extern crate gettext_macros; +#[macro_use] +extern crate lazy_static; + +use wasm_bindgen::{prelude::*, JsCast}; +use web_sys::window; + +init_i18n!( + "plume-front", + af, + ar, + bg, + ca, + cs, + cy, + da, + de, + el, + en, + eo, + es, + fa, + fi, + fr, + gl, + he, + hi, + hr, + hu, + it, + ja, + ko, + nb, + nl, + no, + pl, + pt, + ro, + ru, + sat, + si, + sk, + sl, + sr, + sv, + tr, + uk, + vi, + zh +); + +compile_i18n!(); + +lazy_static! { + static ref CATALOG: gettext::Catalog = { + let catalogs = include_i18n!(); + let lang = window().unwrap().navigator().language().unwrap(); + let lang = lang.splitn(2, '-').next().unwrap_or("en"); + + let english_position = catalogs + .iter() + .position(|(language_code, _)| *language_code == "en") + .unwrap(); + catalogs + .iter() + .find(|(l, _)| l == &lang) + .unwrap_or(&catalogs[english_position]) + .clone() + .1 + }; +} + +#[wasm_bindgen(start)] +pub fn main() -> Result<(), JsValue> { + menu(); + Ok(()) +} + +/// Toggle menu on mobile devices +/// +/// It should normally be working fine even without this code +/// But :focus-within is not yet supported by Webkit/Blink +fn menu() { + let document = window().unwrap().document().unwrap(); + if let Some(button) = document.get_element_by_id("menu") { + if let Some(menu) = document.get_element_by_id("content") { + let show_menu = Closure::wrap(Box::new(|_: web_sys::TouchEvent| { + window() + .unwrap() + .document() + .unwrap() + .get_element_by_id("menu") + .map(|menu| menu.class_list().add_1("show")) + .unwrap() + .unwrap(); + }) as Box); + button + .add_event_listener_with_callback("touchend", show_menu.as_ref().unchecked_ref()) + .unwrap(); + show_menu.forget(); + + let close_menu = Closure::wrap(Box::new(|_: web_sys::TouchEvent| { + window() + .unwrap() + .document() + .unwrap() + .get_element_by_id("menu") + .map(|menu| menu.class_list().remove_1("show")) + .unwrap() + .unwrap() + }) as Box); + menu.add_event_listener_with_callback("touchend", close_menu.as_ref().unchecked_ref()) + .unwrap(); + close_menu.forget(); + } + } +} From d86bd898cb6b64d2f29a44939f63768b0603f141 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 11 Feb 2021 01:19:32 +0900 Subject: [PATCH 05/33] Prepare JavaScript on build process --- build.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/build.rs b/build.rs index e56fc559..d9ba2568 100644 --- a/build.rs +++ b/build.rs @@ -48,19 +48,13 @@ fn main() { create_dir_all(&Path::new("static").join("media")).expect("Couldn't init media directory"); let cache_id = &compute_static_hash()[..8]; - println!("cargo:rerun-if-changed=target/deploy/plume-front.wasm"); - copy("target/deploy/plume-front.wasm", "static/plume-front.wasm") - .and_then(|_| read_to_string("target/deploy/plume-front.js")) - .and_then(|js| { - write( - "static/plume-front.js", - js.replace( - "\"plume-front.wasm\"", - &format!("\"/static/cached/{}/plume-front.wasm\"", cache_id), - ), - ) - }) - .ok(); + println!("cargo:rerun-if-changed=plume-front/pkg/plume_front_bg.wasm"); + copy( + "plume-front/pkg/plume_front_bg.wasm", + "static/plume_front_bg.wasm", + ) + .and_then(|_| copy("plume-front/pkg/plume_front.js", "static/plume_front.js")) + .ok(); println!("cargo:rustc-env=CACHE_ID={}", cache_id) } From d0ada7fc532a8fcff1f1690b685d7abb8de96d05 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 11 Feb 2021 01:19:42 +0900 Subject: [PATCH 06/33] Reimplement search() in web-sys --- plume-front/Cargo.toml | 4 ++++ plume-front/src/lib.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/plume-front/Cargo.toml b/plume-front/Cargo.toml index 23c0f0ff..9e913892 100644 --- a/plume-front/Cargo.toml +++ b/plume-front/Cargo.toml @@ -25,7 +25,11 @@ features = [ 'DomTokenList', 'Element', 'EventTarget', + 'HtmlFormElement', + 'HtmlInputElement', 'Navigator', + 'Node', + 'NodeList', 'TouchEvent', 'Window' ] diff --git a/plume-front/src/lib.rs b/plume-front/src/lib.rs index 110c04ff..33824ba6 100755 --- a/plume-front/src/lib.rs +++ b/plume-front/src/lib.rs @@ -77,6 +77,7 @@ lazy_static! { #[wasm_bindgen(start)] pub fn main() -> Result<(), JsValue> { menu(); + search(); Ok(()) } @@ -119,3 +120,37 @@ fn menu() { } } } + +/// Clear the URL of the search page before submitting request +fn search() { + if let Some(form) = window() + .unwrap() + .document() + .unwrap() + .get_element_by_id("form") + { + let normalize_query = Closure::wrap(Box::new(|_: web_sys::Event| { + window() + .unwrap() + .document() + .unwrap() + .query_selector_all("#form input") + .map(|inputs| { + for i in 0..inputs.length() { + let input = inputs.get(i).unwrap(); + let input = input.dyn_ref::().unwrap(); + if input.name().is_empty() { + input.set_name(&input.dyn_ref::().unwrap().id()); + } + if !input.name().is_empty() && input.value().is_empty() { + input.set_name(""); + } + } + }) + .unwrap(); + }) as Box); + form.add_event_listener_with_callback("submit", normalize_query.as_ref().unchecked_ref()) + .unwrap(); + normalize_query.forget(); + } +} From 208c515d705366f809c5ce9d4619cb719024750b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 11 Feb 2021 01:24:41 +0900 Subject: [PATCH 07/33] Remove prefix 'web_sys' --- plume-front/src/lib.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/plume-front/src/lib.rs b/plume-front/src/lib.rs index 33824ba6..60cd4b8f 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; +use web_sys::{window, Element, Event, HtmlInputElement, TouchEvent}; init_i18n!( "plume-front", @@ -53,6 +53,8 @@ init_i18n!( zh ); +// mod editor; + compile_i18n!(); lazy_static! { @@ -89,7 +91,7 @@ fn menu() { let document = window().unwrap().document().unwrap(); if let Some(button) = document.get_element_by_id("menu") { if let Some(menu) = document.get_element_by_id("content") { - let show_menu = Closure::wrap(Box::new(|_: web_sys::TouchEvent| { + let show_menu = Closure::wrap(Box::new(|_: TouchEvent| { window() .unwrap() .document() @@ -98,13 +100,13 @@ fn menu() { .map(|menu| menu.class_list().add_1("show")) .unwrap() .unwrap(); - }) as Box); + }) as Box); button .add_event_listener_with_callback("touchend", show_menu.as_ref().unchecked_ref()) .unwrap(); show_menu.forget(); - let close_menu = Closure::wrap(Box::new(|_: web_sys::TouchEvent| { + let close_menu = Closure::wrap(Box::new(|_: TouchEvent| { window() .unwrap() .document() @@ -113,7 +115,7 @@ fn menu() { .map(|menu| menu.class_list().remove_1("show")) .unwrap() .unwrap() - }) as Box); + }) as Box); menu.add_event_listener_with_callback("touchend", close_menu.as_ref().unchecked_ref()) .unwrap(); close_menu.forget(); @@ -129,7 +131,7 @@ fn search() { .unwrap() .get_element_by_id("form") { - let normalize_query = Closure::wrap(Box::new(|_: web_sys::Event| { + let normalize_query = Closure::wrap(Box::new(|_: Event| { window() .unwrap() .document() @@ -138,9 +140,9 @@ fn search() { .map(|inputs| { for i in 0..inputs.length() { let input = inputs.get(i).unwrap(); - let input = input.dyn_ref::().unwrap(); + let input = input.dyn_ref::().unwrap(); if input.name().is_empty() { - input.set_name(&input.dyn_ref::().unwrap().id()); + input.set_name(&input.dyn_ref::().unwrap().id()); } if !input.name().is_empty() && input.value().is_empty() { input.set_name(""); @@ -148,7 +150,7 @@ fn search() { } }) .unwrap(); - }) as Box); + }) as Box); form.add_event_listener_with_callback("submit", normalize_query.as_ref().unchecked_ref()) .unwrap(); normalize_query.forget(); From 24ecb1511919e033052bf97f002202be15cd9c8a Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 03:19:46 +0900 Subject: [PATCH 08/33] 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(()) } From 0bcf063b053b979dc86ff7bfaebbf20f24f7d10b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 03:20:33 +0900 Subject: [PATCH 09/33] Remove old plume_front::main --- plume-front/src/main.rs | 129 ---------------------------------------- 1 file changed, 129 deletions(-) delete mode 100755 plume-front/src/main.rs diff --git a/plume-front/src/main.rs b/plume-front/src/main.rs deleted file mode 100755 index 4b73b3eb..00000000 --- a/plume-front/src/main.rs +++ /dev/null @@ -1,129 +0,0 @@ -#![recursion_limit = "128"] -#![feature(decl_macro, proc_macro_hygiene, try_trait)] - -#[macro_use] -extern crate gettext_macros; -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate stdweb; -use stdweb::web::{event::*, *}; - -init_i18n!( - "plume-front", - af, - ar, - bg, - ca, - cs, - cy, - da, - de, - el, - en, - eo, - es, - fa, - fi, - fr, - gl, - he, - hi, - hr, - hu, - it, - ja, - ko, - nb, - nl, - no, - pl, - pt, - ro, - ru, - sat, - si, - sk, - sl, - sr, - sv, - tr, - uk, - vi, - zh -); - -mod editor; - -compile_i18n!(); - -lazy_static! { - static ref CATALOG: gettext::Catalog = { - let catalogs = include_i18n!(); - let lang = js! { return navigator.language }.into_string().unwrap(); - let lang = lang.splitn(2, '-').next().unwrap_or("en"); - - let english_position = catalogs - .iter() - .position(|(language_code, _)| *language_code == "en") - .unwrap(); - catalogs - .iter() - .find(|(l, _)| l == &lang) - .unwrap_or(&catalogs[english_position]) - .clone() - .1 - }; -} - -fn main() { - menu(); - search(); - editor::init() - .map_err(|e| console!(error, format!("Editor error: {:?}", e))) - .ok(); -} - -/// Toggle menu on mobile devices -/// -/// It should normally be working fine even without this code -/// But :focus-within is not yet supported by Webkit/Blink -fn menu() { - if let Some(button) = document().get_element_by_id("menu") { - if let Some(menu) = document().get_element_by_id("content") { - button.add_event_listener(|_: TouchEnd| { - document() - .get_element_by_id("menu") - .map(|menu| menu.class_list().add("show")); - }); - menu.add_event_listener(|_: TouchEnd| { - document() - .get_element_by_id("menu") - .map(|menu| menu.class_list().remove("show")); - }); - } - } -} - -/// Clear the URL of the search page before submitting request -fn search() { - if let Some(form) = document().get_element_by_id("form") { - form.add_event_listener(|_: SubmitEvent| { - document() - .query_selector_all("#form input") - .map(|inputs| { - for input in inputs { - js! { - if (@{&input}.name === "") { - @{&input}.name = @{&input}.id - } - if (@{&input}.name && !@{&input}.value) { - @{&input}.name = ""; - } - } - } - }) - .ok(); - }); - } -} From affe24b9c669c12a247dd5f0f34b46fac42b9573 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 03:37:37 +0900 Subject: [PATCH 10/33] Replace cargo-web with wasm-pack on build environment --- .circleci/config.yml | 4 ++-- .circleci/images/plume-buildenv/Dockerfile | 2 +- Dockerfile | 2 +- Dockerfile.dev | 2 +- script/plume-front.sh | 4 ++-- script/prebuild.sh | 2 +- snap/snapcraft.yaml | 6 +++--- 7 files changed, 11 insertions(+), 11 deletions(-) mode change 100644 => 100755 script/plume-front.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 87d2e802..77db838b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -168,7 +168,7 @@ jobs: steps: - restore_env: cache: <<#parameters.postgres>>postgres<><<^parameters.postgres>>sqlite<> - - run: cargo web deploy -p plume-front + - run: RUSTFLAGS="--cfg=web_sys_unstable_apis" wasm-pack build --target web --release plume-front - run_with_coverage: cmd: | cmd="cargo install --debug --no-default-features --features="${FEATURES}",test --force --path . -j" @@ -203,7 +203,7 @@ jobs: steps: - restore_env: cache: release-<<#parameters.postgres>>postgres<><<^parameters.postgres>>sqlite<> - - run: cargo web deploy -p plume-front --release + - run: RUSTFLAGS="--cfg=web_sys_unstable_apis" wasm-pack build --target web --release plume-front - build: package: plume release: true diff --git a/.circleci/images/plume-buildenv/Dockerfile b/.circleci/images/plume-buildenv/Dockerfile index a16a081d..98a3cc65 100644 --- a/.circleci/images/plume-buildenv/Dockerfile +++ b/.circleci/images/plume-buildenv/Dockerfile @@ -16,7 +16,7 @@ RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2021-0 rustup component add rust-std --target wasm32-unknown-unknown #compile some deps -RUN cargo install cargo-web &&\ +RUN cargo install wasm-pack &&\ cargo install grcov &&\ strip /root/.cargo/bin/* &&\ rm -fr ~/.cargo/registry diff --git a/Dockerfile b/Dockerfile index e0bc20df..f6196e73 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN chmod a+x ./wasm-deps.sh && sleep 1 && ./wasm-deps.sh WORKDIR /app COPY Cargo.toml Cargo.lock rust-toolchain ./ -RUN cargo install cargo-web +RUN cargo install wasm-pack COPY . . diff --git a/Dockerfile.dev b/Dockerfile.dev index 2b67c186..89b9fe16 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -20,7 +20,7 @@ RUN chmod a+x ./wasm-deps.sh && sleep 1 && ./wasm-deps.sh WORKDIR /app COPY Cargo.toml Cargo.lock rust-toolchain ./ RUN cargo install diesel_cli --no-default-features --features postgres --version '=1.3.0' -RUN cargo install cargo-web +RUN cargo install wasm-pack COPY . . diff --git a/script/plume-front.sh b/script/plume-front.sh old mode 100644 new mode 100755 index 8568e076..92714b5e --- a/script/plume-front.sh +++ b/script/plume-front.sh @@ -21,7 +21,7 @@ EOF if [ $ARCH == "aarch64" -o $ARCH == "armv71" ] ; then export PATH=/opt/local/llvm/bin:${PATH} cd /app - RUSTFLAGS="-C linker=lld" cargo web deploy -p plume-front + RUSTFLAGS="-C linker=lld --cfg=web_sys_unstable_apis" wasm-pack build --target web --release plume-front else - cargo web deploy -p plume-front + RUSTFLAGS="--cfg=web_sys_unstable_apis" wasm-pack build --target web --release plume-front fi diff --git a/script/prebuild.sh b/script/prebuild.sh index 4b020d82..d1e56411 100755 --- a/script/prebuild.sh +++ b/script/prebuild.sh @@ -9,7 +9,7 @@ pkg="$4" build () { features="$1" cargo clean - cargo web deploy -p plume-front --release + RUSTFLAGS="--cfg=web_sys_unstable_apis" wasm-pack build --target web --release plume-front cargo build --release --no-default-features --features="${features}" --package=plume-cli cargo build --release --no-default-features --features="${features}" ./script/generate_artifact.sh diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b36a97d2..53262116 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -38,17 +38,17 @@ parts: snapcraftctl set-version $(git describe --tags) export PATH=$PATH:$HOME/.cargo/bin rustup install stable - cargo +stable install --force cargo-web + cargo +stable install --force wasm-pack # Only Tier 1 Rust platforms get rust-lld # On the others (arm64, armhf, powerpc64, s390x) fall back to using # the system LLD we've installed earlier. case ${SNAPCRAFT_ARCH_TRIPLET} in \ aarch64-linux-gnu|arm-linux-gnueabihf|powerpc64-linux-gnu|s390x-linux-gnu) \ - RUSTFLAGS="-C linker=lld-8" cargo web deploy -p plume-front --release \ + RUSTFLAGS="-C linker=lld --cfg=web_sys_unstable_apis" wasm-pack build --target web --release plume-front \ ;; \ *) \ - cargo web deploy -p plume-front --release \ + RUSTFLAGS="--cfg=web_sys_unstable_apis" wasm-pack build --target web --release plume-front \ ;; \ esac From 58bb07d11d23d0dfed53d9672ccd8f7d14367358 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 04:54:19 +0900 Subject: [PATCH 11/33] Remove unused stdweb crate from dependencies --- Cargo.lock | 71 ------------------------------------------ plume-front/Cargo.toml | 2 -- 2 files changed, 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 755e987e..1b052bc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,12 +296,6 @@ dependencies = [ "libc", ] -[[package]] -name = "base-x" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" - [[package]] name = "base64" version = "0.9.3" @@ -998,12 +992,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - [[package]] name = "dotenv" version = "0.14.1" @@ -3024,8 +3012,6 @@ dependencies = [ "serde 1.0.123", "serde_derive", "serde_json", - "stdweb", - "stdweb-internal-runtime", "wasm-bindgen", "web-sys", ] @@ -3970,12 +3956,6 @@ dependencies = [ "serde 1.0.123", ] -[[package]] -name = "sha1" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" - [[package]] name = "sha2" version = "0.8.2" @@ -4134,57 +4114,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stdweb" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68c0ce28cf7400ed022e18da3c4591e14e1df02c70e93573cc59921b3923aeb" -dependencies = [ - "discard", - "rustc_version", - "serde 1.0.123", - "serde_json", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e21ebd9179de08f2300a65454268a17ea3de204627458588c84319c4def3930" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "serde 1.0.123", - "serde_derive", - "syn 0.15.44", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e68f7d08b76979a43e93fe043b66d2626e35d41d68b0b85519202c6dd8ac59fa" -dependencies = [ - "base-x", - "proc-macro2 0.4.30", - "quote 0.6.13", - "serde 1.0.123", - "serde_derive", - "serde_json", - "sha1", - "syn 0.15.44", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52317523542cc0af5b7e31017ad0f7d1e78da50455e38d5657cd17754f617da" - [[package]] name = "string" version = "0.2.1" diff --git a/plume-front/Cargo.toml b/plume-front/Cargo.toml index 9578346f..8e085ef3 100644 --- a/plume-front/Cargo.toml +++ b/plume-front/Cargo.toml @@ -8,8 +8,6 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -stdweb = "=0.4.18" -stdweb-internal-runtime = "=0.1.4" gettext = { git = "https://github.com/Plume-org/gettext/", rev = "294c54d74c699fbc66502b480a37cc66c1daa7f3" } gettext-macros = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" } gettext-utils = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" } From 1bc2749e1b412ba6f363d8c81f7d1775abb5f089 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 04:54:36 +0900 Subject: [PATCH 12/33] Return when required element is not present --- plume-front/src/editor.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plume-front/src/editor.rs b/plume-front/src/editor.rs index 6df999e5..06e45cc4 100644 --- a/plume-front/src/editor.rs +++ b/plume-front/src/editor.rs @@ -365,7 +365,11 @@ fn init_editor() -> Result<(), EditorError> { .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_ed = document().get_element_by_id("plume-fallback-editor"); + if old_ed.is_none() { + return Ok(()); + } + let old_ed = old_ed.unwrap(); let old_title = document().get_element_by_id("plume-editor-title")?; old_ed .dyn_ref::() From e119c5bde9ed039bdb21904eac5b609da56b62a4 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 13:10:32 +0900 Subject: [PATCH 13/33] Update Docker image for testing --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 77db838b..24bd390f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ executors: type: boolean default: false docker: - - image: plumeorg/plume-buildenv:v0.3.0 + - image: plumeorg/plume-buildenv:v0.4.0 - image: <<#parameters.postgres>>circleci/postgres:9.6-alpine<><<^parameters.postgres>>alpine:latest<> environment: POSTGRES_USER: postgres From 6fcece3bc029f8e19d357ea30d9fb1378ce90a9f Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 13:31:22 +0900 Subject: [PATCH 14/33] Follow file name change --- script/generate_artifact.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/generate_artifact.sh b/script/generate_artifact.sh index c61087c7..d39a11d3 100755 --- a/script/generate_artifact.sh +++ b/script/generate_artifact.sh @@ -3,4 +3,4 @@ mkdir bin cp target/release/{plume,plm} bin strip -s bin/* tar -cvzf plume.tar.gz bin/ static/ -tar -cvzf wasm.tar.gz static/plume-front.{js,wasm} +tar -cvzf wasm.tar.gz static/plume_front.{js,wasm} From 347bc653d80806ce38809c3193529099c345336e Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 14:34:32 +0900 Subject: [PATCH 15/33] Install rustfmt and cargo-clippy explicitly in dev env --- Dockerfile.dev | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile.dev b/Dockerfile.dev index 89b9fe16..16e44a6e 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -19,6 +19,7 @@ RUN chmod a+x ./wasm-deps.sh && sleep 1 && ./wasm-deps.sh WORKDIR /app COPY Cargo.toml Cargo.lock rust-toolchain ./ +RUN rustup component add rustfmt clippy RUN cargo install diesel_cli --no-default-features --features postgres --version '=1.3.0' RUN cargo install wasm-pack From 056d68804af44a5d0a593018ef86490681dc0718 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 14:35:06 +0900 Subject: [PATCH 16/33] Remove unused diesel_cli from Docker env image --- Dockerfile.dev | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 16e44a6e..6ebd0b19 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -20,7 +20,6 @@ RUN chmod a+x ./wasm-deps.sh && sleep 1 && ./wasm-deps.sh WORKDIR /app COPY Cargo.toml Cargo.lock rust-toolchain ./ RUN rustup component add rustfmt clippy -RUN cargo install diesel_cli --no-default-features --features postgres --version '=1.3.0' RUN cargo install wasm-pack COPY . . From b55c2ff2f049616a93aeb85752422574880683e7 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 14:38:15 +0900 Subject: [PATCH 17/33] Install zip package in Docker dev image --- Dockerfile.dev | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 6ebd0b19..040d2346 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -11,7 +11,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ make \ openssl \ libssl-dev\ - clang + clang \ + zip WORKDIR /scratch COPY script/wasm-deps.sh . From 5d38c3ad70749e43b5dfe0b7c4575e1b762b6087 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 18:11:49 +0900 Subject: [PATCH 18/33] Fix file name --- .circleci/config.yml | 7 ++++--- script/generate_artifact.sh | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 24bd390f..4b2c40d4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,6 +22,7 @@ executors: FEATURES: <<#parameters.postgres>>postgres<><<^parameters.postgres>>sqlite<> DATABASE_URL: <<#parameters.postgres>>postgres://postgres@localhost/plume<><<^parameters.postgres>>plume.sqlite<> ROCKET_SECRET_KEY: VN5xV1DN7XdpATadOCYcuGeR/dV0hHfgx9mx9TarLdM= + RUSTFLAGS: --cfg=web_sys_unstable_apis commands: @@ -72,7 +73,7 @@ commands: type: string steps: - run: | - export RUSTFLAGS="-Zprofile -Zfewer-names -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Clink-arg=-Xlinker -Clink-arg=--no-keep-memory -Clink-arg=-Xlinker -Clink-arg=--reduce-memory-overheads" + export RUSTFLAGS="-Zprofile -Zfewer-names -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Clink-arg=-Xlinker -Clink-arg=--no-keep-memory -Clink-arg=-Xlinker -Clink-arg=--reduce-memory-overheads $RUSTFLAGS" export CARGO_INCREMENTAL=0 << parameters.cmd >> @@ -168,7 +169,7 @@ jobs: steps: - restore_env: cache: <<#parameters.postgres>>postgres<><<^parameters.postgres>>sqlite<> - - run: RUSTFLAGS="--cfg=web_sys_unstable_apis" wasm-pack build --target web --release plume-front + - run: wasm-pack build --target web --release plume-front - run_with_coverage: cmd: | cmd="cargo install --debug --no-default-features --features="${FEATURES}",test --force --path . -j" @@ -203,7 +204,7 @@ jobs: steps: - restore_env: cache: release-<<#parameters.postgres>>postgres<><<^parameters.postgres>>sqlite<> - - run: RUSTFLAGS="--cfg=web_sys_unstable_apis" wasm-pack build --target web --release plume-front + - run: wasm-pack build --target web --release plume-front - build: package: plume release: true diff --git a/script/generate_artifact.sh b/script/generate_artifact.sh index d39a11d3..84734981 100755 --- a/script/generate_artifact.sh +++ b/script/generate_artifact.sh @@ -3,4 +3,4 @@ mkdir bin cp target/release/{plume,plm} bin strip -s bin/* tar -cvzf plume.tar.gz bin/ static/ -tar -cvzf wasm.tar.gz static/plume_front.{js,wasm} +tar -cvzf wasm.tar.gz static/plume_front{.js,_bg.wasm} From de1c3f3245f70dbeaddf2d8da6cfd8dc87d1a87f Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 18:12:32 +0900 Subject: [PATCH 19/33] Move document() from plume_front::editor to plume_front --- plume-front/src/editor.rs | 12 ++++-------- plume-front/src/lib.rs | 6 +++++- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plume-front/src/editor.rs b/plume-front/src/editor.rs index 06e45cc4..155cf60b 100644 --- a/plume-front/src/editor.rs +++ b/plume-front/src/editor.rs @@ -1,12 +1,12 @@ -use crate::CATALOG; +use crate::{document, CATALOG}; 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, + console, window, ClipboardEvent, Element, Event, FocusEvent, HtmlAnchorElement, HtmlDocument, + HtmlElement, HtmlFormElement, HtmlInputElement, HtmlSelectElement, HtmlTextAreaElement, + KeyboardEvent, MouseEvent, Node, }; macro_rules! mv { @@ -18,10 +18,6 @@ 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: Option<&HtmlInputElement> = elt.dyn_ref(); diff --git a/plume-front/src/lib.rs b/plume-front/src/lib.rs index 94bf5d26..7b48806a 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::{console, window, Element, Event, HtmlInputElement, TouchEvent}; +use web_sys::{console, window, Document, Element, Event, HtmlInputElement, TouchEvent}; init_i18n!( "plume-front", @@ -163,3 +163,7 @@ fn search() { normalize_query.forget(); } } + +fn document() -> Document { + window().unwrap().document().unwrap() +} From 7562cf1e75e5e8f84730f9a0f2b0f70a37aa5965 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 18:18:45 +0900 Subject: [PATCH 20/33] Open menu even when focus-within code doesn't work --- assets/themes/default/_header.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/themes/default/_header.scss b/assets/themes/default/_header.scss index 8b3e9cf8..5705f26c 100644 --- a/assets/themes/default/_header.scss +++ b/assets/themes/default/_header.scss @@ -144,7 +144,7 @@ body > header { } } - body > header:focus-within #content, #content.show { + body > header:focus-within #content, .show + #content { position: fixed; display: flex; flex-direction: column; @@ -280,7 +280,7 @@ body > header { } } - body > header:focus-within #content, #content.show { + body > header:focus-within #content, .show + #content { position: fixed; display: flex; flex-direction: column; From 2c3d429b8e26c6424a927c2e3500589be313a93b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 18:20:48 +0900 Subject: [PATCH 21/33] Suppress Clippy --- plume-front/src/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plume-front/src/editor.rs b/plume-front/src/editor.rs index 155cf60b..8d0f2a81 100644 --- a/plume-front/src/editor.rs +++ b/plume-front/src/editor.rs @@ -210,7 +210,7 @@ fn autosave_debounce() { if let Some(timeout) = timeout.take() { window.clear_timeout_with_handle(timeout); } - let callback = Closure::once(|| autosave()); + let callback = Closure::once(autosave); **timeout = window .set_timeout_with_callback_and_timeout_and_arguments_0( callback.as_ref().unchecked_ref(), From f2411f4607da869d2d0d8443e79398b3e9710437 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 18:26:05 +0900 Subject: [PATCH 22/33] Use a element instead of nav to capture touchend event --- plume-front/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plume-front/src/lib.rs b/plume-front/src/lib.rs index 7b48806a..83985461 100755 --- a/plume-front/src/lib.rs +++ b/plume-front/src/lib.rs @@ -96,7 +96,7 @@ pub fn main() -> Result<(), JsValue> { /// But :focus-within is not yet supported by Webkit/Blink fn menu() { let document = window().unwrap().document().unwrap(); - if let Some(button) = document.get_element_by_id("menu") { + if let Ok(Some(button)) = document.query_selector("#menu a") { if let Some(menu) = document.get_element_by_id("content") { let show_menu = Closure::wrap(Box::new(|_: TouchEvent| { window() From 65991791741a4bc5ba238e41c41b0fa57e8c4aa1 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 18:48:33 +0900 Subject: [PATCH 23/33] Use document() function --- plume-front/src/lib.rs | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/plume-front/src/lib.rs b/plume-front/src/lib.rs index 83985461..74c3557d 100755 --- a/plume-front/src/lib.rs +++ b/plume-front/src/lib.rs @@ -95,14 +95,11 @@ pub fn main() -> Result<(), JsValue> { /// It should normally be working fine even without this code /// But :focus-within is not yet supported by Webkit/Blink fn menu() { - let document = window().unwrap().document().unwrap(); + let document = document(); if let Ok(Some(button)) = document.query_selector("#menu a") { if let Some(menu) = document.get_element_by_id("content") { let show_menu = Closure::wrap(Box::new(|_: TouchEvent| { - window() - .unwrap() - .document() - .unwrap() + self::document() .get_element_by_id("menu") .map(|menu| menu.class_list().add_1("show")) .unwrap() @@ -114,10 +111,7 @@ fn menu() { show_menu.forget(); let close_menu = Closure::wrap(Box::new(|_: TouchEvent| { - window() - .unwrap() - .document() - .unwrap() + self::document() .get_element_by_id("menu") .map(|menu| menu.class_list().remove_1("show")) .unwrap() @@ -132,17 +126,9 @@ fn menu() { /// Clear the URL of the search page before submitting request fn search() { - if let Some(form) = window() - .unwrap() - .document() - .unwrap() - .get_element_by_id("form") - { + if let Some(form) = document().get_element_by_id("form") { let normalize_query = Closure::wrap(Box::new(|_: Event| { - window() - .unwrap() - .document() - .unwrap() + document() .query_selector_all("#form input") .map(|inputs| { for i in 0..inputs.length() { From 16b1337d6700548a017256286b0cb246daa1e9b4 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Feb 2021 18:54:42 +0900 Subject: [PATCH 24/33] Add WAI-ARIA attributes to menu --- plume-front/src/lib.rs | 12 ++++++++++-- templates/base.rs.html | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/plume-front/src/lib.rs b/plume-front/src/lib.rs index 74c3557d..191a3397 100755 --- a/plume-front/src/lib.rs +++ b/plume-front/src/lib.rs @@ -101,7 +101,11 @@ fn menu() { let show_menu = Closure::wrap(Box::new(|_: TouchEvent| { self::document() .get_element_by_id("menu") - .map(|menu| menu.class_list().add_1("show")) + .map(|menu| { + menu.set_attribute("aria-expanded", "true") + .map(|_| menu.class_list().add_1("show")) + }) + .unwrap() .unwrap() .unwrap(); }) as Box); @@ -113,7 +117,11 @@ fn menu() { let close_menu = Closure::wrap(Box::new(|_: TouchEvent| { self::document() .get_element_by_id("menu") - .map(|menu| menu.class_list().remove_1("show")) + .map(|menu| { + menu.set_attribute("aria-expanded", "false") + .map(|_| menu.class_list().remove_1("show")) + }) + .unwrap() .unwrap() .unwrap() }) as Box); diff --git a/templates/base.rs.html b/templates/base.rs.html index 801cd670..115ea4e7 100644 --- a/templates/base.rs.html +++ b/templates/base.rs.html @@ -21,9 +21,9 @@
-
+