From 18ed5532967c72ba21fca7aa79cadf563fcdf85b Mon Sep 17 00:00:00 2001 From: "Kilu.He" <108015703+qinluhe@users.noreply.github.com> Date: Wed, 28 Jun 2023 12:39:04 +0800 Subject: [PATCH] feat: support equation block (#2903) --- frontend/appflowy_tauri/src-tauri/Cargo.lock | 310 +++++++++--------- .../document/BlockSideToolbar/BlockMenu.tsx | 55 ++-- .../BlockSideToolbar/BlockMenuTurnInto.tsx | 25 +- .../BlockSideToolbar.hooks.tsx | 48 ++- .../document/BlockSideToolbar/index.tsx | 17 +- .../document/BlockSlash/index.hooks.ts | 19 +- .../components/document/BlockSlash/index.tsx | 9 +- .../document/EquationBlock/index.tsx | 54 +++ .../EquationBlock/useEquationBlock.ts | 71 ++++ .../components/document/Node/index.tsx | 13 + .../document/TextActionMenu/index.tsx | 39 ++- .../TextActionMenu/menu/TurnIntoSelect.tsx | 27 +- .../document/TextBlock/shortchut.ts | 131 ++++---- .../TextBlock/useTurnIntoBlockEvents.ts | 38 ++- .../document/TodoListBlock/index.tsx | 2 +- .../_shared/InlineBlock/InlineContainer.tsx | 1 - .../document/_shared/KatexMath/index.css | 4 + .../document/_shared/KatexMath/index.tsx | 1 + .../TemporaryInput/EquationEditContent.tsx | 10 +- .../_shared/TurnInto/TurnInto.hooks.ts | 59 +++- .../document/_shared/TurnInto/index.tsx | 11 +- .../appflowy_app/constants/document/config.ts | 6 + .../src/appflowy_app/interfaces/document.ts | 5 + .../async-actions/blocks/duplicate.ts | 3 +- .../document/async-actions/rect_selection.ts | 7 +- .../document/async-actions/turn_to.ts | 7 +- .../stores/reducers/document/slice.ts | 15 - .../src/appflowy_app/utils/document/block.ts | 20 +- 28 files changed, 659 insertions(+), 348 deletions(-) create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/EquationBlock/index.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/EquationBlock/useEquationBlock.ts create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/KatexMath/index.css diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index f55566af16..2c1117ab9a 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -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", diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockMenu.tsx index 6f6ed7480a..72e6aa0cb4 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockMenu.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockMenu.tsx @@ -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(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: , + key: BlockMenuOption.Delete, }, - title: 'Delete', - icon: , - key: BlockMenuOption.Delete, - }, - { - operate: () => { - return handleClick({ operate: handleDuplicate }); + { + operate: () => { + return handleClick({ operate: handleDuplicate }); + }, + title: 'Duplicate', + icon: , + key: BlockMenuOption.Duplicate, }, - title: 'Duplicate', - icon: , - 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} /> ); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockMenuTurnInto.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockMenuTurnInto.tsx index cdcdc2b213..c76f1586e1 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockMenuTurnInto.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockMenuTurnInto.tsx @@ -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(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 ( <> (null); + const [anchorPosition, setAnchorPosition] = React.useState<{ + top: number; + left: number; + }>(); const onClose = useCallback(() => { - setAnchorEl(null); + setAnchorPosition(undefined); }, []); const handleOpen = useCallback((e: React.MouseEvent) => { 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) => { + e.stopPropagation(); + }, []); return { - anchorEl, + anchorPosition, onClose, open, handleOpen, - ...origin, + anchorReference: 'anchorPosition' as const, + transformOrigin, + onMouseDown, + disableRestoreFocus: true, + disableAutoFocus: true, + disableEnforceFocus: true, }; } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/index.tsx index 50569b6413..1cce8fa8ec 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/index.tsx @@ -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) => { 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 - {showPopover && ( - - - - )} + + + ); } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSlash/index.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSlash/index.hooks.ts index c3222de2cb..bee66cd185 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSlash/index.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSlash/index.hooks.ts @@ -11,7 +11,10 @@ export function useBlockSlash() { const { docId } = useSubscribeDocument(); const { blockId, visible, slashText, hoverOption } = useSubscribeSlash(); - const [anchorEl, setAnchorEl] = React.useState(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, diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSlash/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSlash/index.tsx index 2eae8f14af..f4ee554806 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSlash/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSlash/index.tsx @@ -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 ( }) { + const { ref, value, onChange, onOpenPopover, open, anchorPosition, onConfirm, onClosePopover } = + useEquationBlock(node); + + const formula = open ? value : node.data.formula; + + return ( + <> +
+ {formula ? ( + + ) : ( + + + Add a TeX equation + + )} +
+ e.stopPropagation()} + onClose={onClosePopover} + open={open} + anchorReference={'anchorPosition'} + anchorPosition={anchorPosition} + > + + + + ); +} + +export default EquationBlock; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/EquationBlock/useEquationBlock.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/EquationBlock/useEquationBlock.ts new file mode 100644 index 0000000000..15f897c197 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/EquationBlock/useEquationBlock.ts @@ -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) { + const { controller, docId } = useSubscribeDocument(); + const id = node.id; + const dispatch = useAppDispatch(); + const formula = node.data.formula; + const ref = useRef(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, + }; +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx index a7f6bc9bcb..b67919e65b 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx @@ -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) { const { node, childIds, isSelected, ref } = useNode(id); @@ -26,38 +27,50 @@ function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes; } + case BlockType.HeadingBlock: { return ; } + case BlockType.TodoListBlock: { return ; } + case BlockType.QuoteBlock: { return ; } + case BlockType.BulletedListBlock: { return ; } + case BlockType.NumberedListBlock: { return ; } + case BlockType.ToggleListBlock: { return ; } + case BlockType.DividerBlock: { return ; } + case BlockType.CalloutBlock: { return ; } + case BlockType.CodeBlock: return ; + case BlockType.EquationBlock: + return ; default: return ; } }, [node, childIds]); const className = props.className ? ` ${props.className}` : ''; + if (!node) return null; return ( diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.tsx index 44a8581349..23c08c0a14 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.tsx @@ -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 ; }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/TurnIntoSelect.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/TurnIntoSelect.tsx index c608a490e6..9ddf09acfc 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/TurnIntoSelect.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/TurnIntoSelect.tsx @@ -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(null); + const [anchorPosition, setAnchorPosition] = React.useState<{ + top: number; + left: number; + }>(); const { node } = useSubscribeNode(id); const handleClick = useCallback((event: React.MouseEvent) => { - 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', }} /> diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/shortchut.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/shortchut.ts index ba0b40b50f..d2ad58565d 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/shortchut.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/shortchut.ts @@ -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, + }, }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/useTurnIntoBlockEvents.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/useTurnIntoBlockEvents.ts index 2ba5417808..2585343d45 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/useTurnIntoBlockEvents.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/useTurnIntoBlockEvents.ts @@ -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, 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) => canHandle(e, BlockType.EquationBlock), + handler: (e: React.KeyboardEvent) => { + 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) => { @@ -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; } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TodoListBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TodoListBlock/index.tsx index 2d1ec2cfb5..5fb331b82d 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TodoListBlock/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TodoListBlock/index.tsx @@ -32,7 +32,7 @@ export default function TodoListBlock({ /> -
+
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/InlineBlock/InlineContainer.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/InlineBlock/InlineContainer.tsx index 2b43e392d0..b3aaa9fbda 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/InlineBlock/InlineContainer.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/InlineBlock/InlineContainer.tsx @@ -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'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/KatexMath/index.css b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/KatexMath/index.css new file mode 100644 index 0000000000..d127dc343b --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/KatexMath/index.css @@ -0,0 +1,4 @@ + +.katex-html { + white-space: normal; +} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/KatexMath/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/KatexMath/index.tsx index c759ad057a..443a268061 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/KatexMath/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/KatexMath/index.tsx @@ -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 ? : ; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/EquationEditContent.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/EquationEditContent.tsx index ce5eee7143..a9ced76a14 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/EquationEditContent.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/EquationEditContent.tsx @@ -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 (
{ - if (e.key === 'Enter') { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); onConfirm(); } }} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TurnInto/TurnInto.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TurnInto/TurnInto.hooks.ts index 44b81d64cd..7d971217b6 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TurnInto/TurnInto.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TurnInto/TurnInto.hooks.ts @@ -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 = { + ...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) => { @@ -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] ); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TurnInto/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TurnInto/index.tsx index 96e3a85158..008dad9ad3 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TurnInto/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TurnInto/index.tsx @@ -120,11 +120,12 @@ const TurnIntoPopover = ({ title: 'Callout', icon: , }, - // { - // type: BlockType.EquationBlock, - // title: 'Block KatexMath', - // icon: , - // }, + { + key: SlashCommandOptionKey.EQUATION, + type: BlockType.EquationBlock, + title: 'Block Equation', + icon: , + }, ], [node?.data?.level, turnIntoHeading] ); diff --git a/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts b/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts index 97708293c7..71721f11c8 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts @@ -98,4 +98,10 @@ export const blockConfig: Record = { [BlockType.DividerBlock]: { canAddChild: false, }, + [BlockType.EquationBlock]: { + canAddChild: false, + defaultData: { + formula: '', + }, + }, }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts b/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts index d7f03df6a8..d03bb08896 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts @@ -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 extends BlockType.HeadingBlock ? DividerBlockData : Type extends BlockType.CalloutBlock ? CalloutBlockData + : Type extends BlockType.EquationBlock + ? EauqtionBlockData : Type extends BlockType.TextBlock ? TextBlockData : any; diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/duplicate.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/duplicate.ts index 2af62fe738..103b8df19a 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/duplicate.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/duplicate.ts @@ -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], }) diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/rect_selection.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/rect_selection.ts index ed9a30fb6d..dc5eee32db 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/rect_selection.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/rect_selection.ts @@ -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 = {}; + 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, diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/turn_to.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/turn_to.ts index 8526c5e523..8f1a17cc34 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/turn_to.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/turn_to.ts @@ -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(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(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; } ); diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/slice.ts index 0e01b9da9f..96cece63e5 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/slice.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/slice.ts @@ -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<{ diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/block.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/block.ts index 379ff44acd..c15e60da26 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/document/block.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/block.ts @@ -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; }