Remplement plume_front::editor using web-sys

This commit is contained in:
Kitaiti Makoto 2021-02-12 03:19:46 +09:00
parent 208c515d70
commit 24ecb15119
4 changed files with 526 additions and 326 deletions

135
Cargo.lock generated
View file

@ -9,7 +9,7 @@ dependencies = [
"activitystreams-derive", "activitystreams-derive",
"activitystreams-traits", "activitystreams-traits",
"activitystreams-types", "activitystreams-types",
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
] ]
@ -32,7 +32,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ef03168e704b0cae242e7a5d8b40506772b339687e01a3496fc4afe2e8542" checksum = "670ef03168e704b0cae242e7a5d8b40506772b339687e01a3496fc4afe2e8542"
dependencies = [ dependencies = [
"failure", "failure",
"serde 1.0.118", "serde 1.0.123",
"serde_json", "serde_json",
] ]
@ -46,7 +46,7 @@ dependencies = [
"activitystreams-traits", "activitystreams-traits",
"chrono", "chrono",
"mime 0.3.16", "mime 0.3.16",
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
] ]
@ -209,7 +209,7 @@ checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
] ]
[[package]] [[package]]
@ -353,7 +353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d"
dependencies = [ dependencies = [
"byteorder 1.3.4", "byteorder 1.3.4",
"serde 1.0.118", "serde 1.0.123",
] ]
[[package]] [[package]]
@ -526,7 +526,7 @@ dependencies = [
"libc", "libc",
"num-integer", "num-integer",
"num-traits 0.2.14", "num-traits 0.2.14",
"serde 1.0.118", "serde 1.0.123",
"time", "time",
"winapi 0.3.9", "winapi 0.3.9",
] ]
@ -573,13 +573,23 @@ dependencies = [
"lazy_static", "lazy_static",
"nom 5.1.2", "nom 5.1.2",
"rust-ini", "rust-ini",
"serde 1.0.118", "serde 1.0.123",
"serde-hjson", "serde-hjson",
"serde_json", "serde_json",
"toml 0.5.8", "toml 0.5.8",
"yaml-rust", "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]] [[package]]
name = "const-random" name = "const-random"
version = "0.1.13" version = "0.1.13"
@ -654,7 +664,7 @@ dependencies = [
"idna 0.1.5", "idna 0.1.5",
"log 0.4.11", "log 0.4.11",
"publicsuffix", "publicsuffix",
"serde 1.0.118", "serde 1.0.123",
"serde_json", "serde_json",
"time", "time",
"try_from", "try_from",
@ -966,7 +976,7 @@ checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
] ]
[[package]] [[package]]
@ -1161,7 +1171,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
"synstructure", "synstructure",
] ]
@ -1370,7 +1380,7 @@ dependencies = [
"proc-macro-hack 0.5.19", "proc-macro-hack 0.5.19",
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
] ]
[[package]] [[package]]
@ -2012,7 +2022,7 @@ dependencies = [
"log 0.4.11", "log 0.4.11",
"native-tls", "native-tls",
"nom 4.2.3", "nom 4.2.3",
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
] ]
@ -2079,7 +2089,7 @@ dependencies = [
"lindera-dictionary", "lindera-dictionary",
"lindera-ipadic", "lindera-ipadic",
"lindera-ipadic-builder", "lindera-ipadic-builder",
"serde 1.0.118", "serde 1.0.123",
"serde_json", "serde_json",
] ]
@ -2092,7 +2102,7 @@ dependencies = [
"bincode", "bincode",
"byteorder 1.3.4", "byteorder 1.3.4",
"encoding", "encoding",
"serde 1.0.118", "serde 1.0.123",
"yada", "yada",
] ]
@ -2225,7 +2235,7 @@ dependencies = [
"log 0.4.11", "log 0.4.11",
"phf", "phf",
"phf_codegen", "phf_codegen",
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"string_cache", "string_cache",
@ -2312,7 +2322,7 @@ dependencies = [
"migrations_internals", "migrations_internals",
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
] ]
[[package]] [[package]]
@ -2860,7 +2870,7 @@ checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
] ]
[[package]] [[package]]
@ -2871,7 +2881,7 @@ checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
] ]
[[package]] [[package]]
@ -2908,7 +2918,7 @@ dependencies = [
"chrono", "chrono",
"indexmap", "indexmap",
"line-wrap", "line-wrap",
"serde 1.0.118", "serde 1.0.123",
"xml-rs", "xml-rs",
] ]
@ -2945,7 +2955,7 @@ dependencies = [
"rsass", "rsass",
"ructe", "ructe",
"scheduled-thread-pool", "scheduled-thread-pool",
"serde 1.0.118", "serde 1.0.123",
"serde_json", "serde_json",
"shrinkwraprs 0.2.3", "shrinkwraprs 0.2.3",
"tracing", "tracing",
@ -2959,7 +2969,7 @@ dependencies = [
name = "plume-api" name = "plume-api"
version = "0.6.1-dev" version = "0.6.1-dev"
dependencies = [ dependencies = [
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
] ]
@ -2992,7 +3002,7 @@ dependencies = [
"regex-syntax 0.6.21", "regex-syntax 0.6.21",
"reqwest 0.9.24", "reqwest 0.9.24",
"rocket", "rocket",
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"shrinkwraprs 0.3.0", "shrinkwraprs 0.3.0",
@ -3005,11 +3015,14 @@ dependencies = [
name = "plume-front" name = "plume-front"
version = "0.6.1-dev" version = "0.6.1-dev"
dependencies = [ dependencies = [
"console_error_panic_hook",
"gettext", "gettext",
"gettext-macros", "gettext-macros",
"gettext-utils", "gettext-utils",
"js-sys",
"lazy_static", "lazy_static",
"serde 1.0.118", "serde 1.0.123",
"serde_derive",
"serde_json", "serde_json",
"stdweb", "stdweb",
"stdweb-internal-runtime", "stdweb-internal-runtime",
@ -3056,7 +3069,7 @@ dependencies = [
"rocket", "rocket",
"rocket_i18n", "rocket_i18n",
"scheduled-thread-pool", "scheduled-thread-pool",
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"shrinkwraprs 0.2.3", "shrinkwraprs 0.2.3",
@ -3532,7 +3545,7 @@ dependencies = [
"mime 0.3.16", "mime 0.3.16",
"mime_guess 2.0.3", "mime_guess 2.0.3",
"native-tls", "native-tls",
"serde 1.0.118", "serde 1.0.123",
"serde_json", "serde_json",
"serde_urlencoded 0.5.5", "serde_urlencoded 0.5.5",
"socks", "socks",
@ -3571,7 +3584,7 @@ dependencies = [
"native-tls", "native-tls",
"percent-encoding 2.1.0", "percent-encoding 2.1.0",
"pin-project-lite 0.2.0", "pin-project-lite 0.2.0",
"serde 1.0.118", "serde 1.0.123",
"serde_urlencoded 0.7.0", "serde_urlencoded 0.7.0",
"tokio 0.2.24", "tokio 0.2.24",
"tokio-tls", "tokio-tls",
@ -3671,7 +3684,7 @@ dependencies = [
"log 0.4.11", "log 0.4.11",
"notify", "notify",
"rocket", "rocket",
"serde 1.0.118", "serde 1.0.123",
"serde_json", "serde_json",
] ]
@ -3683,7 +3696,7 @@ dependencies = [
"data-encoding", "data-encoding",
"ring", "ring",
"rocket", "rocket",
"serde 1.0.118", "serde 1.0.123",
"time", "time",
] ]
@ -3771,7 +3784,7 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54"
dependencies = [ dependencies = [
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
] ]
@ -3882,9 +3895,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.118" version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -3904,13 +3917,13 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.118" version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
] ]
[[package]] [[package]]
@ -3921,7 +3934,7 @@ checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
"serde 1.0.118", "serde 1.0.123",
] ]
[[package]] [[package]]
@ -3941,7 +3954,7 @@ checksum = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a"
dependencies = [ dependencies = [
"dtoa", "dtoa",
"itoa", "itoa",
"serde 1.0.118", "serde 1.0.123",
"url 1.7.2", "url 1.7.2",
] ]
@ -3954,7 +3967,7 @@ dependencies = [
"form_urlencoded", "form_urlencoded",
"itoa", "itoa",
"ryu", "ryu",
"serde 1.0.118", "serde 1.0.123",
] ]
[[package]] [[package]]
@ -3994,7 +4007,7 @@ dependencies = [
"itertools 0.8.2", "itertools 0.8.2",
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
] ]
[[package]] [[package]]
@ -4007,7 +4020,7 @@ dependencies = [
"itertools 0.8.2", "itertools 0.8.2",
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
] ]
[[package]] [[package]]
@ -4129,7 +4142,7 @@ checksum = "a68c0ce28cf7400ed022e18da3c4591e14e1df02c70e93573cc59921b3923aeb"
dependencies = [ dependencies = [
"discard", "discard",
"rustc_version", "rustc_version",
"serde 1.0.118", "serde 1.0.123",
"serde_json", "serde_json",
"stdweb-derive", "stdweb-derive",
"stdweb-internal-macros", "stdweb-internal-macros",
@ -4145,7 +4158,7 @@ checksum = "0e21ebd9179de08f2300a65454268a17ea3de204627458588c84319c4def3930"
dependencies = [ dependencies = [
"proc-macro2 0.4.30", "proc-macro2 0.4.30",
"quote 0.6.13", "quote 0.6.13",
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
"syn 0.15.44", "syn 0.15.44",
] ]
@ -4159,7 +4172,7 @@ dependencies = [
"base-x", "base-x",
"proc-macro2 0.4.30", "proc-macro2 0.4.30",
"quote 0.6.13", "quote 0.6.13",
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"sha1", "sha1",
@ -4191,7 +4204,7 @@ dependencies = [
"new_debug_unreachable", "new_debug_unreachable",
"phf_shared", "phf_shared",
"precomputed-hash", "precomputed-hash",
"serde 1.0.118", "serde 1.0.123",
"string_cache_codegen", "string_cache_codegen",
"string_cache_shared", "string_cache_shared",
] ]
@ -4290,9 +4303,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.56" version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72" checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
@ -4316,7 +4329,7 @@ checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
"unicode-xid 0.2.1", "unicode-xid 0.2.1",
] ]
@ -4335,7 +4348,7 @@ dependencies = [
"onig", "onig",
"plist", "plist",
"regex-syntax 0.6.21", "regex-syntax 0.6.21",
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"walkdir", "walkdir",
@ -4375,7 +4388,7 @@ dependencies = [
"rayon", "rayon",
"regex", "regex",
"rust-stemmers", "rust-stemmers",
"serde 1.0.118", "serde 1.0.123",
"serde_json", "serde_json",
"smallvec 1.5.1", "smallvec 1.5.1",
"snap", "snap",
@ -4480,7 +4493,7 @@ checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
] ]
[[package]] [[package]]
@ -4647,7 +4660,7 @@ checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
] ]
[[package]] [[package]]
@ -4795,7 +4808,7 @@ version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
dependencies = [ dependencies = [
"serde 1.0.118", "serde 1.0.123",
] ]
[[package]] [[package]]
@ -4804,7 +4817,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [ dependencies = [
"serde 1.0.118", "serde 1.0.123",
] ]
[[package]] [[package]]
@ -4834,7 +4847,7 @@ checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
] ]
[[package]] [[package]]
@ -4873,7 +4886,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
dependencies = [ dependencies = [
"serde 1.0.118", "serde 1.0.123",
"tracing-core", "tracing-core",
] ]
@ -4888,7 +4901,7 @@ dependencies = [
"lazy_static", "lazy_static",
"matchers", "matchers",
"regex", "regex",
"serde 1.0.118", "serde 1.0.123",
"serde_json", "serde_json",
"sharded-slab", "sharded-slab",
"smallvec 1.5.1", "smallvec 1.5.1",
@ -5074,7 +5087,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
dependencies = [ dependencies = [
"rand 0.7.3", "rand 0.7.3",
"serde 1.0.118", "serde 1.0.123",
] ]
[[package]] [[package]]
@ -5086,7 +5099,7 @@ dependencies = [
"idna 0.1.5", "idna 0.1.5",
"lazy_static", "lazy_static",
"regex", "regex",
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"url 1.7.2", "url 1.7.2",
@ -5188,7 +5201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"serde 1.0.118", "serde 1.0.123",
"serde_json", "serde_json",
"wasm-bindgen-macro", "wasm-bindgen-macro",
] ]
@ -5204,7 +5217,7 @@ dependencies = [
"log 0.4.11", "log 0.4.11",
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -5238,7 +5251,7 @@ checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.8", "quote 1.0.8",
"syn 1.0.56", "syn 1.0.60",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -5266,7 +5279,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec24b1b0700d4b466d280228ed0f62274eedeaa80206820f071fdc8ed787b664" checksum = "ec24b1b0700d4b466d280228ed0f62274eedeaa80206820f071fdc8ed787b664"
dependencies = [ dependencies = [
"reqwest 0.9.24", "reqwest 0.9.24",
"serde 1.0.118", "serde 1.0.123",
"serde_derive", "serde_derive",
] ]

View file

@ -17,19 +17,38 @@ lazy_static = "1.3"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
wasm-bindgen = "0.2.70" 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] [dependencies.web-sys]
version = "0.3.47" version = "0.3.47"
features = [ features = [
'console',
'ClipboardEvent',
'CssStyleDeclaration',
'DataTransfer',
'Document', 'Document',
'DomStringMap',
'DomTokenList', 'DomTokenList',
'Element', 'Element',
'EventTarget', 'EventTarget',
'FocusEvent',
'History',
'HtmlAnchorElement',
'HtmlDocument',
'HtmlFormElement', 'HtmlFormElement',
'HtmlInputElement', 'HtmlInputElement',
'HtmlSelectElement',
'HtmlTextAreaElement',
'KeyboardEvent',
'Storage',
'Location',
'MouseEvent',
'Navigator', 'Navigator',
'Node', 'Node',
'NodeList', 'NodeList',
'Text',
'TouchEvent', 'TouchEvent',
'Window' 'Window'
] ]

View file

@ -1,9 +1,12 @@
use crate::CATALOG; use crate::CATALOG;
use serde::{Deserialize, Serialize}; use js_sys::{encode_uri_component, Date, RegExp};
use std::sync::Mutex; use serde_derive::{Deserialize, Serialize};
use stdweb::{ use std::{convert::TryInto, sync::Mutex};
unstable::{TryFrom, TryInto}, use wasm_bindgen::{prelude::*, JsCast, JsValue};
web::{event::*, html_element::*, *}, use web_sys::{
console, window, ClipboardEvent, Document, Element, Event, FocusEvent, HtmlAnchorElement,
HtmlDocument, HtmlElement, HtmlFormElement, HtmlInputElement, HtmlSelectElement,
HtmlTextAreaElement, KeyboardEvent, MouseEvent, Node,
}; };
macro_rules! mv { 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 { fn get_elt_value(id: &'static str) -> String {
let elt = document().get_element_by_id(id).unwrap(); let elt = document().get_element_by_id(id).unwrap();
let inp: Result<InputElement, _> = elt.clone().try_into(); let inp: Option<&HtmlInputElement> = elt.dyn_ref();
let textarea: Result<TextAreaElement, _> = elt.clone().try_into(); let textarea: Option<&HtmlTextAreaElement> = elt.dyn_ref();
let select: Result<SelectElement, _> = elt.try_into(); let select: Option<&HtmlSelectElement> = elt.dyn_ref();
inp.map(|i| i.raw_value()).unwrap_or_else(|_| { inp.map(|i| i.value()).unwrap_or_else(|| {
textarea textarea
.map(|t| t.value()) .map(|t| t.value())
.unwrap_or_else(|_| select.unwrap().raw_value()) .unwrap_or_else(|| select.unwrap().value())
}) })
} }
fn set_value<S: AsRef<str>>(id: &'static str, val: S) { fn set_value<S: AsRef<str>>(id: &'static str, val: S) {
let elt = document().get_element_by_id(id).unwrap(); let elt = document().get_element_by_id(id).unwrap();
let inp: Result<InputElement, _> = elt.clone().try_into(); let inp: Option<&HtmlInputElement> = elt.dyn_ref();
let textarea: Result<TextAreaElement, _> = elt.clone().try_into(); let textarea: Option<&HtmlTextAreaElement> = elt.dyn_ref();
let select: Result<SelectElement, _> = elt.try_into(); let select: Option<&HtmlSelectElement> = elt.dyn_ref();
inp.map(|i| i.set_raw_value(val.as_ref())) inp.map(|i| i.set_value(val.as_ref())).unwrap_or_else(|| {
.unwrap_or_else(|_| { textarea
textarea .map(|t| t.set_value(val.as_ref()))
.map(|t| t.set_value(val.as_ref())) .unwrap_or_else(|| select.unwrap().set_value(val.as_ref()))
.unwrap_or_else(|_| select.unwrap().set_raw_value(val.as_ref())) })
})
} }
fn no_return(evt: KeyDownEvent) { fn no_return(evt: KeyboardEvent) {
if evt.key() == "Enter" { if evt.key() == "Enter" {
evt.prevent_default(); evt.prevent_default();
} }
@ -50,7 +56,6 @@ fn no_return(evt: KeyDownEvent) {
pub enum EditorError { pub enum EditorError {
NoneError, NoneError,
DOMError, DOMError,
TypeError,
} }
impl From<std::option::NoneError> for EditorError { impl From<std::option::NoneError> for EditorError {
@ -58,22 +63,7 @@ impl From<std::option::NoneError> for EditorError {
EditorError::NoneError EditorError::NoneError
} }
} }
impl From<stdweb::web::error::InvalidCharacterError> for EditorError { const AUTOSAVE_DEBOUNCE_TIME: i32 = 5000;
fn from(_: stdweb::web::error::InvalidCharacterError) -> Self {
EditorError::DOMError
}
}
impl From<stdweb::private::TODO> for EditorError {
fn from(_: stdweb::private::TODO) -> Self {
EditorError::DOMError
}
}
impl From<stdweb::private::ConversionError> for EditorError {
fn from(_: stdweb::private::ConversionError) -> Self {
EditorError::TypeError
}
}
const AUTOSAVE_DEBOUNCE_TIME: u32 = 5000;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct AutosaveInformation { struct AutosaveInformation {
contents: String, contents: String,
@ -84,10 +74,16 @@ struct AutosaveInformation {
tags: String, tags: String,
title: String, title: String,
} }
js_serializable!(AutosaveInformation);
fn is_basic_editor() -> bool { fn is_basic_editor() -> bool {
if let Some(basic_editor) = window().local_storage().get("basic-editor") { if let Some(basic_editor) = window()
basic_editor == "true" .unwrap()
.local_storage()
.unwrap()
.unwrap()
.get("basic-editor")
.unwrap()
{
&basic_editor == "true"
} else { } else {
false false
} }
@ -96,65 +92,58 @@ fn get_title() -> String {
if is_basic_editor() { if is_basic_editor() {
get_elt_value("title") get_elt_value("title")
} else { } else {
let title_field = HtmlElement::try_from( document()
document() .query_selector("#plume-editor > h1")
.query_selector("#plume-editor > h1") .unwrap()
.ok() .unwrap()
.unwrap() .dyn_ref::<HtmlElement>()
.unwrap(), .unwrap()
) .inner_text()
.ok()
.unwrap();
title_field.inner_text()
} }
} }
fn get_autosave_id() -> String { fn get_autosave_id() -> String {
format!( format!(
"editor_contents={}", "editor_contents={}",
window().location().unwrap().pathname().unwrap() window().unwrap().location().pathname().unwrap()
) )
} }
fn get_editor_contents() -> String { fn get_editor_contents() -> String {
if is_basic_editor() { if is_basic_editor() {
get_elt_value("editor-content") get_elt_value("editor-content")
} else { } else {
let editor = let editor = document().query_selector("article").unwrap().unwrap();
HtmlElement::try_from(document().query_selector("article").ok().unwrap().unwrap()) let child_nodes = editor.child_nodes();
.ok() let mut md = String::new();
.unwrap(); for i in 0..child_nodes.length() {
editor.child_nodes().iter().fold(String::new(), |md, ch| { let ch = child_nodes.get(i).unwrap();
let to_append = match ch.node_type() { let to_append = match ch.node_type() {
NodeType::Element => { Node::ELEMENT_NODE => {
if js! { return @{&ch}.tagName; } == "DIV" { let elt = ch.dyn_ref::<Element>().unwrap();
(js! { return @{&ch}.innerHTML; }) if elt.tag_name() == "DIV" {
.try_into() elt.inner_html()
.unwrap_or_default()
} else { } else {
(js! { return @{&ch}.outerHTML; }) elt.outer_html()
.try_into()
.unwrap_or_default()
} }
} }
NodeType::Text => ch.node_value().unwrap_or_default(), Node::TEXT_NODE => ch.node_value().unwrap_or_default(),
_ => unreachable!(), _ => unreachable!(),
}; };
format!("{}\n\n{}", md, to_append) md = format!("{}\n\n{}", md, to_append);
}) }
md
} }
} }
fn get_subtitle() -> String { fn get_subtitle() -> String {
if is_basic_editor() { if is_basic_editor() {
get_elt_value("subtitle") get_elt_value("subtitle")
} else { } else {
let subtitle_element = HtmlElement::try_from( document()
document() .query_selector("#plume-editor > h2")
.query_selector("#plume-editor > h2") .unwrap()
.unwrap() .unwrap()
.unwrap(), .dyn_ref::<HtmlElement>()
) .unwrap()
.ok() .inner_text()
.unwrap();
subtitle_element.inner_text()
} }
} }
fn autosave() { fn autosave() {
@ -169,27 +158,31 @@ fn autosave() {
}; };
let id = get_autosave_id(); let id = get_autosave_id();
match window() match window()
.unwrap()
.local_storage() .local_storage()
.insert(&id, &serde_json::to_string(&info).unwrap()) .unwrap()
.unwrap()
.set(&id, &serde_json::to_string(&info).unwrap())
{ {
Ok(_) => {} 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() { 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 autosave_info: AutosaveInformation = serde_json::from_str(&autosave_str).ok().unwrap();
let message = i18n!( let message = i18n!(
CATALOG, CATALOG,
"Do you want to load the local autosave last edited at {}?"; "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("editor-content", &autosave_info.contents);
set_value("title", &autosave_info.title); set_value("title", &autosave_info.title);
set_value("subtitle", &autosave_info.subtitle); set_value("subtitle", &autosave_info.subtitle);
@ -202,18 +195,33 @@ fn load_autosave() {
} }
} }
fn clear_autosave() { fn clear_autosave() {
window().local_storage().remove(&get_autosave_id()); window()
console!(log, &format!("Saved to {}", &get_autosave_id())); .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! { lazy_static! {
static ref AUTOSAVE_TIMEOUT: Mutex<Option<TimeoutHandle>> = Mutex::new(None); static ref AUTOSAVE_TIMEOUT: Mutex<Option<TimeoutHandle>> = Mutex::new(None);
} }
fn autosave_debounce() { fn autosave_debounce() {
let window = window().unwrap();
let timeout = &mut AUTOSAVE_TIMEOUT.lock().unwrap(); let timeout = &mut AUTOSAVE_TIMEOUT.lock().unwrap();
if let Some(timeout) = timeout.take() { 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( fn init_widget(
parent: &Element, parent: &Element,
@ -222,19 +230,33 @@ fn init_widget(
content: String, content: String,
disable_return: bool, disable_return: bool,
) -> Result<HtmlElement, EditorError> { ) -> Result<HtmlElement, EditorError> {
let widget = placeholder(make_editable(tag).try_into()?, &placeholder_text); let widget = placeholder(
make_editable(tag).dyn_into::<HtmlElement>().unwrap(),
&placeholder_text,
);
if !content.is_empty() { 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 { if disable_return {
widget.add_event_listener(no_return); let callback = Closure::wrap(Box::new(no_return) as Box<dyn FnMut(KeyboardEvent)>);
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 // We need to do that to make sure the placeholder is correctly rendered
widget.focus(); widget.focus().map_err(|_| EditorError::DOMError)?;
widget.blur(); widget.blur().map_err(|_| EditorError::DOMError)?;
filter_paste(&widget); filter_paste(&widget);
@ -243,48 +265,88 @@ fn init_widget(
fn filter_paste(elt: &HtmlElement) { fn filter_paste(elt: &HtmlElement) {
// Only insert text when pasting something // Only insert text when pasting something
js! { let insert_text = Closure::wrap(Box::new(|evt: ClipboardEvent| {
@{&elt}.addEventListener("paste", function (evt) { evt.prevent_default();
evt.preventDefault(); if let Some(data) = evt.clipboard_data() {
document.execCommand("insertText", false, evt.clipboardData.getData("text")); if let Ok(data) = data.get_data("text") {
}); document()
}; .dyn_ref::<HtmlDocument>()
.unwrap()
.exec_command_with_show_ui_and_value("insertText", false, &data)
.unwrap();
}
}
}) as Box<dyn FnMut(ClipboardEvent)>);
elt.add_event_listener_with_callback("paste", insert_text.as_ref().unchecked_ref())
.unwrap();
insert_text.forget();
} }
pub fn init() -> Result<(), EditorError> { pub fn init() -> Result<(), EditorError> {
if let Some(ed) = document().get_element_by_id("plume-fallback-editor") { if let Some(ed) = document().get_element_by_id("plume-fallback-editor") {
load_autosave(); load_autosave();
ed.add_event_listener(|_: SubmitEvent| clear_autosave()); let callback = Closure::wrap(Box::new(|_| clear_autosave()) as Box<dyn FnMut(Event)>);
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 // Check if the user wants to use the basic editor
if window() if window()
.unwrap()
.local_storage() .local_storage()
.unwrap()
.unwrap()
.get("basic-editor") .get("basic-editor")
.map(|x| x == "true") .map(|x| x.is_some() && x.unwrap() == "true")
.unwrap_or(true) .unwrap_or(true)
{ {
if let Some(editor) = document().get_element_by_id("plume-fallback-editor") { if let Some(editor) = document().get_element_by_id("plume-fallback-editor") {
if let Ok(Some(title_label)) = document().query_selector("label[for=title]") { if let Ok(Some(title_label)) = document().query_selector("label[for=title]") {
let editor_button = document().create_element("a")?; let editor_button = document()
js! { @{&editor_button}.href = "#"; } .create_element("a")
editor_button.add_event_listener(|_: ClickEvent| { .map_err(|_| EditorError::DOMError)?;
if window() editor_button
.dyn_ref::<HtmlAnchorElement>()
.unwrap()
.set_href("#");
let disable_basic_editor = Closure::wrap(Box::new(|_| {
let window = window().unwrap();
if window
.local_storage() .local_storage()
.insert("basic-editor", "false") .unwrap()
.unwrap()
.set("basic-editor", "false")
.is_err() .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 window.history().unwrap().go_with_delta(0).ok(); // refresh
}); })
editor_button.append_child( as Box<dyn FnMut(MouseEvent)>);
&document().create_text_node(&i18n!(CATALOG, "Open the rich text editor")), 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<dyn FnMut(KeyboardEvent)>
); );
editor.insert_before(&editor_button, &title_label).ok();
document() document()
.get_element_by_id("editor-content") .get_element_by_id("editor-content")
.unwrap() .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> { fn init_editor() -> Result<(), EditorError> {
if let Some(ed) = document().get_element_by_id("plume-editor") { if let Some(ed) = document().get_element_by_id("plume-editor") {
// Show the editor // Show the editor
js! { @{&ed}.style.display = "block"; }; ed.dyn_ref::<HtmlElement>()
.unwrap()
.style()
.set_property("display", "block")
.map_err(|_| EditorError::DOMError)?;
// And hide the HTML-only fallback // 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")?;
let old_title = document().get_element_by_id("plume-editor-title")?; let old_title = document().get_element_by_id("plume-editor-title")?;
js! { old_ed
@{&old_ed}.style.display = "none"; .dyn_ref::<HtmlElement>()
@{&old_title}.style.display = "none"; .unwrap()
}; .style()
.set_property("display", "none")
.map_err(|_| EditorError::DOMError)?;
old_title
.dyn_ref::<HtmlElement>()
.unwrap()
.style()
.set_property("display", "none")
.map_err(|_| EditorError::DOMError)?;
// Get content from the old editor (when editing an article for instance) // Get content from the old editor (when editing an article for instance)
let title_val = get_elt_value("title"); let title_val = get_elt_value("title");
@ -326,35 +400,44 @@ fn init_editor() -> Result<(), EditorError> {
content_val.clone(), content_val.clone(),
false, false,
)?; )?;
js! { @{&content}.innerHTML = @{content_val}; }; content.set_inner_html(&content_val);
// character counter // character counter
content.add_event_listener(mv!(content => move |_: KeyDownEvent| { let character_counter = Closure::wrap(Box::new(mv!(content => move |_| {
window().set_timeout(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") { if let Some(e) = document().get_element_by_id("char-count") {
let count = chars_left("#plume-fallback-editor", &content).unwrap_or_default(); let count = chars_left("#plume-fallback-editor", &content).unwrap_or_default();
let text = i18n!(CATALOG, "Around {} characters left"; count); let text = i18n!(CATALOG, "Around {} characters left"; count);
HtmlElement::try_from(e).map(|e| { e.dyn_ref::<HtmlElement>().map(|e| {
js!{@{e}.innerText = @{text}}; e.set_inner_text(&text);
}).ok(); }).unwrap();
}; };
}), 0); })) as Box<dyn FnMut()>);
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(); autosave_debounce();
})); })) as Box<dyn FnMut(KeyboardEvent)>);
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( let show_popup = Closure::wrap(Box::new(mv!(title, subtitle, content, old_ed => move |_| {
mv!(title, subtitle, content, old_ed => move |_: ClickEvent| { let popup = document().get_element_by_id("publish-popup").or_else(||
let popup = document().get_element_by_id("publish-popup").or_else(|| init_popup(&title, &subtitle, &content, &old_ed).ok()
init_popup(&title, &subtitle, &content, &old_ed).ok() ).unwrap();
).unwrap(); let bg = document().get_element_by_id("popup-bg").or_else(||
let bg = document().get_element_by_id("popup-bg").or_else(|| init_popup_bg().ok()
init_popup_bg().ok() ).unwrap();
).unwrap();
popup.class_list().add("show").unwrap(); popup.class_list().add_1("show").unwrap();
bg.class_list().add("show").unwrap(); bg.class_list().add_1("show").unwrap();
}), })) as Box<dyn FnMut(MouseEvent)>);
); 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(); show_errors();
setup_close_button(); setup_close_button();
@ -364,32 +447,47 @@ fn init_editor() -> Result<(), EditorError> {
fn setup_close_button() { fn setup_close_button() {
if let Some(button) = document().get_element_by_id("close-editor") { if let Some(button) = document().get_element_by_id("close-editor") {
button.add_event_listener(|_: ClickEvent| { let close_editor = Closure::wrap(Box::new(|_| {
window() window()
.unwrap()
.local_storage() .local_storage()
.insert("basic-editor", "true") .unwrap()
.unwrap()
.set("basic-editor", "true")
.unwrap(); .unwrap();
window().history().go(0).unwrap(); // Refresh the page window()
}); .unwrap()
.history()
.unwrap()
.go_with_delta(0)
.unwrap(); // Refresh the page
}) as Box<dyn FnMut(MouseEvent)>);
button
.add_event_listener_with_callback("click", close_editor.as_ref().unchecked_ref())
.unwrap();
close_editor.forget();
} }
} }
fn show_errors() { fn show_errors() {
if let Ok(Some(header)) = document().query_selector("header") { let document = document();
let list = document().create_element("header").unwrap(); if let Ok(Some(header)) = document.query_selector("header") {
list.class_list().add("messages").unwrap(); let list = document.create_element("header").unwrap();
for error in document().query_selector_all("p.error").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 error
.parent_element() .parent_element()
.unwrap() .unwrap()
.remove_child(&error) .remove_child(&error)
.unwrap(); .unwrap();
list.append_child(&error); let _ = list.append_child(&error);
} }
header header
.parent_element() .parent_element()
.unwrap() .unwrap()
.insert_before(&list, &header.next_sibling().unwrap()) .insert_before(&list, header.next_sibling().as_ref())
.unwrap(); .unwrap();
} }
} }
@ -400,9 +498,17 @@ fn init_popup(
content: &HtmlElement, content: &HtmlElement,
old_ed: &Element, old_ed: &Element,
) -> Result<Element, EditorError> { ) -> Result<Element, EditorError> {
let popup = document().create_element("div")?; let document = document();
popup.class_list().add("popup")?; let popup = document
popup.set_attribute("id", "publish-popup")?; .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") let tags = get_elt_value("tags")
.split(',') .split(',')
@ -410,112 +516,157 @@ fn init_popup(
.map(str::to_string) .map(str::to_string)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let license = get_elt_value("license"); let license = get_elt_value("license");
make_input(&i18n!(CATALOG, "Tags"), "popup-tags", &popup).set_raw_value(&tags.join(", ")); make_input(&i18n!(CATALOG, "Tags"), "popup-tags", &popup).set_value(&tags.join(", "));
make_input(&i18n!(CATALOG, "License"), "popup-license", &popup).set_raw_value(&license); make_input(&i18n!(CATALOG, "License"), "popup-license", &popup).set_value(&license);
let cover_label = document().create_element("label")?; let cover_label = document
cover_label.append_child(&document().create_text_node(&i18n!(CATALOG, "Cover"))); .create_element("label")
cover_label.set_attribute("for", "cover")?; .map_err(|_| EditorError::DOMError)?;
let cover = document().get_element_by_id("cover")?; 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(); cover.parent_element()?.remove_child(&cover).ok();
popup.append_child(&cover_label); popup
popup.append_child(&cover); .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") { if let Some(draft_checkbox) = document.get_element_by_id("draft") {
let draft_label = document().create_element("label")?; let draft_checkbox = draft_checkbox.dyn_ref::<HtmlInputElement>().unwrap();
draft_label.set_attribute("for", "popup-draft")?; 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(); let draft = document.create_element("input").unwrap();
js! { draft.set_id("popup-draft");
@{&draft}.id = "popup-draft"; let draft = draft.dyn_ref::<HtmlInputElement>().unwrap();
@{&draft}.name = "popup-draft"; draft.set_name("popup-draft");
@{&draft}.type = "checkbox"; draft.set_type("checkbox");
@{&draft}.checked = @{&draft_checkbox}.checked; draft.set_checked(draft_checkbox.checked());
};
draft_label.append_child(&draft); draft_label
draft_label.append_child(&document().create_text_node(&i18n!(CATALOG, "This is a draft"))); .append_child(&draft)
popup.append_child(&draft_label); .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")?; let button = document
js! { .create_element("input")
@{&button}.type = "submit"; .map_err(|_| EditorError::DOMError)?;
@{&button}.value = @{i18n!(CATALOG, "Publish")}; button
}; .append_child(&document.create_text_node(&i18n!(CATALOG, "Publish")))
button.append_child(&document().create_text_node(&i18n!(CATALOG, "Publish"))); .map_err(|_| EditorError::DOMError)?;
button.add_event_listener( let button = button.dyn_ref::<HtmlInputElement>().unwrap();
mv!(title, subtitle, content, old_ed => move |_: ClickEvent| { button.set_type("submit");
title.focus(); // Remove the placeholder before publishing button.set_value(&i18n!(CATALOG, "Publish"));
set_value("title", title.inner_text()); let callback = Closure::wrap(Box::new(mv!(title, subtitle, content, old_ed => move |_| {
subtitle.focus(); let document = self::document();
set_value("subtitle", subtitle.inner_text()); title.focus().unwrap(); // Remove the placeholder before publishing
content.focus(); set_value("title", title.inner_text());
set_value("editor-content", content.child_nodes().iter().fold(String::new(), |md, ch| { subtitle.focus().unwrap();
let to_append = match ch.node_type() { set_value("subtitle", subtitle.inner_text());
NodeType::Element => { content.focus().unwrap();
if js!{ return @{&ch}.tagName; } == "DIV" { let mut md = String::new();
(js!{ return @{&ch}.innerHTML; }).try_into().unwrap_or_default() let child_nodes = content.child_nodes();
} else { for i in 0..child_nodes.length() {
(js!{ return @{&ch}.outerHTML; }).try_into().unwrap_or_default() let ch = child_nodes.get(i).unwrap();
} let to_append = match ch.node_type() {
}, Node::ELEMENT_NODE => {
NodeType::Text => ch.node_value().unwrap_or_default(), let ch = ch.dyn_ref::<Element>().unwrap();
_ => unreachable!(), if ch.tag_name() == "DIV" {
}; ch.inner_html()
format!("{}\n\n{}", md, to_append) } else {
})); ch.outer_html()
set_value("tags", get_elt_value("popup-tags")); }
if let Some(draft) = document().get_element_by_id("popup-draft") { },
js!{ Node::TEXT_NODE => ch.node_value().unwrap_or_default(),
document.getElementById("draft").checked = @{draft}.checked; _ => unreachable!(),
};
}
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();
}; };
}), md = format!("{}\n\n{}", md, to_append);
); }
popup.append_child(&button); 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::<HtmlInputElement>().unwrap();
let draft = draft.dyn_ref::<HtmlInputElement>().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::<HtmlFormElement>().unwrap();
old_ed.submit().unwrap();
})) as Box<dyn FnMut(MouseEvent)>);
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) Ok(popup)
} }
fn init_popup_bg() -> Result<Element, EditorError> { fn init_popup_bg() -> Result<Element, EditorError> {
let bg = document().create_element("div")?; let bg = document()
bg.class_list().add("popup-bg")?; .create_element("div")
bg.set_attribute("id", "popup-bg")?; .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); document()
bg.add_event_listener(|_: ClickEvent| close_popup()); .body()?
.append_child(&bg)
.map_err(|_| EditorError::DOMError)?;
let callback = Closure::wrap(Box::new(|_| close_popup()) as Box<dyn FnMut(MouseEvent)>);
bg.add_event_listener_with_callback("click", callback.as_ref().unchecked_ref())
.unwrap();
callback.forget();
Ok(bg) Ok(bg)
} }
fn chars_left(selector: &str, content: &HtmlElement) -> Option<i32> { fn chars_left(selector: &str, content: &HtmlElement) -> Option<i32> {
match document().query_selector(selector) { match document().query_selector(selector) {
Ok(Some(form)) => HtmlElement::try_from(form).ok().and_then(|form| { Ok(Some(form)) => form.dyn_ref::<HtmlElement>().and_then(|form| {
if let Some(len) = form if let Some(len) = form
.get_attribute("content-size") .get_attribute("content-size")
.and_then(|s| s.parse::<i32>().ok()) .and_then(|s| s.parse::<i32>().ok())
{ {
(js! { (encode_uri_component(&content.inner_html())
let x = encodeURIComponent(@{content}.innerHTML) .replace("%20", "+")
.replace(/%20/g, "+") .replace("%0A", "%0D0A")
.replace(/%0A/g, "%0D%0A") .replace_by_pattern(&RegExp::new("[!'*()]", "g"), "XXX")
.replace(new RegExp("[!'*()]", "g"), "XXX") // replace exceptions of encodeURIComponent with placeholder .length()
.length + 2; + 2_u32)
console.log(x); .try_into()
return x; .map(|c: i32| len - c)
}) .ok()
.try_into()
.map(|c: i32| len - c)
.ok()
} else { } else {
None None
} }
@ -525,26 +676,26 @@ fn chars_left(selector: &str, content: &HtmlElement) -> Option<i32> {
} }
fn close_popup() { 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("publish-popup").map(hide);
document().get_element_by_id("popup-bg").map(hide); document().get_element_by_id("popup-bg").map(hide);
} }
fn make_input(label_text: &str, name: &'static str, form: &Element) -> InputElement { fn make_input(label_text: &str, name: &'static str, form: &Element) -> HtmlInputElement {
let label = document().create_element("label").unwrap(); let document = document();
label.append_child(&document().create_text_node(label_text)); let label = document.create_element("label").unwrap();
label
.append_child(&document.create_text_node(label_text))
.unwrap();
label.set_attribute("for", name).unwrap(); label.set_attribute("for", name).unwrap();
let inp: InputElement = document() let inp = document.create_element("input").unwrap();
.create_element("input") let inp = inp.dyn_into::<HtmlInputElement>().unwrap();
.unwrap()
.try_into()
.unwrap();
inp.set_attribute("name", name).unwrap(); inp.set_attribute("name", name).unwrap();
inp.set_attribute("id", name).unwrap(); inp.set_attribute("id", name).unwrap();
form.append_child(&label); form.append_child(&label).unwrap();
form.append_child(&inp); form.append_child(&inp).unwrap();
inp inp
} }
@ -558,36 +709,46 @@ fn make_editable(tag: &'static str) -> Element {
} }
fn placeholder(elt: HtmlElement, text: &str) -> HtmlElement { fn placeholder(elt: HtmlElement, text: &str) -> HtmlElement {
elt.dataset().insert("placeholder", text).unwrap(); elt.dataset().set("placeholder", text).unwrap();
elt.dataset().insert("edited", "false").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" { if elt.dataset().get("edited").unwrap().as_str() != "true" {
clear_children(&elt); clear_children(&elt);
} }
})); })) as Box<dyn FnMut(FocusEvent)>);
elt.add_event_listener(mv!(elt => move |_: BlurEvent| { 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" { if elt.dataset().get("edited").unwrap().as_str() != "true" {
clear_children(&elt); clear_children(&elt);
let ph = document().create_element("span").expect("Couldn't create placeholder"); let ph = document().create_element("span").expect("Couldn't create placeholder");
ph.class_list().add("placeholder").expect("Couldn't add class"); 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())); ph.append_child(&document().create_text_node(&elt.dataset().get("placeholder").unwrap_or_default())).unwrap();
elt.append_child(&ph); elt.append_child(&ph).unwrap();
} }
})); })) as Box<dyn FnMut(Event)>);
elt.add_event_listener(mv!(elt => move |_: KeyUpEvent| { elt.add_event_listener_with_callback("blur", callback.as_ref().unchecked_ref())
elt.dataset().insert("edited", if elt.inner_text().trim_matches('\n').is_empty() { .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" "false"
} else { } else {
"true" "true"
}).expect("Couldn't update edition state"); }).expect("Couldn't update edition state");
})); })) as Box<dyn FnMut(KeyboardEvent)>);
elt.add_event_listener_with_callback("keyup", callback.as_ref().unchecked_ref())
.unwrap();
callback.forget();
elt elt
} }
fn clear_children(elt: &HtmlElement) { fn clear_children(elt: &HtmlElement) {
for child in elt.child_nodes() { let child_nodes = elt.child_nodes();
elt.remove_child(&child).unwrap(); for _ in 0..child_nodes.length() {
elt.remove_child(&child_nodes.get(0).unwrap()).unwrap();
} }
} }

View file

@ -7,7 +7,7 @@ extern crate gettext_macros;
extern crate lazy_static; extern crate lazy_static;
use wasm_bindgen::{prelude::*, JsCast}; use wasm_bindgen::{prelude::*, JsCast};
use web_sys::{window, Element, Event, HtmlInputElement, TouchEvent}; use web_sys::{console, window, Element, Event, HtmlInputElement, TouchEvent};
init_i18n!( init_i18n!(
"plume-front", "plume-front",
@ -53,7 +53,7 @@ init_i18n!(
zh zh
); );
// mod editor; mod editor;
compile_i18n!(); compile_i18n!();
@ -78,8 +78,15 @@ lazy_static! {
#[wasm_bindgen(start)] #[wasm_bindgen(start)]
pub fn main() -> Result<(), JsValue> { 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(); menu();
search(); search();
editor::init()
.map_err(|e| console::error_1(&&format!("Editor error: {:?}", e).into()))
.ok();
Ok(()) Ok(())
} }