diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..d8bb9aa4 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,10 @@ +[target.wasm32-unknown-unknown] +# required for clippy +rustflags = [ + "--cfg", "web_sys_unstable_apis", +] + +[target.x86_64-unknown-linux-gnu] +rustflags = [ + "--cfg", "web_sys_unstable_apis", +] diff --git a/.circleci/config.yml b/.circleci/config.yml index 87d2e802..cd7f2d06 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 @@ -168,7 +168,7 @@ jobs: steps: - restore_env: cache: <<#parameters.postgres>>postgres<><<^parameters.postgres>>sqlite<> - - run: cargo web deploy -p 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 +203,7 @@ jobs: steps: - restore_env: cache: release-<<#parameters.postgres>>postgres<><<^parameters.postgres>>sqlite<> - - run: cargo web deploy -p plume-front --release + - run: 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..5187add0 100644 --- a/.circleci/images/plume-buildenv/Dockerfile +++ b/.circleci/images/plume-buildenv/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:buster-20201117 +FROM debian:buster-20210208 ENV PATH="/root/.cargo/bin:${PATH}" #install native/circleci/build dependancies @@ -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/CHANGELOG.md b/CHANGELOG.md index 74de3970..cea9de71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ ### Fixed - Percent-encode URI for remote_interact (#866, #857) -- Menu animation not opening on iOS (#876) +- Menu animation not opening on iOS (#876, #897) ## [[0.6.0]] - 2020-12-29 diff --git a/Cargo.lock b/Cargo.lock index 6a22928a..1b052bc5 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]] @@ -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" @@ -353,7 +347,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 +520,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 +567,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 +658,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 +970,7 @@ checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -988,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" @@ -1161,7 +1159,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", "synstructure", ] @@ -1370,7 +1368,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]] @@ -1929,9 +1927,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", ] @@ -2012,7 +2010,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 +2077,7 @@ dependencies = [ "lindera-dictionary", "lindera-ipadic", "lindera-ipadic-builder", - "serde 1.0.118", + "serde 1.0.123", "serde_json", ] @@ -2092,7 +2090,7 @@ dependencies = [ "bincode", "byteorder 1.3.4", "encoding", - "serde 1.0.118", + "serde 1.0.123", "yada", ] @@ -2225,7 +2223,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 +2310,7 @@ dependencies = [ "migrations_internals", "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -2860,7 +2858,7 @@ checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -2871,7 +2869,7 @@ checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -2908,7 +2906,7 @@ dependencies = [ "chrono", "indexmap", "line-wrap", - "serde 1.0.118", + "serde 1.0.123", "xml-rs", ] @@ -2945,7 +2943,7 @@ dependencies = [ "rsass", "ructe", "scheduled-thread-pool", - "serde 1.0.118", + "serde 1.0.123", "serde_json", "shrinkwraprs 0.2.3", "tracing", @@ -2959,7 +2957,7 @@ dependencies = [ name = "plume-api" version = "0.6.1-dev" dependencies = [ - "serde 1.0.118", + "serde 1.0.123", "serde_derive", ] @@ -2992,7 +2990,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,14 +3003,17 @@ 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", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -3054,7 +3055,7 @@ dependencies = [ "rocket", "rocket_i18n", "scheduled-thread-pool", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", "serde_json", "shrinkwraprs 0.2.3", @@ -3530,7 +3531,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", @@ -3569,7 +3570,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", @@ -3669,7 +3670,7 @@ dependencies = [ "log 0.4.11", "notify", "rocket", - "serde 1.0.118", + "serde 1.0.123", "serde_json", ] @@ -3681,7 +3682,7 @@ dependencies = [ "data-encoding", "ring", "rocket", - "serde 1.0.118", + "serde 1.0.123", "time", ] @@ -3769,7 +3770,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", ] @@ -3880,9 +3881,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", ] @@ -3902,13 +3903,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]] @@ -3919,7 +3920,7 @@ checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" dependencies = [ "itoa", "ryu", - "serde 1.0.118", + "serde 1.0.123", ] [[package]] @@ -3939,7 +3940,7 @@ checksum = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" dependencies = [ "dtoa", "itoa", - "serde 1.0.118", + "serde 1.0.123", "url 1.7.2", ] @@ -3952,15 +3953,9 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.118", + "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" @@ -3992,7 +3987,7 @@ dependencies = [ "itertools 0.8.2", "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -4005,7 +4000,7 @@ dependencies = [ "itertools 0.8.2", "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -4119,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.118", - "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.118", - "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.118", - "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" @@ -4189,7 +4133,7 @@ dependencies = [ "new_debug_unreachable", "phf_shared", "precomputed-hash", - "serde 1.0.118", + "serde 1.0.123", "string_cache_codegen", "string_cache_shared", ] @@ -4288,9 +4232,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", @@ -4314,7 +4258,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", ] @@ -4333,7 +4277,7 @@ dependencies = [ "onig", "plist", "regex-syntax 0.6.21", - "serde 1.0.118", + "serde 1.0.123", "serde_derive", "serde_json", "walkdir", @@ -4373,7 +4317,7 @@ dependencies = [ "rayon", "regex", "rust-stemmers", - "serde 1.0.118", + "serde 1.0.123", "serde_json", "smallvec 1.5.1", "snap", @@ -4478,7 +4422,7 @@ checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -4645,7 +4589,7 @@ checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -4793,7 +4737,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]] @@ -4802,7 +4746,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]] @@ -4832,7 +4776,7 @@ checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", ] [[package]] @@ -4871,7 +4815,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", ] @@ -4886,7 +4830,7 @@ dependencies = [ "lazy_static", "matchers", "regex", - "serde 1.0.118", + "serde 1.0.123", "serde_json", "sharded-slab", "smallvec 1.5.1", @@ -5072,7 +5016,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]] @@ -5084,7 +5028,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", @@ -5181,28 +5125,28 @@ 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", + "serde 1.0.123", "serde_json", "wasm-bindgen-macro", ] [[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", "log 0.4.11", "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.56", + "syn 1.0.60", "wasm-bindgen-shared", ] @@ -5220,9 +5164,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,28 +5174,28 @@ 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", - "syn 1.0.56", + "syn 1.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[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", @@ -5264,7 +5208,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/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/assets/themes/default/_header.scss b/assets/themes/default/_header.scss index 8b3e9cf8..11d05c33 100644 --- a/assets/themes/default/_header.scss +++ b/assets/themes/default/_header.scss @@ -115,7 +115,7 @@ body > header { opacity: 1; } } - @-webkit-keyframes menuOpening { + @-webkit-keyframes menuOpening { from { -webkit-transform: scaleX(0); transform-origin: left; @@ -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; @@ -216,9 +216,7 @@ body > header { display: block; position: absolute; left: 50%; - transform: translate(-50%, 0); transform: translateZ(0); - -webkit-transform: none !important; opacity: 0; font-size: 0.9em; white-space: nowrap; @@ -230,9 +228,8 @@ body > header { i { margin-bottom: 0.75em; } .mobile-label { opacity: 1; - transform: translate(-50%, 80%); - -webkit-transform: translate(-50%, 80%); - } + transform: translate(-50%, 80%); + } } } } @@ -252,17 +249,17 @@ body > header { } } @-webkit-keyframes menuOpening { - from { - -webkit-transform: scaleX(0); - transform-origin: left; - opacity: 0; - } - to { + from { + -webkit-transform: scaleX(0); + transform-origin: left; + opacity: 0; + } + to { -webkit-transform: scaleX(1); transform-origin: left; opacity: 1; + } } -} body > header { flex-direction: column; @@ -280,7 +277,7 @@ body > header { } } - body > header:focus-within #content, #content.show { + body > header:focus-within #content, .show + #content { position: fixed; display: flex; flex-direction: column; 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) } diff --git a/plume-front/Cargo.toml b/plume-front/Cargo.toml index d84b4320..8e085ef3 100644 --- a/plume-front/Cargo.toml +++ b/plume-front/Cargo.toml @@ -4,12 +4,49 @@ 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" 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" } 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..8d0f2a81 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 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, Element, Event, FocusEvent, HtmlAnchorElement, HtmlDocument, + HtmlElement, HtmlFormElement, HtmlInputElement, HtmlSelectElement, HtmlTextAreaElement, + KeyboardEvent, MouseEvent, Node, }; macro_rules! mv { @@ -17,30 +20,29 @@ macro_rules! mv { 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 +52,6 @@ fn no_return(evt: KeyDownEvent) { pub enum EditorError { NoneError, DOMError, - TypeError, } impl From for EditorError { @@ -58,22 +59,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 +70,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 +88,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 +154,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 +191,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 +226,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 +261,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 +355,30 @@ 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_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")?; - 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 new file mode 100755 index 00000000..191a3397 --- /dev/null +++ b/plume-front/src/lib.rs @@ -0,0 +1,163 @@ +#![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::{console, window, Document, Element, Event, HtmlInputElement, TouchEvent}; + +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 = 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> { + 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(()) +} + +/// 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 = 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| { + self::document() + .get_element_by_id("menu") + .map(|menu| { + menu.set_attribute("aria-expanded", "true") + .map(|_| menu.class_list().add_1("show")) + }) + .unwrap() + .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(|_: TouchEvent| { + self::document() + .get_element_by_id("menu") + .map(|menu| { + menu.set_attribute("aria-expanded", "false") + .map(|_| menu.class_list().remove_1("show")) + }) + .unwrap() + .unwrap() + .unwrap() + }) as Box); + menu.add_event_listener_with_callback("touchend", close_menu.as_ref().unchecked_ref()) + .unwrap(); + close_menu.forget(); + } + } +} + +/// Clear the URL of the search page before submitting request +fn search() { + if let Some(form) = document().get_element_by_id("form") { + let normalize_query = Closure::wrap(Box::new(|_: Event| { + document() + .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(); + } +} + +fn document() -> Document { + window().unwrap().document().unwrap() +} 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(); - }); - } -} diff --git a/script/generate_artifact.sh b/script/generate_artifact.sh index c61087c7..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} diff --git a/script/plume-front.sh b/script/plume-front.sh old mode 100644 new mode 100755 index 8568e076..63b2f5dd --- 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" wasm-pack build --target web --release plume-front else - cargo web deploy -p plume-front + wasm-pack build --target web --release plume-front fi diff --git a/script/prebuild.sh b/script/prebuild.sh index 4b020d82..4e575809 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 + 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..d480fc0d 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" wasm-pack build --target web --release plume-front \ ;; \ *) \ - cargo web deploy -p plume-front --release \ + wasm-pack build --target web --release plume-front \ ;; \ esac diff --git a/templates/base.rs.html b/templates/base.rs.html index 7dfc783a..115ea4e7 100644 --- a/templates/base.rs.html +++ b/templates/base.rs.html @@ -21,9 +21,9 @@
-
+ - + diff --git a/templates/users/header.rs.html b/templates/users/header.rs.html index 567d8a12..e7f5080e 100644 --- a/templates/users/header.rs.html +++ b/templates/users/header.rs.html @@ -14,7 +14,7 @@ @user.fqn -

+

@if user.is_admin() { @i18n!(ctx.1, "Admin") }