feat: support equation block (#2903)

This commit is contained in:
Kilu.He 2023-06-28 12:39:04 +08:00 committed by GitHub
parent 7f74fd6149
commit 18ed553296
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 659 additions and 348 deletions

View File

@ -105,7 +105,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "appflowy-integrate"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d1882d#d1882d6784a8863419727be92c29923cd175fd50"
dependencies = [
"anyhow",
"collab",
@ -145,9 +145,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "arrayvec"
version = "0.7.3"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8868f09ff8cea88b079da74ae569d9b8c62a23c68c746240b704ee6f7525c89c"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "async-stream"
@ -168,7 +168,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -179,7 +179,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -203,7 +203,7 @@ dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps 6.1.0",
"system-deps 6.1.1",
]
[[package]]
@ -600,7 +600,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -827,7 +827,7 @@ checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8"
dependencies = [
"glib-sys",
"libc",
"system-deps 6.1.0",
"system-deps 6.1.1",
]
[[package]]
@ -837,7 +837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838"
dependencies = [
"serde",
"toml 0.7.4",
"toml 0.7.5",
]
[[package]]
@ -886,9 +886,9 @@ dependencies = [
[[package]]
name = "cfg-expr"
version = "0.15.2"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70d3ad08698a0568b0562f22710fe6bfc1f4a61a367c77d0398c562eadd453a"
checksum = "215c0072ecc28f92eeb0eea38ba63ddfcb65c2828c46311d646f1a3ff5f9841c"
dependencies = [
"smallvec",
"target-lexicon",
@ -935,7 +935,7 @@ checksum = "cf9cc2b23599e6d7479755f3594285efb3f74a1bdca7a7374948bc831e23a552"
dependencies = [
"chrono",
"chrono-tz-build 0.1.0",
"phf 0.11.1",
"phf 0.11.2",
]
[[package]]
@ -956,8 +956,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9998fb9f7e9b2111641485bf8beb32f92945f97f92a3d061f744cfef335f751"
dependencies = [
"parse-zoneinfo",
"phf 0.11.1",
"phf_codegen 0.11.1",
"phf 0.11.2",
"phf_codegen 0.11.2",
]
[[package]]
@ -1030,7 +1030,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d1882d#d1882d6784a8863419727be92c29923cd175fd50"
dependencies = [
"anyhow",
"bytes",
@ -1048,7 +1048,7 @@ dependencies = [
[[package]]
name = "collab-client-ws"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d1882d#d1882d6784a8863419727be92c29923cd175fd50"
dependencies = [
"bytes",
"collab-sync",
@ -1066,7 +1066,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d1882d#d1882d6784a8863419727be92c29923cd175fd50"
dependencies = [
"anyhow",
"async-trait",
@ -1092,7 +1092,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d1882d#d1882d6784a8863419727be92c29923cd175fd50"
dependencies = [
"proc-macro2",
"quote",
@ -1104,7 +1104,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d1882d#d1882d6784a8863419727be92c29923cd175fd50"
dependencies = [
"anyhow",
"collab",
@ -1122,7 +1122,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d1882d#d1882d6784a8863419727be92c29923cd175fd50"
dependencies = [
"anyhow",
"chrono",
@ -1142,7 +1142,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d1882d#d1882d6784a8863419727be92c29923cd175fd50"
dependencies = [
"bincode",
"chrono",
@ -1162,7 +1162,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d1882d#d1882d6784a8863419727be92c29923cd175fd50"
dependencies = [
"anyhow",
"async-trait",
@ -1193,7 +1193,7 @@ dependencies = [
[[package]]
name = "collab-sync"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d1882d#d1882d6784a8863419727be92c29923cd175fd50"
dependencies = [
"bytes",
"collab",
@ -1292,21 +1292,20 @@ dependencies = [
[[package]]
name = "core-graphics-types"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b"
checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33"
dependencies = [
"bitflags",
"core-foundation",
"foreign-types",
"libc",
]
[[package]]
name = "cpufeatures"
version = "0.2.7"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c"
dependencies = [
"libc",
]
@ -1397,7 +1396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -1452,7 +1451,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -1463,7 +1462,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
dependencies = [
"darling_core",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -1629,7 +1628,7 @@ checksum = "80663502655af01a2902dff3f06869330782267924bf1788410b74edcd93770a"
dependencies = [
"cc",
"rustc_version",
"toml 0.7.4",
"toml 0.7.5",
"vswhom",
"winreg 0.11.0",
]
@ -1655,6 +1654,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
[[package]]
name = "errno"
version = "0.3.1"
@ -1868,7 +1873,7 @@ dependencies = [
"flowy-notification",
"flowy-task",
"futures",
"indexmap",
"indexmap 1.9.3",
"lazy_static",
"lib-dispatch",
"lib-infra",
@ -1916,7 +1921,7 @@ dependencies = [
"flowy-derive",
"flowy-error",
"flowy-notification",
"indexmap",
"indexmap 1.9.3",
"lib-dispatch",
"nanoid",
"parking_lot 0.12.1",
@ -2213,7 +2218,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -2294,7 +2299,7 @@ dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps 6.1.0",
"system-deps 6.1.1",
]
[[package]]
@ -2311,7 +2316,7 @@ dependencies = [
"libc",
"pango-sys",
"pkg-config",
"system-deps 6.1.0",
"system-deps 6.1.1",
]
[[package]]
@ -2325,7 +2330,7 @@ dependencies = [
"gobject-sys",
"libc",
"pkg-config",
"system-deps 6.1.0",
"system-deps 6.1.1",
]
[[package]]
@ -2337,15 +2342,15 @@ dependencies = [
"gdk-sys",
"glib-sys",
"libc",
"system-deps 6.1.0",
"system-deps 6.1.1",
"x11",
]
[[package]]
name = "generator"
version = "0.7.4"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3e123d9ae7c02966b4d892e550bdc32164f05853cd40ab570650ad600596a8a"
checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
dependencies = [
"cc",
"libc",
@ -2400,9 +2405,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.27.2"
version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
[[package]]
name = "gio"
@ -2430,7 +2435,7 @@ dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps 6.1.0",
"system-deps 6.1.1",
"winapi",
]
@ -2476,7 +2481,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4"
dependencies = [
"libc",
"system-deps 6.1.0",
"system-deps 6.1.1",
]
[[package]]
@ -2517,7 +2522,7 @@ checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a"
dependencies = [
"glib-sys",
"libc",
"system-deps 6.1.0",
"system-deps 6.1.1",
]
[[package]]
@ -2558,7 +2563,7 @@ dependencies = [
"gobject-sys",
"libc",
"pango-sys",
"system-deps 6.1.0",
"system-deps 6.1.1",
]
[[package]]
@ -2577,9 +2582,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.3.19"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782"
checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049"
dependencies = [
"bytes",
"fnv",
@ -2587,7 +2592,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
"indexmap",
"indexmap 1.9.3",
"slab",
"tokio",
"tokio-util",
@ -2612,6 +2617,12 @@ dependencies = [
"ahash 0.8.3",
]
[[package]]
name = "hashbrown"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
[[package]]
name = "heck"
version = "0.3.3"
@ -2732,9 +2743,9 @@ dependencies = [
[[package]]
name = "hyper"
version = "0.14.26"
version = "0.14.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
dependencies = [
"bytes",
"futures-channel",
@ -2834,17 +2845,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "idna"
version = "0.4.0"
@ -2896,6 +2896,16 @@ dependencies = [
"serde",
]
[[package]]
name = "indexmap"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [
"equivalent",
"hashbrown 0.14.0",
]
[[package]]
name = "infer"
version = "0.12.0"
@ -2927,9 +2937,9 @@ dependencies = [
[[package]]
name = "ipnet"
version = "2.7.2"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]]
name = "itertools"
@ -3145,9 +3155,9 @@ dependencies = [
[[package]]
name = "lib0"
version = "0.16.5"
version = "0.16.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf23122cb1c970b77ea6030eac5e328669415b65d2ab245c99bfb110f9d62dc"
checksum = "49d27ae71668a38ad135d463703ce0c5d9cf5a29f9a02add7a0dac6ebb523196"
dependencies = [
"serde",
"serde_json",
@ -3156,9 +3166,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.146"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "libloading"
@ -3631,9 +3641,9 @@ dependencies = [
[[package]]
name = "openssl"
version = "0.10.54"
version = "0.10.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019"
checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d"
dependencies = [
"bitflags",
"cfg-if",
@ -3652,7 +3662,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -3663,9 +3673,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.88"
version = "0.9.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617"
checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
dependencies = [
"cc",
"libc",
@ -3717,7 +3727,7 @@ dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps 6.1.0",
"system-deps 6.1.1",
]
[[package]]
@ -3797,9 +3807,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pest"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70"
checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9"
dependencies = [
"thiserror",
"ucd-trie",
@ -3807,9 +3817,9 @@ dependencies = [
[[package]]
name = "pest_derive"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb"
checksum = "aef623c9bbfa0eedf5a0efba11a5ee83209c326653ca31ff019bec3a95bfff2b"
dependencies = [
"pest",
"pest_generator",
@ -3817,22 +3827,22 @@ dependencies = [
[[package]]
name = "pest_generator"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e"
checksum = "b3e8cba4ec22bada7fc55ffe51e2deb6a0e0db2d0b7ab0b103acc80d2510c190"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
name = "pest_meta"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411"
checksum = "a01f71cb40bd8bb94232df14b946909e14660e33fc05db3e50ae2a82d7ea0ca0"
dependencies = [
"once_cell",
"pest",
@ -3863,11 +3873,11 @@ dependencies = [
[[package]]
name = "phf"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_shared 0.11.1",
"phf_shared 0.11.2",
]
[[package]]
@ -3892,12 +3902,12 @@ dependencies = [
[[package]]
name = "phf_codegen"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770"
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
dependencies = [
"phf_generator 0.11.1",
"phf_shared 0.11.1",
"phf_generator 0.11.2",
"phf_shared 0.11.2",
]
[[package]]
@ -3922,11 +3932,11 @@ dependencies = [
[[package]]
name = "phf_generator"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared 0.11.1",
"phf_shared 0.11.2",
"rand 0.8.5",
]
@ -3979,9 +3989,9 @@ dependencies = [
[[package]]
name = "phf_shared"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher",
]
@ -4003,7 +4013,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -4031,7 +4041,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590"
dependencies = [
"base64 0.21.2",
"indexmap",
"indexmap 1.9.3",
"line-wrap",
"quick-xml",
"serde",
@ -4074,12 +4084,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "prettyplease"
version = "0.2.6"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b69d39aab54d069e7f2fe8cb970493e7834601ca2d8c65fd7bbd183578080d1"
checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282"
dependencies = [
"proc-macro2",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -4133,9 +4143,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.60"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
dependencies = [
"unicode-ident",
]
@ -4582,11 +4592,11 @@ dependencies = [
[[package]]
name = "rust_decimal"
version = "1.29.1"
version = "1.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc"
checksum = "d0446843641c69436765a35a5a77088e28c2e6a12da93e84aa3ab1cd4aa5a042"
dependencies = [
"arrayvec 0.7.3",
"arrayvec 0.7.4",
"borsh",
"bytecheck",
"byteorder",
@ -4600,9 +4610,9 @@ dependencies = [
[[package]]
name = "rust_decimal_macros"
version = "1.29.1"
version = "1.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e773fd3da1ed42472fdf3cfdb4972948a555bc3d73f5e0bdb99d17e7b54c687"
checksum = "7ca5c398d85f83b9a44de754a2048625a8c5eafcf070da7b8f116b685e2f6608"
dependencies = [
"quote",
"rust_decimal",
@ -4861,14 +4871,14 @@ checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
name = "serde_json"
version = "1.0.96"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3"
dependencies = [
"itoa 1.0.6",
"ryu",
@ -4883,14 +4893,14 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
name = "serde_spanned"
version = "0.6.2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d"
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
dependencies = [
"serde",
]
@ -4916,7 +4926,7 @@ dependencies = [
"base64 0.21.2",
"chrono",
"hex",
"indexmap",
"indexmap 1.9.3",
"serde",
"serde_json",
"serde_with_macros",
@ -4932,7 +4942,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -4999,9 +5009,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]]
name = "sha2"
version = "0.10.6"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
dependencies = [
"cfg-if",
"cpufeatures",
@ -5245,9 +5255,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.18"
version = "2.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616"
dependencies = [
"proc-macro2",
"quote",
@ -5269,14 +5279,14 @@ dependencies = [
[[package]]
name = "system-deps"
version = "6.1.0"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5fa6fb9ee296c0dc2df41a656ca7948546d061958115ddb0bcaae43ad0d17d2"
checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3"
dependencies = [
"cfg-expr 0.15.2",
"cfg-expr 0.15.3",
"heck 0.4.1",
"pkg-config",
"toml 0.7.4",
"toml 0.7.5",
"version-compare 0.1.1",
]
@ -5357,15 +5367,15 @@ dependencies = [
[[package]]
name = "target-lexicon"
version = "0.12.7"
version = "0.12.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5"
checksum = "1b1c7f239eb94671427157bd93b3694320f3668d4e1eff08c7285366fd777fac"
[[package]]
name = "tauri"
version = "1.4.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc35893c7e08d9564a9206bd52182dce031b0d5132dc946b3e166e00d03f8cfe"
checksum = "7fbe522898e35407a8e60dc3870f7579fea2fc262a6a6072eccdd37ae1e1d91e"
dependencies = [
"anyhow",
"cocoa",
@ -5542,7 +5552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb"
dependencies = [
"embed-resource",
"toml 0.7.4",
"toml 0.7.5",
]
[[package]]
@ -5626,7 +5636,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -5729,7 +5739,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -5836,9 +5846,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.7.4"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec"
checksum = "1ebafdf5ad1220cb59e7d17cf4d2c72015297b75b19a10472f99b89225089240"
dependencies = [
"serde",
"serde_spanned",
@ -5848,20 +5858,20 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.10"
version = "0.19.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739"
checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7"
dependencies = [
"indexmap",
"indexmap 2.0.0",
"serde",
"serde_spanned",
"toml_datetime",
@ -5922,13 +5932,13 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.24"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
]
[[package]]
@ -6189,7 +6199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
dependencies = [
"form_urlencoded",
"idna 0.4.0",
"idna",
"percent-encoding",
"serde",
]
@ -6218,11 +6228,11 @@ dependencies = [
[[package]]
name = "validator"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32ad5bf234c7d3ad1042e5252b7eddb2c4669ee23f32c7dd0e9b7705f07ef591"
checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd"
dependencies = [
"idna 0.2.3",
"idna",
"lazy_static",
"regex",
"serde",
@ -6345,7 +6355,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
"wasm-bindgen-shared",
]
@ -6379,7 +6389,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.22",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -6444,7 +6454,7 @@ dependencies = [
"pango-sys",
"pkg-config",
"soup2-sys",
"system-deps 6.1.0",
"system-deps 6.1.1",
]
[[package]]
@ -6887,9 +6897,9 @@ dependencies = [
[[package]]
name = "yrs"
version = "0.16.5"
version = "0.16.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c2aef2bf89b4f7c003f9c73f1c8097427ca32e1d006443f3f607f11e79a797b"
checksum = "04e5192da97bd1621497ddf66b42475fb0cc44b6ebcf64510f0d3827c0b15cad"
dependencies = [
"atomic_refcell",
"lib0",

View File

@ -6,6 +6,8 @@ import BlockMenuTurnInto from '$app/components/document/BlockSideToolbar/BlockMe
import TextField from '@mui/material/TextField';
import { Keyboard } from '$app/constants/document/keyboard';
import { selectOptionByUpDown } from '$app/utils/document/menu';
import { useSubscribeNode } from '$app/components/document/_shared/SubscribeNode.hooks';
import { BlockType } from '$app/interfaces/document';
enum BlockMenuOption {
Duplicate = 'Duplicate',
@ -22,6 +24,7 @@ interface Option {
function BlockMenu({ id, onClose }: { id: string; onClose: () => void }) {
const { handleDelete, handleDuplicate } = useBlockMenu(id);
const { node } = useSubscribeNode(id);
const [subMenuOpened, setSubMenuOpened] = useState(false);
const [hovered, setHovered] = useState<BlockMenuOption | null>(null);
@ -39,29 +42,36 @@ function BlockMenu({ id, onClose }: { id: string; onClose: () => void }) {
[onClose]
);
const excludeTurnIntoBlock = useMemo(() => {
return [BlockType.DividerBlock].includes(node.type);
}, [node.type]);
const options: Option[] = useMemo(
() => [
{
operate: () => {
return handleClick({ operate: handleDelete });
() =>
[
{
operate: () => {
return handleClick({ operate: handleDelete });
},
title: 'Delete',
icon: <Delete />,
key: BlockMenuOption.Delete,
},
title: 'Delete',
icon: <Delete />,
key: BlockMenuOption.Delete,
},
{
operate: () => {
return handleClick({ operate: handleDuplicate });
{
operate: () => {
return handleClick({ operate: handleDuplicate });
},
title: 'Duplicate',
icon: <ContentCopy />,
key: BlockMenuOption.Duplicate,
},
title: 'Duplicate',
icon: <ContentCopy />,
key: BlockMenuOption.Duplicate,
},
{
key: BlockMenuOption.TurnInto,
},
],
[handleClick, handleDelete, handleDuplicate]
excludeTurnIntoBlock
? null
: {
key: BlockMenuOption.TurnInto,
},
].filter((item) => item !== null) as Option[],
[excludeTurnIntoBlock, handleClick, handleDelete, handleDuplicate]
);
const onKeyDown = useCallback(
@ -131,7 +141,10 @@ function BlockMenu({ id, onClose }: { id: string; onClose: () => void }) {
}}
menuOpened={subMenuOpened}
isHovered={hovered === BlockMenuOption.TurnInto}
onClose={() => setSubMenuOpened(false)}
onClose={() => {
setSubMenuOpened(false);
onClose();
}}
id={id}
/>
);

View File

@ -1,4 +1,4 @@
import React, { MouseEvent, useRef } from 'react';
import React, { MouseEvent, useEffect, useRef } from 'react';
import { ArrowRight, Transform } from '@mui/icons-material';
import MenuItem from '$app/components/document/_shared/MenuItem';
import TurnIntoPopover from '$app/components/document/_shared/TurnInto';
@ -17,8 +17,22 @@ function BlockMenuTurnInto({
menuOpened: boolean;
}) {
const ref = useRef<HTMLDivElement | null>(null);
const open = isHovered && menuOpened && Boolean(ref.current);
const [anchorPosition, setAnchorPosition] = React.useState<{ top: number; left: number }>();
const open = Boolean(anchorPosition);
useEffect(() => {
if (isHovered && menuOpened) {
const rect = ref.current?.getBoundingClientRect();
if (!rect) return;
setAnchorPosition({
top: rect.top + rect.height / 2,
left: rect.left + rect.width,
});
} else {
setAnchorPosition(undefined);
}
}, [isHovered, menuOpened]);
return (
<>
<MenuItem
@ -45,11 +59,8 @@ function BlockMenuTurnInto({
},
}}
onClose={onClose}
anchorEl={ref.current}
anchorOrigin={{
vertical: 'center',
horizontal: 'right',
}}
anchorReference={'anchorPosition'}
anchorPosition={anchorPosition}
transformOrigin={{
vertical: 'center',
horizontal: 'left',

View File

@ -40,6 +40,10 @@ export function useBlockSideToolbar({ container }: { container: HTMLDivElement }
top = headingBlockTopOffset[nodeData.level];
}
if (node.type === BlockType.DividerBlock) {
top = -3;
}
setStyle({
opacity: '1',
pointerEvents: 'auto',
@ -99,39 +103,47 @@ function getNodeIdByPoint(x: number, y: number) {
: null;
}
const origin: {
anchorOrigin: PopoverOrigin;
transformOrigin: PopoverOrigin;
} = {
anchorOrigin: {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: {
vertical: 'bottom',
horizontal: 'left',
},
const transformOrigin: PopoverOrigin = {
vertical: 'bottom',
horizontal: 'left',
};
export function usePopover() {
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
const [anchorPosition, setAnchorPosition] = React.useState<{
top: number;
left: number;
}>();
const onClose = useCallback(() => {
setAnchorEl(null);
setAnchorPosition(undefined);
}, []);
const handleOpen = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
setAnchorEl(e.currentTarget);
const rect = e.currentTarget.getBoundingClientRect();
setAnchorPosition({
top: rect.top + rect.height,
left: rect.left + rect.width,
});
}, []);
const open = Boolean(anchorEl);
const open = Boolean(anchorPosition);
const onMouseDown = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
}, []);
return {
anchorEl,
anchorPosition,
onClose,
open,
handleOpen,
...origin,
anchorReference: 'anchorPosition' as const,
transformOrigin,
onMouseDown,
disableRestoreFocus: true,
disableAutoFocus: true,
disableEnforceFocus: true,
};
}

View File

@ -11,6 +11,7 @@ import { rectSelectionActions } from '$app_reducers/document/slice';
import { addBlockBelowClickThunk } from '$app_reducers/document/async-actions/menu';
import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
import { RANGE_NAME, RECT_RANGE_NAME } from '$app/constants/document/name';
import { setRectSelectionThunk } from '$app_reducers/document/async-actions/rect_selection';
export default function BlockSideToolbar({ container }: { container: HTMLDivElement }) {
const dispatch = useAppDispatch();
@ -22,9 +23,6 @@ export default function BlockSideToolbar({ container }: { container: HTMLDivElem
);
const { handleOpen, ...popoverProps } = usePopover();
// prevent popover from showing when anchorEl is not in DOM
const showPopover = popoverProps.anchorEl ? document.contains(popoverProps.anchorEl) : true;
if (!nodeId || isDragging) return null;
return (
@ -65,11 +63,12 @@ export default function BlockSideToolbar({ container }: { container: HTMLDivElem
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
if (!nodeId) return;
dispatch(
rectSelectionActions.setSelectionById({
setRectSelectionThunk({
docId,
blockId: nodeId,
selection: [nodeId],
})
);
handleOpen(e);
}}
>
@ -78,11 +77,9 @@ export default function BlockSideToolbar({ container }: { container: HTMLDivElem
</div>
</Portal>
{showPopover && (
<Popover {...popoverProps}>
<BlockMenu id={nodeId} onClose={popoverProps.onClose} />
</Popover>
)}
<Popover {...popoverProps}>
<BlockMenu id={nodeId} onClose={popoverProps.onClose} />
</Popover>
</>
);
}

View File

@ -11,7 +11,10 @@ export function useBlockSlash() {
const { docId } = useSubscribeDocument();
const { blockId, visible, slashText, hoverOption } = useSubscribeSlash();
const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);
const [anchorPosition, setAnchorPosition] = React.useState<{
top: number;
left: number;
}>();
useEffect(() => {
if (blockId && visible) {
@ -19,11 +22,17 @@ export function useBlockSlash() {
const el = blockEl.querySelector(`[role="textbox"]`) as HTMLElement;
if (el) {
setAnchorEl(el);
const rect = el.getBoundingClientRect();
setAnchorPosition({
top: rect.top + rect.height,
left: rect.left,
});
return;
}
}
setAnchorEl(null);
setAnchorPosition(undefined);
}, [blockId, visible]);
useEffect(() => {
@ -43,11 +52,11 @@ export function useBlockSlash() {
dispatch(slashCommandActions.closeSlashCommand(docId));
}, [dispatch, docId]);
const open = Boolean(anchorEl);
const open = Boolean(anchorPosition);
return {
open,
anchorEl,
anchorPosition,
onClose,
blockId,
searchText,

View File

@ -5,18 +5,15 @@ import { useBlockSlash } from '$app/components/document/BlockSlash/index.hooks';
import { Keyboard } from '$app/constants/document/keyboard';
function BlockSlash({ container }: { container: HTMLDivElement }) {
const { blockId, open, onClose, anchorEl, searchText, hoverOption } = useBlockSlash();
const { blockId, open, onClose, anchorPosition, searchText, hoverOption } = useBlockSlash();
if (!blockId) return null;
return (
<Popover
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
anchorReference={'anchorPosition'}
anchorPosition={anchorPosition}
transformOrigin={{
vertical: 'top',
horizontal: 'left',

View File

@ -0,0 +1,54 @@
import React from 'react';
import { BlockType, NestedBlock } from '$app/interfaces/document';
import KatexMath from '$app/components/document/_shared/KatexMath';
import Popover from '@mui/material/Popover';
import EquationEditContent from '$app/components/document/_shared/TemporaryInput/EquationEditContent';
import { useEquationBlock } from '$app/components/document/EquationBlock/useEquationBlock';
import { Functions } from '@mui/icons-material';
function EquationBlock({ node }: { node: NestedBlock<BlockType.EquationBlock> }) {
const { ref, value, onChange, onOpenPopover, open, anchorPosition, onConfirm, onClosePopover } =
useEquationBlock(node);
const formula = open ? value : node.data.formula;
return (
<>
<div
ref={ref}
onClick={onOpenPopover}
className={'flex min-h-[59px] cursor-pointer items-center justify-center overflow-hidden hover:bg-main-selector'}
>
{formula ? (
<KatexMath latex={formula} />
) : (
<span className={'flex text-shade-2'}>
<Functions />
<span>Add a TeX equation</span>
</span>
)}
</div>
<Popover
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
onMouseDown={(e) => e.stopPropagation()}
onClose={onClosePopover}
open={open}
anchorReference={'anchorPosition'}
anchorPosition={anchorPosition}
>
<EquationEditContent
placeholder={'c = \\pm\\sqrt{a^2 + b^2\\text{ if }a\\neq 0\\text{ or }b\\neq 0}'}
multiline={true}
value={value}
onChange={onChange}
onConfirm={onConfirm}
/>
</Popover>
</>
);
}
export default EquationBlock;

View File

@ -0,0 +1,71 @@
import { useCallback, useRef, useState } from 'react';
import { BlockType, NestedBlock } from '$app/interfaces/document';
import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
import { useAppDispatch } from '$app/stores/store';
import { updateNodeDataThunk } from '$app_reducers/document/async-actions';
import { rectSelectionActions } from '$app_reducers/document/slice';
import { setRectSelectionThunk } from '$app_reducers/document/async-actions/rect_selection';
export function useEquationBlock(node: NestedBlock<BlockType.EquationBlock>) {
const { controller, docId } = useSubscribeDocument();
const id = node.id;
const dispatch = useAppDispatch();
const formula = node.data.formula;
const ref = useRef<HTMLDivElement>(null);
const [value, setValue] = useState(formula);
const [anchorPosition, setAnchorPosition] = useState<{
top: number;
left: number;
}>();
const open = Boolean(anchorPosition);
const onChange = useCallback((newVal: string) => {
setValue(newVal);
}, []);
const onOpenPopover = useCallback(() => {
setValue(formula);
const rect = ref.current?.getBoundingClientRect();
if (!rect) return;
setAnchorPosition({
top: rect.top + rect.height,
left: rect.left + rect.width / 2,
});
}, [formula]);
const onClosePopover = useCallback(() => {
setAnchorPosition(undefined);
dispatch(
setRectSelectionThunk({
docId,
selection: [id],
})
);
}, [dispatch, id, docId]);
const onConfirm = useCallback(async () => {
await dispatch(
updateNodeDataThunk({
id,
data: {
formula: value,
},
controller,
})
);
onClosePopover();
}, [dispatch, id, value, controller, onClosePopover]);
return {
open,
ref,
value,
onChange,
onOpenPopover,
onClosePopover,
onConfirm,
anchorPosition,
};
}

View File

@ -17,6 +17,7 @@ import CalloutBlock from '$app/components/document/CalloutBlock';
import BlockOverlay from '$app/components/document/Overlay/BlockOverlay';
import CodeBlock from '$app/components/document/CodeBlock';
import { NodeIdContext } from '$app/components/document/_shared/SubscribeNode.hooks';
import EquationBlock from '$app/components/document/EquationBlock';
function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes<HTMLDivElement>) {
const { node, childIds, isSelected, ref } = useNode(id);
@ -26,38 +27,50 @@ function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes<H
case BlockType.TextBlock: {
return <TextBlock node={node} childIds={childIds} />;
}
case BlockType.HeadingBlock: {
return <HeadingBlock node={node} />;
}
case BlockType.TodoListBlock: {
return <TodoListBlock node={node} childIds={childIds} />;
}
case BlockType.QuoteBlock: {
return <QuoteBlock node={node} childIds={childIds} />;
}
case BlockType.BulletedListBlock: {
return <BulletedListBlock node={node} childIds={childIds} />;
}
case BlockType.NumberedListBlock: {
return <NumberedListBlock node={node} childIds={childIds} />;
}
case BlockType.ToggleListBlock: {
return <ToggleListBlock node={node} childIds={childIds} />;
}
case BlockType.DividerBlock: {
return <DividerBlock />;
}
case BlockType.CalloutBlock: {
return <CalloutBlock node={node} childIds={childIds} />;
}
case BlockType.CodeBlock:
return <CodeBlock node={node} />;
case BlockType.EquationBlock:
return <EquationBlock node={node} />;
default:
return <UnSupportedBlock />;
}
}, [node, childIds]);
const className = props.className ? ` ${props.className}` : '';
if (!node) return null;
return (

View File

@ -1,8 +1,11 @@
import { useMenuStyle } from './index.hooks';
import TextActionMenuList from '$app/components/document/TextActionMenu/menu';
import BlockPortal from '$app/components/document/BlockPortal';
import { useMemo } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useSubscribeRanges } from '$app/components/document/_shared/SubscribeSelection.hooks';
import { debounce } from '$app/utils/tool';
import { getBlock } from '$app/components/document/_shared/SubscribeNode.hooks';
import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
const TextActionComponent = ({ container }: { container: HTMLDivElement }) => {
const { ref, id } = useMenuStyle(container);
@ -30,6 +33,15 @@ const TextActionComponent = ({ container }: { container: HTMLDivElement }) => {
const TextActionMenu = ({ container }: { container: HTMLDivElement }) => {
const range = useSubscribeRanges();
const { docId } = useSubscribeDocument();
const [show, setShow] = useState(false);
const debounceShow = useMemo(() => {
return debounce(() => {
setShow(true);
}, 100);
}, []);
const canShow = useMemo(() => {
const { isDragging, focus, anchor, ranges, caret } = range;
@ -37,19 +49,38 @@ const TextActionMenu = ({ container }: { container: HTMLDivElement }) => {
if (isDragging) return false;
// don't show if no focus or anchor
if (!caret) return false;
const isSameLine = anchor?.id === focus?.id;
if (!anchor || !focus) return false;
const anchorNode = getBlock(docId, anchor.id);
const focusNode = getBlock(docId, focus.id);
// include document title
if (!anchorNode.parent || !focusNode.parent) return false;
const isSameLine = anchor.id === focus.id;
// show toolbar if range has multiple nodes
if (!isSameLine) return true;
const caretRange = ranges?.[caret.id];
if (!caretRange) return false;
// show toolbar if range is not collapsed
return caretRange.length > 0;
}, [range]);
}, [docId, range]);
if (!canShow) return null;
useEffect(() => {
if (!canShow) {
debounceShow.cancel();
setShow(false);
return;
}
debounceShow();
}, [canShow, debounceShow]);
if (!show) return null;
return <TextActionComponent container={container} />;
};

View File

@ -6,18 +6,26 @@ import MenuTooltip from './MenuTooltip';
import { useSubscribeNode } from '$app/components/document/_shared/SubscribeNode.hooks';
function TurnIntoSelect({ id }: { id: string }) {
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
const [anchorPosition, setAnchorPosition] = React.useState<{
top: number;
left: number;
}>();
const { node } = useSubscribeNode(id);
const handleClick = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
const rect = event.currentTarget.getBoundingClientRect();
setAnchorPosition({
top: rect.top + rect.height + 5,
left: rect.left,
});
}, []);
const handleClose = useCallback(() => {
setAnchorEl(null);
setAnchorPosition(undefined);
}, []);
const open = Boolean(anchorEl);
const open = Boolean(anchorPosition);
return (
<>
@ -33,14 +41,11 @@ function TurnIntoSelect({ id }: { id: string }) {
id={id}
open={open}
onClose={handleClose}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'center',
horizontal: 'center',
}}
anchorReference={'anchorPosition'}
anchorPosition={anchorPosition}
transformOrigin={{
vertical: 'center',
horizontal: 'center',
vertical: 'top',
horizontal: 'left',
}}
/>
</>

View File

@ -1,75 +1,64 @@
import { Keyboard } from '$app/constants/document/keyboard';
import { BlockType } from '$app/interfaces/document';
export const turnIntoShortcuts = {
[Keyboard.keys.SPACE]: [
{
type: BlockType.HeadingBlock,
/**
* # or ## or ###
*/
markdownRegexp: /^(#{1,3})(\s)+$/,
},
{
type: BlockType.TodoListBlock,
/**
* -[] or -[x] or -[ ] or [] or [x] or [ ]
*/
markdownRegexp: /^((-)?\[(x|\s)?\])(\s)+$/,
},
{
type: BlockType.BulletedListBlock,
/**
* - or + or *
*/
markdownRegexp: /^(\s*[-+*])(\s)+$/,
},
{
type: BlockType.NumberedListBlock,
/**
* 1. or 2. or 3.
* a. or b. or c.
*/
markdownRegexp: /^(\s*[\d|a-zA-Z]+\.)(\s)+$/,
},
{
type: BlockType.QuoteBlock,
/**
* " or or
*/
markdownRegexp: /^("|“|”)(\s)+$/,
},
{
type: BlockType.CalloutBlock,
/**
* [!TIP] or [!INFO] or [!WARNING] or [!DANGER]
*/
markdownRegexp: /^(\[!)(TIP|INFO|WARNING|DANGER)(\])(\s)+$/,
},
{
type: BlockType.ToggleListBlock,
/**
* >
*/
markdownRegexp: /^(>)(\s)+$/,
},
],
[Keyboard.keys.BACK_QUOTE]: [
{
type: BlockType.CodeBlock,
/**
* ```
*/
markdownRegexp: /^(```)$/,
},
],
[Keyboard.keys.REDUCE]: [
{
type: BlockType.DividerBlock,
/**
* ---
*/
markdownRegexp: /^(-{3,})$/,
},
],
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export const turnIntoConfig: Record<
BlockType,
{
type: BlockType;
markdownRegexp: RegExp;
triggerKey: string;
}
> = {
[BlockType.HeadingBlock]: {
type: BlockType.HeadingBlock,
markdownRegexp: /^(#{1,3})(\s)+$/,
triggerKey: Keyboard.keys.SPACE,
},
[BlockType.TodoListBlock]: {
type: BlockType.TodoListBlock,
markdownRegexp: /^((-)?\[(x|\s)?\])(\s)+$/,
triggerKey: Keyboard.keys.SPACE,
},
[BlockType.BulletedListBlock]: {
type: BlockType.BulletedListBlock,
markdownRegexp: /^(\s*[-+*])(\s)+$/,
triggerKey: Keyboard.keys.SPACE,
},
[BlockType.NumberedListBlock]: {
type: BlockType.NumberedListBlock,
markdownRegexp: /^(\s*[\d|a-zA-Z]+\.)(\s)+$/,
triggerKey: Keyboard.keys.SPACE,
},
[BlockType.QuoteBlock]: {
type: BlockType.QuoteBlock,
markdownRegexp: /^("|“|”)(\s)+$/,
triggerKey: Keyboard.keys.SPACE,
},
[BlockType.ToggleListBlock]: {
type: BlockType.ToggleListBlock,
markdownRegexp: /^(>)(\s)+$/,
triggerKey: Keyboard.keys.SPACE,
},
[BlockType.CalloutBlock]: {
type: BlockType.CalloutBlock,
markdownRegexp: /^(\[!)(TIP|INFO|WARNING|DANGER)(\])(\s)+$/,
triggerKey: Keyboard.keys.SPACE,
},
[BlockType.EquationBlock]: {
type: BlockType.EquationBlock,
markdownRegexp: /^(\${2})(\s)*(.+)(\s)*(\${2})$/,
triggerKey: Keyboard.keys.DOLLAR,
},
[BlockType.DividerBlock]: {
type: BlockType.DividerBlock,
markdownRegexp: /^(-{3,})$/,
triggerKey: Keyboard.keys.REDUCE,
},
[BlockType.CodeBlock]: {
type: BlockType.CodeBlock,
markdownRegexp: /^(```)$/,
triggerKey: Keyboard.keys.BACK_QUOTE,
},
};

View File

@ -11,7 +11,7 @@ import isHotkey from 'is-hotkey';
import { slashCommandActions } from '$app_reducers/document/slice';
import { getDeltaText } from '$app/utils/document/delta';
import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
import { turnIntoShortcuts } from './shortchut';
import { turnIntoConfig } from './shortchut';
export function useTurnIntoBlockEvents(id: string) {
const { docId, controller } = useSubscribeDocument();
@ -43,12 +43,11 @@ export function useTurnIntoBlockEvents(id: string) {
const canHandle = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>, type: BlockType) => {
{
const triggerKey = event.key;
const shortcutItem = turnIntoShortcuts[triggerKey]?.find((item) => item.type === type);
const triggerKey = event.key === turnIntoConfig[type].triggerKey ? event.key : undefined;
if (!shortcutItem) return false;
if (!triggerKey) return false;
const regex = shortcutItem.markdownRegexp;
const regex = turnIntoConfig[type].markdownRegexp;
// This error will be thrown if the block type is not in the config, and it will happen in development environment
if (!regex) {
@ -80,6 +79,20 @@ export function useTurnIntoBlockEvents(id: string) {
};
}, [getDeltaContent]);
const getAttrs = useCallback(
(type: BlockType) => {
const flag = getFlag();
if (!flag) return;
const triggerKey = turnIntoConfig[type].triggerKey;
const regex = turnIntoConfig[type].markdownRegexp;
const match = `${flag}${triggerKey}`.match(regex);
return match?.[3];
},
[getFlag]
);
const spaceTriggerMap = useMemo(() => {
return {
[BlockType.HeadingBlock]: () => {
@ -182,6 +195,19 @@ export function useTurnIntoBlockEvents(id: string) {
dispatch(turnToBlockThunk({ id, data, type: BlockType.CodeBlock, controller }));
},
},
{
canHandle: (e: React.KeyboardEvent<HTMLDivElement>) => canHandle(e, BlockType.EquationBlock),
handler: (e: React.KeyboardEvent<HTMLDivElement>) => {
e.preventDefault();
const formula = getAttrs(BlockType.EquationBlock);
const data = {
formula,
};
dispatch(turnToBlockThunk({ id, data, type: BlockType.EquationBlock, controller }));
},
},
{
// Here custom slash key event for TextBlock
canHandle: (e: React.KeyboardEvent<HTMLDivElement>) => {
@ -200,7 +226,7 @@ export function useTurnIntoBlockEvents(id: string) {
},
},
];
}, [canHandle, controller, dispatch, docId, getDeltaContent, getFlag, id, spaceTriggerMap]);
}, [canHandle, controller, dispatch, docId, getAttrs, getDeltaContent, getFlag, id, spaceTriggerMap]);
return turnIntoBlockEvents;
}

View File

@ -32,7 +32,7 @@ export default function TodoListBlock({
/>
</div>
</div>
<div className={'flex-1'}>
<div className={`flex-1 ${checked ? 'text-shade-2 line-through' : ''}`}>
<TextBlock node={node} />
</div>
</div>

View File

@ -7,7 +7,6 @@ import { useAppDispatch } from '$app/stores/store';
import { createTemporary } from '$app_reducers/document/async-actions/temporary';
import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
import KatexMath from '$app/components/document/_shared/KatexMath';
import { rangeActions } from '$app_reducers/document/slice';
const LEFT_CARET_CLASS = 'inline-block-with-cursor-left';
const RIGHT_CARET_CLASS = 'inline-block-with-cursor-right';

View File

@ -0,0 +1,4 @@
.katex-html {
white-space: normal;
}

View File

@ -1,6 +1,7 @@
import React from 'react';
import 'katex/dist/katex.min.css';
import { BlockMath, InlineMath } from 'react-katex';
import './index.css';
function KatexMath({ latex, isInline = false }: { latex: string; isInline?: boolean }) {
return isInline ? <InlineMath math={latex} /> : <BlockMath math={latex} />;

View File

@ -7,19 +7,25 @@ function EquationEditContent({
value,
onChange,
onConfirm,
placeholder = 'E = mc^2',
multiline = false,
}: {
value: string;
placeholder?: string;
onChange: (newVal: string) => void;
onConfirm: () => void;
multiline?: boolean;
}) {
return (
<div className={'flex p-2'}>
<TextField
placeholder={'E = mc^2'}
placeholder={placeholder}
autoFocus={true}
multiline={multiline}
label='Equation'
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
onConfirm();
}
}}

View File

@ -4,11 +4,43 @@ import { BlockData, BlockType, NestedBlock } from '$app/interfaces/document';
import { blockConfig } from '$app/constants/document/config';
import { turnToBlockThunk } from '$app_reducers/document/async-actions';
import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
import Delta from 'quill-delta';
import { getDeltaText } from '$app/utils/document/delta';
import { rangeActions, rectSelectionActions } from '$app_reducers/document/slice';
import { setRectSelectionThunk } from '$app_reducers/document/async-actions/rect_selection';
export function useTurnInto({ node, onClose }: { node: NestedBlock; onClose?: () => void }) {
const dispatch = useAppDispatch();
const { controller } = useSubscribeDocument();
const { controller, docId } = useSubscribeDocument();
const getTurnIntoData = useCallback(
(targetType: BlockType, sourceNode: NestedBlock) => {
if (targetType === sourceNode.type) return;
const config = blockConfig[targetType];
const defaultData = config.defaultData;
const data: BlockData<any> = {
...defaultData,
delta: sourceNode?.data?.delta || [],
};
if (targetType === BlockType.EquationBlock) {
data.formula = getDeltaText(new Delta(sourceNode.data.delta));
delete data.delta;
}
if (sourceNode.type === BlockType.EquationBlock) {
data.delta = [
{
insert: node.data.formula,
},
];
}
return data;
},
[node.data.formula]
);
const turnIntoBlock = useCallback(
async (type: BlockType, isSelected: boolean, data?: BlockData<any>) => {
@ -17,27 +49,34 @@ export function useTurnInto({ node, onClose }: { node: NestedBlock; onClose?: ()
return;
}
const config = blockConfig[type];
await dispatch(
const updateData = {
...getTurnIntoData(type, node),
...data,
};
const { payload: newBlockId } = await dispatch(
turnToBlockThunk({
id: node.id,
controller,
type,
data: {
...config.defaultData,
delta: node?.data?.delta || [],
...data,
},
data: updateData,
})
);
onClose?.();
dispatch(
setRectSelectionThunk({
docId,
selection: [newBlockId as string],
})
);
},
[onClose, controller, dispatch, node]
[controller, getTurnIntoData, node, dispatch, onClose, docId]
);
const turnIntoHeading = useCallback(
(level: number, isSelected: boolean) => {
turnIntoBlock(BlockType.HeadingBlock, isSelected, { level });
return turnIntoBlock(BlockType.HeadingBlock, isSelected, { level });
},
[turnIntoBlock]
);

View File

@ -120,11 +120,12 @@ const TurnIntoPopover = ({
title: 'Callout',
icon: <Lightbulb />,
},
// {
// type: BlockType.EquationBlock,
// title: 'Block KatexMath',
// icon: <Functions />,
// },
{
key: SlashCommandOptionKey.EQUATION,
type: BlockType.EquationBlock,
title: 'Block Equation',
icon: <Functions />,
},
],
[node?.data?.level, turnIntoHeading]
);

View File

@ -98,4 +98,10 @@ export const blockConfig: Record<string, BlockConfig> = {
[BlockType.DividerBlock]: {
canAddChild: false,
},
[BlockType.EquationBlock]: {
canAddChild: false,
defaultData: {
formula: '',
},
},
};

View File

@ -34,6 +34,9 @@ export enum BlockType {
ColumnBlock = 'column',
}
export interface EauqtionBlockData {
formula: string;
}
export interface HeadingBlockData extends TextBlockData {
level: number;
}
@ -88,6 +91,8 @@ export type BlockData<Type> = Type extends BlockType.HeadingBlock
? DividerBlockData
: Type extends BlockType.CalloutBlock
? CalloutBlockData
: Type extends BlockType.EquationBlock
? EauqtionBlockData
: Type extends BlockType.TextBlock
? TextBlockData
: any;

View File

@ -4,6 +4,7 @@ import { rectSelectionActions } from '$app_reducers/document/slice';
import { getDuplicateActions } from '$app/utils/document/action';
import { RootState } from '$app/stores/store';
import { DOCUMENT_NAME } from '$app/constants/document/name';
import { setRectSelectionThunk } from '$app_reducers/document/async-actions/rect_selection';
export const duplicateBelowNodeThunk = createAsyncThunk(
'document/duplicateBelowNode',
@ -22,7 +23,7 @@ export const duplicateBelowNodeThunk = createAsyncThunk(
await controller.applyActions(duplicateActions.actions);
dispatch(
rectSelectionActions.updateSelections({
setRectSelectionThunk({
docId,
selection: [duplicateActions.newNodeId],
})

View File

@ -1,6 +1,6 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { getNextNodeId, getPrevNodeId } from '$app/utils/document/block';
import { rectSelectionActions } from '$app_reducers/document/slice';
import { rangeActions, rectSelectionActions } from '$app_reducers/document/slice';
import { RootState } from '$app/stores/store';
export const setRectSelectionThunk = createAsyncThunk(
@ -16,19 +16,24 @@ export const setRectSelectionThunk = createAsyncThunk(
const { docId, selection } = payload;
const documentState = (getState() as RootState).document[docId];
const selected: Record<string, boolean> = {};
selection.forEach((id) => {
const node = documentState.nodes[id];
if (!node.parent) {
return;
}
selected[id] = selected[id] === undefined ? true : selected[id];
selected[node.parent] = false;
const nextNodeId = getNextNodeId(documentState, node.parent);
const prevNodeId = getPrevNodeId(documentState, node.parent);
if ((nextNodeId && selection.includes(nextNodeId)) || (prevNodeId && selection.includes(prevNodeId))) {
selected[node.parent] = true;
}
});
dispatch(rangeActions.initialState(docId));
dispatch(
rectSelectionActions.updateSelections({
docId,

View File

@ -23,6 +23,7 @@ export const turnToBlockThunk = createAsyncThunk(
const state = (getState() as RootState).document[docId];
const node = state.nodes[id];
if (!node.parent) return;
const parent = state.nodes[node.parent];
@ -31,12 +32,15 @@ export const turnToBlockThunk = createAsyncThunk(
const block = newBlock<any>(type, parent.id, type === BlockType.DividerBlock ? {} : data);
let caretId = block.id;
// insert new block after current block
let insertActions = [controller.getInsertAction(block, node.id)];
const insertActions = [controller.getInsertAction(block, node.id)];
if (type === BlockType.DividerBlock) {
const newTextNode = newBlock<any>(BlockType.TextBlock, parent.id, data);
insertActions.push(controller.getInsertAction(newTextNode, block.id));
caretId = newTextNode.id;
}
// check if prev node is allowed to have children
const config = blockConfig[block.type];
// if new block is not allowed to have children, move children to parent
@ -57,6 +61,7 @@ export const turnToBlockThunk = createAsyncThunk(
caret: { id: caretId, index: 0, length: 0 },
})
);
return caretId;
}
);

View File

@ -128,21 +128,6 @@ export const rectSelectionSlice = createSlice({
state[docId].selection = selection;
},
// set block selected
setSelectionById: (
state,
action: PayloadAction<{
docId: string;
blockId: string;
}>
) => {
const { docId, blockId } = action.payload;
const selection = state[docId].selection;
if (selection.includes(blockId)) return;
state[docId].selection = [...selection, blockId];
},
setDragging: (
state,
action: PayloadAction<{

View File

@ -5,11 +5,13 @@ import { nanoid } from 'nanoid';
export function blockPB2Node(block: BlockPB) {
let data = {};
try {
data = JSON.parse(block.data);
} catch {
Log.error('[Document Open] json parse error', block.data);
}
const node = {
id: block.id,
type: block.ty as BlockType,
@ -17,6 +19,7 @@ export function blockPB2Node(block: BlockPB) {
children: block.children_id,
data,
};
return node;
}
@ -26,58 +29,71 @@ export function generateId() {
export function getPrevLineId(state: DocumentState, id: string) {
const node = state.nodes[id];
if (!node.parent) return;
const parent = state.nodes[node.parent];
const children = state.children[parent.children];
const index = children.indexOf(id);
const prevNodeId = children[index - 1];
const prevNode = state.nodes[prevNodeId];
if (!prevNode) {
return parent.id;
}
// find prev line
let prevLineId = prevNode.id;
while (prevLineId) {
const prevLineChildren = state.children[state.nodes[prevLineId].children];
if (prevLineChildren.length === 0) break;
prevLineId = prevLineChildren[prevLineChildren.length - 1];
}
return prevLineId || parent.id;
}
export function getNextLineId(state: DocumentState, id: string) {
const node = state.nodes[id];
if (!node.parent) return;
const firstChild = state.children[node.children][0];
if (firstChild) return firstChild;
let nextNodeId = getNextNodeId(state, id);
if (!node.parent) return;
let parent: NestedBlock | null = state.nodes[node.parent];
while (!nextNodeId && parent) {
nextNodeId = getNextNodeId(state, parent.id);
parent = parent.parent ? state.nodes[parent.parent] : null;
}
return nextNodeId;
}
export function getNextNodeId(state: DocumentState, id: string) {
const node = state.nodes[id];
if (!node.parent) return;
const parent = state.nodes[node.parent];
const children = state.children[parent.children];
const index = children.indexOf(id);
const nextNodeId = children[index + 1];
return nextNodeId;
}
export function getPrevNodeId(state: DocumentState, id: string) {
const node = state.nodes[id];
if (!node.parent) return;
const parent = state.nodes[node.parent];
const children = state.children[parent.children];
const index = children.indexOf(id);
const prevNodeId = children[index - 1];
return prevNodeId;
}