From f23c6098a7e0b2b9580a2f8d8d6a4936e3f27f56 Mon Sep 17 00:00:00 2001 From: "Kilu.He" <108015703+qinluhe@users.noreply.github.com> Date: Fri, 19 May 2023 14:56:43 +0800 Subject: [PATCH] Support to show text action toolbar when the selection exists and the range is not collapsed (#2525) * feat: support text action menu * fix: selection bugs * fix: review suggestions * fix: ci tsc failed --- frontend/appflowy_tauri/src-tauri/Cargo.lock | 655 ++++++++++-------- .../BlockHorizontalToolbar/FormatButton.tsx | 32 - .../BlockHorizontalToolbar/index.hooks.ts | 33 - .../document/BlockHorizontalToolbar/index.tsx | 30 - .../BlockRangeSelection.hooks.ts | 104 +++ ....hooks.tsx => BlockRectSelection.hooks.ts} | 43 +- .../BlockSelection/BlockRectSelection.tsx | 13 + .../document/BlockSelection/index.tsx | 21 +- .../document/BlockSideToolbar/index.tsx | 6 +- .../document/CalloutBlock/index.tsx | 8 +- .../document/CodeBlock/elements.tsx | 10 +- .../components/document/CodeBlock/index.tsx | 8 +- .../components/document/Overlay/index.tsx | 9 +- .../components/document/Root/index.tsx | 17 +- .../document/TextActionMenu/index.hooks.ts | 43 ++ .../document/TextActionMenu/index.tsx | 46 ++ .../TextActionMenu/menu/FormatButton.tsx | 68 ++ .../menu}/FormatIcon.tsx | 2 +- .../TextActionMenu/menu/MenuTooltip.tsx | 20 + .../TextActionMenu/menu/TurnIntoSelect.tsx | 50 ++ .../TextActionMenu/menu/index.hooks.ts | 47 ++ .../document/TextActionMenu/menu/index.tsx | 39 ++ .../components/document/TextBlock/Leaf.tsx | 24 +- .../document/TextBlock/events/Events.hooks.ts | 21 +- .../components/document/TextBlock/index.tsx | 4 +- .../document/_shared/SubscribeNode.hooks.ts | 11 +- .../document/_shared/Text/TextEvents.hooks.ts | 6 +- .../document/_shared/Text/TextInput.hooks.ts | 54 +- .../_shared/Text/TextSelection.hooks.ts | 91 +-- .../_shared/TurnInto/TurnInto.hooks.ts | 49 ++ .../document/_shared/TurnInto/index.tsx | 138 ++++ .../appflowy_app/constants/document/config.ts | 83 ++- .../constants/document/toolbar.ts | 25 - .../src/appflowy_app/interfaces/document.ts | 81 ++- .../async-actions/blocks/text/backspace.ts | 6 +- .../async-actions/blocks/text/split.ts | 6 +- .../reducers/document/async-actions/format.ts | 89 +++ .../document/async-actions/range_selection.ts | 65 +- .../stores/reducers/document/slice.ts | 48 +- .../utils/document/blocks/code/decorate.ts | 2 +- .../utils/document/blocks/common.ts | 65 +- .../utils/document/blocks/selection.ts | 22 + .../utils/document/blocks/text/delta.ts | 85 ++- .../utils/document/blocks/text/format.ts | 25 - .../utils/document/blocks/text/hotkey.ts | 11 - .../utils/document/blocks/text/toolbar.ts | 27 - .../appflowy_app/utils/document/toolbar.ts | 19 + .../src/appflowy_app/views/DocumentPage.tsx | 10 +- .../src/services/backend/index.ts | 2 + .../appflowy_tauri/src/styles/template.css | 1 - 50 files changed, 1607 insertions(+), 767 deletions(-) delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatButton.tsx delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.hooks.ts delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockRangeSelection.hooks.ts rename frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/{BlockSelection.hooks.tsx => BlockRectSelection.hooks.ts} (75%) create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockRectSelection.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.hooks.ts create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatButton.tsx rename frontend/appflowy_tauri/src/appflowy_app/components/document/{BlockHorizontalToolbar => TextActionMenu/menu}/FormatIcon.tsx (91%) create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/MenuTooltip.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/TurnIntoSelect.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/index.hooks.ts create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/index.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TurnInto/TurnInto.hooks.ts create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TurnInto/index.tsx delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/constants/document/toolbar.ts create mode 100644 frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/format.ts create mode 100644 frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/selection.ts delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/format.ts delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/toolbar.ts create mode 100644 frontend/appflowy_tauri/src/appflowy_app/utils/document/toolbar.ts diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index b045b28fc4..5dd9075f87 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "appflowy-integrate" @@ -159,7 +159,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -170,7 +170,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -194,14 +194,14 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] name = "atomic_refcell" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "857253367827bd9d0fd973f0ef15506a96e79e41b0ad7aa691203a4e3214f6c8" +checksum = "79d6dc922a2792b006573f60b2648076355daeae5ce9cb59507e5908c9625d31" [[package]] name = "autocfg" @@ -232,7 +232,7 @@ dependencies = [ "http", "hyper", "ring", - "time 0.3.20", + "time 0.3.15", "tokio", "tower", "tracing", @@ -392,7 +392,7 @@ dependencies = [ "percent-encoding", "regex", "sha2", - "time 0.3.20", + "time 0.3.15", "tracing", ] @@ -499,7 +499,7 @@ dependencies = [ "itoa 1.0.6", "num-integer", "ryu", - "time 0.3.20", + "time 0.3.15", ] [[package]] @@ -614,6 +614,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block" version = "0.1.6" @@ -716,15 +728,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.1" +version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" [[package]] name = "bytecheck" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13fe11640a23eb24562225322cd3e452b93a3d4091d62fab69c70542fcd17d1f" +checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -733,9 +745,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31225543cb46f81a7e224762764f4a6a0f097b1db0b175f69e8065efaa42de5" +checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" dependencies = [ "proc-macro2", "quote", @@ -805,17 +817,17 @@ checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] name = "cargo_toml" -version = "0.13.3" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497049e9477329f8f6a559972ee42e117487d01d1e8c2cc9f836ea6fa23a9e1a" +checksum = "7f83bc2e401ed041b7057345ebc488c005efa0341d5541ce7004d30458d0090b" dependencies = [ "serde", - "toml 0.5.11", + "toml 0.7.3", ] [[package]] @@ -844,12 +856,13 @@ dependencies = [ [[package]] name = "cfb" -version = "0.6.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f89d248799e3f15f91b70917f65381062a01bb8e222700ea0e5a7ff9785f9c" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ "byteorder", - "uuid 0.8.2", + "fnv", + "uuid", ] [[package]] @@ -1004,16 +1017,6 @@ dependencies = [ "objc", ] -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "collab" version = "0.1.0" @@ -1211,7 +1214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" dependencies = [ "lazy_static", - "nom 5.1.2", + "nom 5.1.3", "serde", "yaml-rust", ] @@ -1387,54 +1390,16 @@ dependencies = [ ] [[package]] -name = "cxx" -version = "1.0.94" +name = "cty" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.15", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] name = "darling" -version = "0.13.4" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" dependencies = [ "darling_core", "darling_macro", @@ -1442,27 +1407,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.109", + "syn 2.0.16", ] [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.16", ] [[package]] @@ -1620,6 +1585,19 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "embed-resource" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80663502655af01a2902dff3f06869330782267924bf1788410b74edcd93770a" +dependencies = [ + "cc", + "rustc_version", + "toml 0.7.3", + "vswhom", + "winreg 0.11.0", +] + [[package]] name = "embed_plist" version = "1.2.2" @@ -1744,12 +1722,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", - "miniz_oxide 0.6.2", + "miniz_oxide 0.7.1", ] [[package]] @@ -1785,12 +1763,28 @@ dependencies = [ "walkdir", ] +[[package]] +name = "flowy-config" +version = "0.1.0" +dependencies = [ + "appflowy-integrate", + "bytes", + "flowy-codegen", + "flowy-derive", + "flowy-error", + "flowy-sqlite", + "lib-dispatch", + "protobuf", + "strum_macros", +] + [[package]] name = "flowy-core" version = "0.1.0" dependencies = [ "appflowy-integrate", "bytes", + "flowy-config", "flowy-database2", "flowy-document2", "flowy-error", @@ -1992,14 +1986,18 @@ dependencies = [ name = "flowy-sqlite" version = "0.1.0" dependencies = [ + "anyhow", "diesel", "diesel_derives", "diesel_migrations", "error-chain", "lazy_static", "libsqlite3-sys", + "parking_lot 0.12.1", "r2d2", "scheduled-thread-pool", + "serde", + "serde_json", "tracing", ] @@ -2085,6 +2083,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futf" version = "0.1.5" @@ -2151,7 +2155,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -2232,7 +2236,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -2249,7 +2253,21 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps 6.0.5", + "system-deps 6.1.0", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca49a59ad8cfdf36ef7330fe7bdfbe1d34323220cc16a0de2679ee773aee2c2" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps 6.1.0", ] [[package]] @@ -2261,7 +2279,7 @@ dependencies = [ "gdk-sys", "glib-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", "x11", ] @@ -2354,7 +2372,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", "winapi", ] @@ -2400,7 +2418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" dependencies = [ "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -2441,7 +2459,7 @@ checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -2482,7 +2500,7 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -2501,9 +2519,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -2722,19 +2740,18 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] name = "ico" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031530fe562d8c8d71c0635013d6d155bbfe8ba0aa4b4d2d24ce8af6b71047bd" +checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" dependencies = [ "byteorder", "png", @@ -2811,9 +2828,9 @@ dependencies = [ [[package]] name = "infer" -version = "0.7.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20b2b533137b9cad970793453d4f921c2e91312a6d88b1085c07bc15fc51bb3b" +checksum = "a898e4b7951673fce96614ce5751d13c40fc5674bc2d759288e46c3ab62598b3" dependencies = [ "cfb", ] @@ -2919,21 +2936,22 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] [[package]] name = "json-patch" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3fa5a61630976fc4c353c70297f2e93f1930e3ccee574d59d618ccbd5154ce" +checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658" dependencies = [ "serde", "serde_json", + "thiserror", "treediff", ] @@ -3067,9 +3085,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.142" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libloading" @@ -3083,9 +3101,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "librocksdb-sys" @@ -3115,9 +3133,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" dependencies = [ "cc", "pkg-config", @@ -3133,15 +3151,6 @@ dependencies = [ "safemem", ] -[[package]] -name = "link-cplusplus" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -3150,9 +3159,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.3.3" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" [[package]] name = "lock_api" @@ -3404,9 +3413,9 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] name = "nom" -version = "5.1.2" +version = "5.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" dependencies = [ "lexical-core", "memchr", @@ -3494,6 +3503,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "objc" version = "0.2.7" @@ -3555,9 +3573,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.51" +version = "0.10.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ea2d98598bf9ada7ea6ee8a30fb74f9156b63bbe495d64ec2b87c269d2dda3" +checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" dependencies = [ "bitflags", "cfg-if", @@ -3576,7 +3594,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -3587,9 +3605,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.86" +version = "0.9.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "992bac49bdbab4423199c654a5515bd2a6c6a23bf03f2dd3bdb7e5ae6259bc69" +checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" dependencies = [ "cc", "libc", @@ -3641,7 +3659,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -3701,12 +3719,6 @@ dependencies = [ "regex", ] -[[package]] -name = "paste" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" - [[package]] name = "pathdiff" version = "0.2.1" @@ -3727,9 +3739,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1403e8401ad5dedea73c626b99758535b342502f8d1e361f4a2dd952749122" +checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" dependencies = [ "thiserror", "ucd-trie", @@ -3737,9 +3749,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be99c4c1d2fc2769b1d00239431d711d08f6efedcecb8b6e30707160aee99c15" +checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" dependencies = [ "pest", "pest_generator", @@ -3747,22 +3759,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56094789873daa36164de2e822b3888c6ae4b4f9da555a1103587658c805b1e" +checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] name = "pest_meta" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6733073c7cff3d8459fda0e42f13a047870242aed8b509fe98000928975f359e" +checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" dependencies = [ "once_cell", "pest", @@ -3918,22 +3930,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.16", ] [[package]] @@ -3950,9 +3962,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" @@ -3965,7 +3977,7 @@ dependencies = [ "line-wrap", "quick-xml", "serde", - "time 0.3.20", + "time 0.3.15", ] [[package]] @@ -4044,9 +4056,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" dependencies = [ "unicode-ident", ] @@ -4169,9 +4181,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -4187,6 +4199,12 @@ dependencies = [ "scheduled-thread-pool", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -4270,9 +4288,12 @@ dependencies = [ [[package]] name = "raw-window-handle" -version = "0.5.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" +dependencies = [ + "cty", +] [[package]] name = "rayon" @@ -4374,9 +4395,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.16" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ "base64 0.21.0", "bytes", @@ -4406,7 +4427,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "winreg 0.10.1", ] [[package]] @@ -4426,23 +4447,26 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.41" +version = "0.7.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21499ed91807f07ae081880aabb2ccc0235e9d88011867d984525e9a4c3cfa3e" +checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" dependencies = [ + "bitvec", "bytecheck", "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", "seahash", + "tinyvec", + "uuid", ] [[package]] name = "rkyv_derive" -version = "0.7.41" +version = "0.7.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1c672430eb41556291981f45ca900a0239ad007242d1cb4b4167af842db666" +checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" dependencies = [ "proc-macro2", "quote", @@ -4528,9 +4552,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.13" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags", "errno", @@ -4640,12 +4664,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "scratch" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" - [[package]] name = "sct" version = "0.7.0" @@ -4664,9 +4682,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "ca2855b3715770894e67cbfa3df957790aa0c9edc3bf06efa1a84d77fa0839d1" dependencies = [ "bitflags", "core-foundation", @@ -4677,9 +4695,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" dependencies = [ "core-foundation-sys", "libc", @@ -4716,9 +4734,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] @@ -4736,13 +4754,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -4764,7 +4782,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -4790,24 +4808,30 @@ dependencies = [ [[package]] name = "serde_with" -version = "1.14.0" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" dependencies = [ + "base64 0.13.1", + "chrono", + "hex", + "indexmap", "serde", + "serde_json", "serde_with_macros", + "time 0.3.15", ] [[package]] name = "serde_with_macros" -version = "1.5.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.16", ] [[package]] @@ -5108,9 +5132,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" dependencies = [ "proc-macro2", "quote", @@ -5132,9 +5156,9 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.0.5" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fe581ad25d11420b873cf9aedaca0419c2b411487b134d4d21065f3d092055" +checksum = "e5fa6fb9ee296c0dc2df41a656ca7948546d061958115ddb0bcaae43ad0d17d2" dependencies = [ "cfg-expr 0.15.1", "heck 0.4.1", @@ -5145,9 +5169,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.15.8" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8e6399427c8494f9849b58694754d7cc741293348a6836b6c8d2c5aa82d8e6" +checksum = "dd3cde9c0cd2b872616bba26b818e0d6469330196869d7e5000dba96ce9431df" dependencies = [ "bitflags", "cairo-rs", @@ -5160,6 +5184,7 @@ dependencies = [ "gdk", "gdk-pixbuf", "gdk-sys", + "gdkwayland-sys", "gdkx11-sys", "gio", "glib", @@ -5177,18 +5202,35 @@ dependencies = [ "objc", "once_cell", "parking_lot 0.12.1", - "paste", "png", "raw-window-handle", "scopeguard", "serde", + "tao-macros", "unicode-segmentation", - "uuid 1.3.1", + "uuid", "windows 0.39.0", "windows-implement", "x11-dl", ] +[[package]] +name = "tao-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b27a4bcc5eb524658234589bdffc7e7bfb996dbae6ce9393bfd39cb4159b445" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.38" @@ -5202,15 +5244,15 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.6" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5" +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" [[package]] name = "tauri" -version = "1.2.4" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7e0f1d535e7cbbbab43c82be4fc992b84f9156c16c160955617e0260ebc449" +checksum = "d42ba3a2e8556722f31336a0750c10dbb6a81396a1c452977f515da83f69f842" dependencies = [ "anyhow", "cocoa", @@ -5247,7 +5289,7 @@ dependencies = [ "thiserror", "tokio", "url", - "uuid 1.3.1", + "uuid", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -5255,27 +5297,29 @@ dependencies = [ [[package]] name = "tauri-build" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8807c85d656b2b93927c19fe5a5f1f1f348f96c2de8b90763b3c2d561511f9b4" +checksum = "929b3bd1248afc07b63e33a6a53c3f82c32d0b0a5e216e4530e94c467e019389" dependencies = [ "anyhow", "cargo_toml", "heck 0.4.1", "json-patch", "semver", + "serde", "serde_json", "tauri-utils", - "winres", + "tauri-winres", + "winnow", ] [[package]] name = "tauri-codegen" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14388d484b6b1b5dc0f6a7d6cc6433b3b230bec85eaa576adcdf3f9fafa49251" +checksum = "e5a2105f807c6f50b2fa2ce5abd62ef207bc6f14c9fcc6b8caec437f6fb13bde" dependencies = [ - "base64 0.13.1", + "base64 0.21.0", "brotli", "ico", "json-patch", @@ -5290,16 +5334,16 @@ dependencies = [ "sha2", "tauri-utils", "thiserror", - "time 0.3.20", - "uuid 1.3.1", + "time 0.3.15", + "uuid", "walkdir", ] [[package]] name = "tauri-macros" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069319e5ecbe653a799b94b0690d9f9bf5d00f7b1d3989aa331c524d4e354075" +checksum = "8784cfe6f5444097e93c69107d1ac5e8f13d02850efa8d8f2a40fe79674cef46" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -5311,9 +5355,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c507d954d08ac8705d235bc70ec6975b9054fb95ff7823af72dbb04186596f3b" +checksum = "b3b80ea3fcd5fefb60739a3b577b277e8fc30434538a2f5bba82ad7d4368c422" dependencies = [ "gtk", "http", @@ -5324,16 +5368,17 @@ dependencies = [ "serde_json", "tauri-utils", "thiserror", - "uuid 1.3.1", + "url", + "uuid", "webview2-com", "windows 0.39.0", ] [[package]] name = "tauri-runtime-wry" -version = "0.12.2" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36b1c5764a41a13176a4599b5b7bd0881bea7d94dfe45e1e755f789b98317e30" +checksum = "d1c396950b1ba06aee1b4ffe6c7cd305ff433ca0e30acbc5fa1a2f92a4ce70f1" dependencies = [ "cocoa", "gtk", @@ -5342,7 +5387,7 @@ dependencies = [ "raw-window-handle", "tauri-runtime", "tauri-utils", - "uuid 1.3.1", + "uuid", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -5351,9 +5396,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5abbc109a6eb45127956ffcc26ef0e875d160150ac16cfa45d26a6b2871686f1" +checksum = "5a6f9c2dafef5cbcf52926af57ce9561bd33bb41d7394f8bb849c0330260d864" dependencies = [ "brotli", "ctor", @@ -5377,6 +5422,16 @@ dependencies = [ "windows 0.39.0", ] +[[package]] +name = "tauri-winres" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" +dependencies = [ + "embed-resource", + "toml 0.7.3", +] + [[package]] name = "tempfile" version = "3.5.0" @@ -5424,15 +5479,6 @@ dependencies = [ "unic-segment", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "terminal_size" version = "0.1.17" @@ -5466,7 +5512,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -5502,29 +5548,14 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" dependencies = [ "itoa 1.0.6", + "libc", + "num_threads", "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" - -[[package]] -name = "time-macros" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" -dependencies = [ - "time-core", ] [[package]] @@ -5544,9 +5575,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" dependencies = [ "autocfg", "bytes", @@ -5558,18 +5589,18 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -5643,9 +5674,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -5752,13 +5783,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.16", ] [[package]] @@ -5780,9 +5811,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -5851,9 +5882,9 @@ dependencies = [ [[package]] name = "treediff" -version = "3.0.2" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" +checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" dependencies = [ "serde_json", ] @@ -5916,9 +5947,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uncased" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" dependencies = [ "version_check", ] @@ -6038,15 +6069,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" -version = "0.8.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - -[[package]] -name = "uuid" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb" +checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ "getrandom 0.2.9", ] @@ -6102,6 +6127,26 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "walkdir" version = "2.3.3" @@ -6142,9 +6187,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -6152,24 +6197,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.16", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -6179,9 +6224,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6189,28 +6234,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.16", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -6260,7 +6305,7 @@ dependencies = [ "pango-sys", "pkg-config", "soup2-sys", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -6604,19 +6649,20 @@ dependencies = [ ] [[package]] -name = "winres" -version = "0.1.12" +name = "winreg" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" +checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" dependencies = [ - "toml 0.5.11", + "cfg-if", + "winapi", ] [[package]] name = "wry" -version = "0.23.4" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c1ad8e2424f554cc5bdebe8aa374ef5b433feff817aebabca0389961fc7ef98" +checksum = "33748f35413c8a98d45f7a08832d848c0c5915501803d1faade5a4ebcd258cea" dependencies = [ "base64 0.13.1", "block", @@ -6650,6 +6696,15 @@ dependencies = [ "windows-implement", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x11" version = "2.21.0" diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatButton.tsx deleted file mode 100644 index 0c0d44416c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatButton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { toggleFormat, isFormatActive } from '$app/utils/document/blocks/text/format'; -import IconButton from '@mui/material/IconButton'; -import Tooltip from '@mui/material/Tooltip'; - -import { command } from '$app/constants/document/toolbar'; -import FormatIcon from './FormatIcon'; -import { BaseEditor } from 'slate'; - -const FormatButton = ({ editor, format, icon }: { editor: BaseEditor; format: string; icon: string }) => { - return ( - - {command[format].title} - {command[format].key} - - } - placement='top-start' - > - toggleFormat(editor, format)} - > - - - - ); -}; - -export default FormatButton; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.hooks.ts deleted file mode 100644 index 5510f7a77f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.hooks.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { useEffect, useRef } from 'react'; -import { useFocused, useSlate } from 'slate-react'; -import { calcToolbarPosition } from '$app/utils/document/blocks/text/toolbar'; -export function useHoveringToolbar(id: string) { - const editor = useSlate(); - const inFocus = useFocused(); - const ref = useRef(null); - - useEffect(() => { - const el = ref.current; - if (!el) return; - const nodeRect = document.querySelector(`[data-block-id="${id}"]`)?.getBoundingClientRect(); - - if (!nodeRect) return; - const position = calcToolbarPosition(editor, el, nodeRect); - - if (!position) { - el.style.opacity = '0'; - el.style.pointerEvents = 'none'; - } else { - el.style.opacity = '1'; - el.style.pointerEvents = 'auto'; - el.style.top = position.top; - el.style.left = position.left; - } - }); - - return { - ref, - inFocus, - editor, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.tsx deleted file mode 100644 index 1ad8dc025e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import FormatButton from './FormatButton'; -import Portal from '../BlockPortal'; -import { useHoveringToolbar } from './index.hooks'; - -const BlockHorizontalToolbar = ({ id }: { id: string }) => { - const { inFocus, ref, editor } = useHoveringToolbar(id); - if (!inFocus) return null; - - return ( - -
{ - // prevent toolbar from taking focus away from editor - e.preventDefault(); - }} - > - {['bold', 'italic', 'underlined', 'strikethrough', 'code'].map((format) => ( - - ))} -
-
- ); -}; - -export default BlockHorizontalToolbar; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockRangeSelection.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockRangeSelection.hooks.ts new file mode 100644 index 0000000000..a0861acf5c --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockRangeSelection.hooks.ts @@ -0,0 +1,104 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { getBlockIdByPoint } from '$app/utils/document/blocks/selection'; +import { rangeSelectionActions } from '$app_reducers/document/slice'; +import { useAppDispatch } from '$app/stores/store'; +import { getNodesInRange } from '$app/utils/document/blocks/common'; +import { setRangeSelectionThunk } from '$app_reducers/document/async-actions/range_selection'; + +export function useBlockRangeSelection(container: HTMLDivElement) { + const dispatch = useAppDispatch(); + const anchorRef = useRef<{ + id: string; + point: { x: number; y: number }; + range?: Range; + } | null>(null); + + const [isDragging, setDragging] = useState(false); + + const reset = useCallback(() => { + dispatch(rangeSelectionActions.clearRange()); + }, [dispatch]); + + useEffect(() => { + dispatch(rangeSelectionActions.setDragging(isDragging)); + }, [dispatch, isDragging]); + + const handleDragStart = useCallback( + (e: MouseEvent) => { + reset(); + const blockId = getBlockIdByPoint(e.target as HTMLElement); + if (!blockId) { + return; + } + + const startX = e.clientX + container.scrollLeft; + const startY = e.clientY + container.scrollTop; + anchorRef.current = { + id: blockId, + point: { + x: startX, + y: startY, + }, + }; + setDragging(true); + }, + [container.scrollLeft, container.scrollTop, reset] + ); + + const handleDraging = useCallback( + (e: MouseEvent) => { + if (!isDragging || !anchorRef.current) return; + + const blockId = getBlockIdByPoint(e.target as HTMLElement); + if (!blockId) { + return; + } + + const anchorId = anchorRef.current.id; + if (anchorId === blockId) { + const endX = e.clientX + container.scrollTop; + const isForward = endX > anchorRef.current.point.x; + dispatch(rangeSelectionActions.setForward(isForward)); + return; + } + + const endY = e.clientY + container.scrollTop; + const isForward = endY > anchorRef.current.point.y; + dispatch(rangeSelectionActions.setForward(isForward)); + }, + [container.scrollTop, dispatch, isDragging] + ); + + const handleDragEnd = useCallback(() => { + if (!isDragging) return; + setDragging(false); + dispatch(setRangeSelectionThunk()); + }, [dispatch, isDragging]); + + // TODO: This is a hack to fix the issue that the selection is lost when scrolling + const handleScroll = useCallback(() => { + if (isDragging || !anchorRef.current) return; + const selection = window.getSelection(); + if (!selection?.rangeCount && anchorRef.current.range) { + selection?.addRange(anchorRef.current.range); + } else { + anchorRef.current.range = selection?.getRangeAt(0); + } + }, [isDragging]); + + useEffect(() => { + document.addEventListener('mousedown', handleDragStart); + document.addEventListener('mousemove', handleDraging, true); + document.addEventListener('mouseup', handleDragEnd); + container.addEventListener('scroll', handleScroll); + + return () => { + document.removeEventListener('mousedown', handleDragStart); + document.removeEventListener('mousemove', handleDraging, true); + document.removeEventListener('mouseup', handleDragEnd); + container.removeEventListener('scroll', handleScroll); + }; + }, [handleDragStart, handleDragEnd, handleDraging, container, handleScroll]); + + return null; +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockSelection.hooks.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockRectSelection.hooks.ts similarity index 75% rename from frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockSelection.hooks.tsx rename to frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockRectSelection.hooks.ts index 359b5763bf..370e57a3f3 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockSelection.hooks.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockRectSelection.hooks.ts @@ -1,18 +1,12 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useAppDispatch } from '$app/stores/store'; -import { rectSelectionActions } from "@/appflowy_app/stores/reducers/document/slice"; +import { rectSelectionActions } from '@/appflowy_app/stores/reducers/document/slice'; import { useNodesRect } from '$app/components/document/BlockSelection/NodesRect.hooks'; -import { setRectSelectionThunk } from "$app_reducers/document/async-actions/rect_selection"; +import { setRectSelectionThunk } from '$app_reducers/document/async-actions/rect_selection'; +import { isPointInBlock } from '$app/utils/document/blocks/selection'; -export function useBlockSelection({ - container, - onDragging, -}: { - container: HTMLDivElement; - onDragging?: (_isDragging: boolean) => void; -}) { - const ref = useRef(null); - const disaptch = useAppDispatch(); +export function useBlockRectSelection({ container }: { container: HTMLDivElement }) { + const dispatch = useAppDispatch(); const [isDragging, setDragging] = useState(false); const startPointRef = useRef([]); @@ -20,8 +14,8 @@ export function useBlockSelection({ const { getIntersectedBlockIds } = useNodesRect(container); useEffect(() => { - onDragging?.(isDragging); - }, [isDragging, onDragging]); + dispatch(rectSelectionActions.setDragging(isDragging)); + }, [dispatch, isDragging]); const [rect, setRect] = useState<{ startX: number; @@ -45,17 +39,6 @@ export function useBlockSelection({ }; }, [container.scrollLeft, container.scrollTop, rect]); - const isPointInBlock = useCallback((target: HTMLElement | null) => { - let node = target; - while (node) { - if (node.getAttribute('data-block-id')) { - return true; - } - node = node.parentElement; - } - return false; - }, []); - const handleDragStart = useCallback( (e: MouseEvent) => { if (isPointInBlock(e.target as HTMLElement)) { @@ -74,7 +57,7 @@ export function useBlockSelection({ endY: startY, }); }, - [container.scrollLeft, container.scrollTop, isPointInBlock] + [container.scrollLeft, container.scrollTop] ); const updateSelctionsByPoint = useCallback( @@ -92,9 +75,9 @@ export function useBlockSelection({ }; const blockIds = getIntersectedBlockIds(newRect); setRect(newRect); - disaptch(setRectSelectionThunk(blockIds)); + dispatch(setRectSelectionThunk(blockIds)); }, - [container.scrollLeft, container.scrollTop, disaptch, getIntersectedBlockIds, isDragging] + [container.scrollLeft, container.scrollTop, dispatch, getIntersectedBlockIds, isDragging] ); const handleDraging = useCallback( @@ -119,7 +102,7 @@ export function useBlockSelection({ const handleDragEnd = useCallback( (e: MouseEvent) => { if (isPointInBlock(e.target as HTMLElement) && !isDragging) { - disaptch(rectSelectionActions.updateSelections([])); + dispatch(rectSelectionActions.updateSelections([])); return; } if (!isDragging) return; @@ -128,11 +111,10 @@ export function useBlockSelection({ setDragging(false); setRect(null); }, - [disaptch, isDragging, isPointInBlock, updateSelctionsByPoint] + [dispatch, isDragging, updateSelctionsByPoint] ); useEffect(() => { - if (!ref.current) return; document.addEventListener('mousedown', handleDragStart); document.addEventListener('mousemove', handleDraging); document.addEventListener('mouseup', handleDragEnd); @@ -147,6 +129,5 @@ export function useBlockSelection({ return { isDragging, style, - ref, }; } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockRectSelection.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockRectSelection.tsx new file mode 100644 index 0000000000..8d6b0c9fd4 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/BlockRectSelection.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { useBlockRectSelection } from '$app/components/document/BlockSelection/BlockRectSelection.hooks'; + +function BlockRectSelection({ container }: { container: HTMLDivElement }) { + const { isDragging, style } = useBlockRectSelection({ + container, + }); + + if (!isDragging) return null; + return
; +} + +export default BlockRectSelection; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/index.tsx index 0a3ac62a84..b311918d22 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSelection/index.tsx @@ -1,21 +1,12 @@ -import { useBlockSelection } from './BlockSelection.hooks'; import React from 'react'; +import BlockRectSelection from '$app/components/document/BlockSelection/BlockRectSelection'; +import { useBlockRangeSelection } from '$app/components/document/BlockSelection/BlockRangeSelection.hooks'; -function BlockSelection({ - container, - onDragging, -}: { - container: HTMLDivElement; - onDragging?: (_isDragging: boolean) => void; -}) { - const { isDragging, style, ref } = useBlockSelection({ - container, - onDragging, - }); - +function BlockSelection({ container }: { container: HTMLDivElement }) { + useBlockRangeSelection(container); return ( -
- {isDragging ?
: null} +
+
); } 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 589c5e8cb9..7142ec8baf 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 @@ -5,13 +5,17 @@ import DragIndicatorRoundedIcon from '@mui/icons-material/DragIndicatorRounded'; import Portal from '../BlockPortal'; import { IconButton } from '@mui/material'; import BlockMenu from '../BlockMenu'; +import { useAppSelector } from '$app/stores/store'; const sx = { height: 24, width: 24 }; export default function BlockSideToolbar(props: { container: HTMLDivElement }) { const { nodeId, style, ref, menuOpen, handleToggleMenu } = useBlockSideToolbar(props); + const isDragging = useAppSelector( + (state) => state.documentRangeSelection.isDragging || state.documentRectSelection.isDragging + ); - if (!nodeId) return null; + if (!nodeId || isDragging) return null; return ( <> diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/CalloutBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/CalloutBlock/index.tsx index feaa568e35..7d37b05e21 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/CalloutBlock/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/CalloutBlock/index.tsx @@ -1,10 +1,11 @@ import { BlockType, NestedBlock } from '$app/interfaces/document'; import TextBlock from '$app/components/document/TextBlock'; import NodeChildren from '$app/components/document/Node/NodeChildren'; -import { IconButton, Popover } from '@mui/material'; +import { IconButton } from '@mui/material'; import emojiData from '@emoji-mart/data'; import Picker from '@emoji-mart/react'; import { useCalloutBlock } from '$app/components/document/CalloutBlock/CalloutBlock.hooks'; +import Popover from '@mui/material/Popover'; export default function CalloutBlock({ node, @@ -17,7 +18,7 @@ export default function CalloutBlock({ return (
-
+
e.stopPropagation()}>
{ newChildren = {newChildren}; } + const className = [ + 'token', + leaf.prism_token && leaf.prism_token, + leaf.strikethrough && 'line-through', + leaf.selectionHighlighted && 'bg-main-secondary', + ].filter(Boolean); + return ( - + {newChildren} ); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/CodeBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/CodeBlock/index.tsx index f94d2f3873..fb89a5c9f3 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/CodeBlock/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/CodeBlock/index.tsx @@ -1,7 +1,6 @@ import { BlockType, NestedBlock } from '$app/interfaces/document'; import { useCodeBlock } from './CodeBlock.hooks'; import { Editable, Slate } from 'slate-react'; -import BlockHorizontalToolbar from '$app/components/document/BlockHorizontalToolbar'; import React from 'react'; import { CodeLeaf, CodeBlockElement } from './elements'; import SelectLanguage from './SelectLanguage'; @@ -23,10 +22,13 @@ export default function CodeBlock({
- decorateCodeFunc(entry, language)} + decorate={(entry) => { + const codeRange = decorateCodeFunc(entry, language); + const range = rest.decorate(entry); + return [...range, ...codeRange]; + }} renderLeaf={CodeLeaf} renderElement={CodeBlockElement} placeholder={placeholder || 'Please enter some text...'} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/Overlay/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/Overlay/index.tsx index 304c09027b..d1a08c7071 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/Overlay/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/Overlay/index.tsx @@ -1,13 +1,14 @@ -import React, { useState } from 'react'; +import React from 'react'; import BlockSideToolbar from '../BlockSideToolbar'; import BlockSelection from '../BlockSelection'; +import TextActionMenu from '$app/components/document/TextActionMenu'; export default function Overlay({ container }: { container: HTMLDivElement }) { - const [isDragging, setDragging] = useState(false); return ( <> - {isDragging ? null : } - + + + ); } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/Root/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/Root/index.tsx index 8069b0e1a4..a0795df350 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/Root/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/Root/index.tsx @@ -19,18 +19,11 @@ function Root({ documentData }: { documentData: DocumentData }) { } return ( -
{ - // prevent backspace from going back - if (e.key === 'Backspace') { - e.stopPropagation(); - } - }} - > - -
+ <> +
+ +
+ ); } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.hooks.ts new file mode 100644 index 0000000000..821d03e893 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.hooks.ts @@ -0,0 +1,43 @@ +import { useEffect, useRef, useState } from 'react'; +import { calcToolbarPosition } from '$app/utils/document/toolbar'; +import { useAppSelector } from '$app/stores/store'; + +export function useMenuStyle(container: HTMLDivElement) { + const ref = useRef(null); + const range = useAppSelector((state) => state.documentRangeSelection); + + const [scrollTop, setScrollTop] = useState(container.scrollTop); + useEffect(() => { + const el = ref.current; + if (!el) return; + + const id = range.focus?.id; + if (!id) return; + + const position = calcToolbarPosition(el); + + if (!position) { + el.style.opacity = '0'; + el.style.pointerEvents = 'none'; + } else { + el.style.opacity = '1'; + el.style.pointerEvents = 'auto'; + el.style.top = position.top; + el.style.left = position.left; + } + }); + + useEffect(() => { + const handleScroll = () => { + setScrollTop(container.scrollTop); + }; + container.addEventListener('scroll', handleScroll); + return () => { + container.removeEventListener('scroll', handleScroll); + }; + }, [container]); + + return { + ref, + }; +} 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 new file mode 100644 index 0000000000..3625e53c5a --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.tsx @@ -0,0 +1,46 @@ +import { useMenuStyle } from './index.hooks'; +import { useAppSelector } from '$app/stores/store'; +import { isEqual } from '$app/utils/tool'; +import TextActionMenuList from '$app/components/document/TextActionMenu/menu'; + +const TextActionComponent = ({ container }: { container: HTMLDivElement }) => { + const { ref } = useMenuStyle(container); + + return ( +
{ + // prevent toolbar from taking focus away from editor + e.preventDefault(); + e.stopPropagation(); + }} + > + +
+ ); +}; +const TextActionMenu = ({ container }: { container: HTMLDivElement }) => { + const canShow = useAppSelector((state) => { + const range = state.documentRangeSelection; + if (range.isDragging) return false; + const anchorNode = range.anchor; + const focusNode = range.focus; + if (!anchorNode || !focusNode) return false; + const isSameLine = anchorNode.id === focusNode.id; + const isCollapsed = isEqual(anchorNode.selection.anchor, anchorNode.selection.focus); + return !(isSameLine && isCollapsed); + }); + if (!canShow) return null; + + return ( +
+ +
+ ); +}; + +export default TextActionMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatButton.tsx new file mode 100644 index 0000000000..5fc5127c9c --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatButton.tsx @@ -0,0 +1,68 @@ +import IconButton from '@mui/material/IconButton'; +import FormatIcon from './FormatIcon'; +import React, { useCallback, useEffect, useMemo, useContext } from 'react'; +import { TextAction } from '$app/interfaces/document'; +import MenuTooltip from '$app/components/document/TextActionMenu/menu/MenuTooltip'; +import { getFormatActiveThunk, toggleFormatThunk } from '$app_reducers/document/async-actions/format'; +import { useAppDispatch, useAppSelector } from '$app/stores/store'; +import { DocumentControllerContext } from '$app/stores/effects/document/document_controller'; +import { useSubscribeNode } from '$app/components/document/_shared/SubscribeNode.hooks'; + +const FormatButton = ({ format, icon }: { format: TextAction; icon: string }) => { + const dispatch = useAppDispatch(); + const controller = useContext(DocumentControllerContext); + + const focusId = useAppSelector((state) => state.documentRangeSelection.focus?.id || ''); + const { node: focusNode } = useSubscribeNode(focusId); + + const [isActive, setIsActive] = React.useState(false); + const color = useMemo(() => (isActive ? '#00BCF0' : 'white'), [isActive]); + + const formatTooltips: Record = useMemo( + () => ({ + [TextAction.Bold]: 'Bold', + [TextAction.Italic]: 'Italic', + [TextAction.Underline]: 'Underline', + [TextAction.Strikethrough]: 'Strike through', + [TextAction.Code]: 'Mark as Code', + }), + [] + ); + + const isFormatActive = useCallback(async () => { + if (!focusNode) return false; + const { payload: isActive } = await dispatch(getFormatActiveThunk(format)); + return !!isActive; + }, [dispatch, format, focusNode]); + + const toggleFormat = useCallback( + async (format: TextAction) => { + if (!controller) return; + await dispatch( + toggleFormatThunk({ + format, + controller, + isActive, + }) + ); + }, + [controller, dispatch, isActive] + ); + + useEffect(() => { + void (async () => { + const isActive = await isFormatActive(); + setIsActive(isActive); + })(); + }, [isFormatActive]); + + return ( + + toggleFormat(format)}> + + + + ); +}; + +export default FormatButton; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatIcon.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatIcon.tsx similarity index 91% rename from frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatIcon.tsx rename to frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatIcon.tsx index 39aeafebac..c3fa3bb69e 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatIcon.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatIcon.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { FormatBold, FormatUnderlined, FormatItalic, CodeOutlined, StrikethroughSOutlined } from '@mui/icons-material'; -import { iconSize } from '$app/constants/document/toolbar'; +export const iconSize = { width: 18, height: 18 }; export default function FormatIcon({ icon }: { icon: string }) { switch (icon) { diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/MenuTooltip.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/MenuTooltip.tsx new file mode 100644 index 0000000000..73d7251010 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/MenuTooltip.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import Tooltip from '@mui/material/Tooltip'; + +function MenuTooltip({ title, children }: { children: JSX.Element; title?: string }) { + return ( + + {title} +
+ } + placement='top-start' + > +
{children}
+ + ); +} + +export default MenuTooltip; 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 new file mode 100644 index 0000000000..c608a490e6 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/TurnIntoSelect.tsx @@ -0,0 +1,50 @@ +import React, { useCallback } from 'react'; +import TurnIntoPopover from '$app/components/document/_shared/TurnInto'; +import Button from '@mui/material/Button'; +import ArrowDropDown from '@mui/icons-material/ArrowDropDown'; +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 { node } = useSubscribeNode(id); + const handleClick = useCallback((event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }, []); + + const handleClose = useCallback(() => { + setAnchorEl(null); + }, []); + + const open = Boolean(anchorEl); + + return ( + <> + + + + + + ); +} + +export default TurnIntoSelect; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/index.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/index.hooks.ts new file mode 100644 index 0000000000..d5c52fdfd2 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/index.hooks.ts @@ -0,0 +1,47 @@ +import { useAppSelector } from '$app/stores/store'; +import { useMemo } from 'react'; +import { + blockConfig, + defaultTextActionProps, + multiLineTextActionGroups, + multiLineTextActionProps, + textActionGroups, +} from '$app/constants/document/config'; +import { useSubscribeNode } from '$app/components/document/_shared/SubscribeNode.hooks'; +import { TextAction } from '$app/interfaces/document'; + +export function useTextActionMenu() { + const range = useAppSelector((state) => state.documentRangeSelection); + + const id = useMemo(() => { + return range.anchor?.id === range.focus?.id ? range.anchor?.id : undefined; + }, [range]); + + const { node } = useSubscribeNode(id || ''); + + const items = useMemo(() => { + if (node) { + const config = blockConfig[node.type]; + const { customItems, excludeItems } = { + ...defaultTextActionProps, + ...config.textActionMenuProps, + }; + return customItems?.filter((item) => !excludeItems?.includes(item)) || []; + } else { + return multiLineTextActionProps.customItems || []; + } + }, [node]); + + // the groups have default items, so we need to filter the items if this node has excluded items + const groupItems: TextAction[][] = useMemo(() => { + const groups = node ? textActionGroups : multiLineTextActionGroups; + return groups.map((group) => { + return group.filter((item) => items.includes(item)); + }); + }, [JSON.stringify(items), node]); + + return { + groupItems, + id, + }; +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/index.tsx new file mode 100644 index 0000000000..4184f99260 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/index.tsx @@ -0,0 +1,39 @@ +import { TextAction } from '$app/interfaces/document'; +import React, { useCallback } from 'react'; +import TurnIntoSelect from '$app/components/document/TextActionMenu/menu/TurnIntoSelect'; +import FormatButton from '$app/components/document/TextActionMenu/menu/FormatButton'; +import { useTextActionMenu } from '$app/components/document/TextActionMenu/menu/index.hooks'; + +function TextActionMenuList() { + const { groupItems, id } = useTextActionMenu(); + const renderNode = useCallback((action: TextAction, id?: string) => { + switch (action) { + case TextAction.Turn: + return id ? : null; + case TextAction.Bold: + case TextAction.Italic: + case TextAction.Underline: + case TextAction.Strikethrough: + case TextAction.Code: + return ; + default: + return null; + } + }, []); + + return ( +
+ {groupItems.map((group, i: number) => ( +
+ {group.map((item) => ( +
+ {renderNode(item, id)} +
+ ))} +
+ ))} +
+ ); +} + +export default TextActionMenuList; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/Leaf.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/Leaf.tsx index 60aa7ecb77..06416564ee 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/Leaf.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/Leaf.tsx @@ -10,20 +10,12 @@ interface LeafProps extends RenderLeafProps { selectionHighlighted?: boolean; }; } -const Leaf = ({ - attributes, - children, - leaf, -}: LeafProps) => { +const Leaf = ({ attributes, children, leaf }: LeafProps) => { let newChildren = children; if (leaf.bold) { newChildren = {children}; } - if (leaf.code) { - newChildren = {newChildren}; - } - if (leaf.italic) { newChildren = {newChildren}; } @@ -32,16 +24,14 @@ const Leaf = ({ newChildren = {newChildren}; } - let className = ""; - if (leaf.strikethrough) { - className += "line-through"; - } - if (leaf.selectionHighlighted) { - className += " bg-main-secondary"; - } + const className = [ + leaf.strikethrough && 'line-through', + leaf.selectionHighlighted && 'bg-main-secondary', + leaf.code && 'bg-main-selector', + ].filter(Boolean); return ( - + {newChildren} ); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/events/Events.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/events/Events.hooks.ts index 1b6ea72606..bf592938d6 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/events/Events.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/events/Events.hooks.ts @@ -2,20 +2,23 @@ import { Editor } from 'slate'; import { useTurnIntoBlock } from './TurnIntoEvents.hooks'; import { useCallback, useContext, useMemo } from 'react'; import { keyBoardEventKeyMap } from '$app/constants/document/text_block'; -import { triggerHotkey } from '$app/utils/document/blocks/text/hotkey'; import { TextBlockKeyEventHandlerParams } from '$app/interfaces/document'; import isHotkey from 'is-hotkey'; import { indentNodeThunk, outdentNodeThunk, splitNodeThunk } from '$app_reducers/document/async-actions'; import { DocumentControllerContext } from '$app/stores/effects/document/document_controller'; -import { useAppDispatch } from '$app/stores/store'; +import { useAppDispatch, useAppSelector } from '$app/stores/store'; import { useDefaultTextInputEvents } from '$app/components/document/_shared/Text/TextEvents.hooks'; import { ReactEditor } from 'slate-react'; export function useTextBlockKeyEvent(id: string, editor: ReactEditor) { const controller = useContext(DocumentControllerContext); const dispatch = useAppDispatch(); - const defaultTextInputEvents = useDefaultTextInputEvents(id); + const isFocusCurrentNode = useAppSelector((state) => { + const { anchor, focus } = state.documentRangeSelection; + if (!anchor || !focus) return false; + return anchor.id === id && focus.id === id; + }); const { turnIntoBlockEvents } = useTurnIntoBlock(id); @@ -84,18 +87,20 @@ export function useTextBlockKeyEvent(id: string, editor: ReactEditor) { const onKeyDown = useCallback( (event: React.KeyboardEvent) => { + if (!isFocusCurrentNode) { + event.preventDefault(); + return; + } + + event.stopPropagation(); // This is list of key events that can be handled by TextBlock const keyEvents = [...events, ...turnIntoBlockEvents]; const matchKeys = keyEvents.filter((keyEvent) => keyEvent.canHandle(event, editor)); - if (matchKeys.length === 0) { - triggerHotkey(event, editor); - return; - } matchKeys.forEach((matchKey) => matchKey.handler(event, editor)); }, - [editor, events, turnIntoBlockEvents] + [editor, events, turnIntoBlockEvents, isFocusCurrentNode] ); return { diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx index 880e7f629f..275a79f94e 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx @@ -1,8 +1,7 @@ import { Slate, Editable } from 'slate-react'; import Leaf from './Leaf'; import { useTextBlock } from './TextBlock.hooks'; -import BlockHorizontalToolbar from '../BlockHorizontalToolbar'; -import React, { useEffect } from 'react'; +import React from 'react'; import { NestedBlock } from '$app/interfaces/document'; import NodeChildren from '$app/components/document/Node/NodeChildren'; @@ -23,7 +22,6 @@ function TextBlock({ <>
- {/**/} } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/SubscribeNode.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/SubscribeNode.hooks.ts index c73b1315c9..e159b73b11 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/SubscribeNode.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/SubscribeNode.hooks.ts @@ -2,7 +2,7 @@ import { useAppSelector } from '@/appflowy_app/stores/store'; import { useMemo, useRef } from 'react'; import { DocumentState, Node, RangeSelectionState } from '$app/interfaces/document'; import { nodeInRange } from '$app/utils/document/blocks/common'; -import { getNodeEndSelection, selectionIsForward } from '$app/utils/document/blocks/text/delta'; +import { getNodeEndSelection } from '$app/utils/document/blocks/text/delta'; /** * Subscribe node information @@ -18,7 +18,7 @@ export function useSubscribeNode(id: string) { }); const isSelected = useAppSelector((state) => { - return state.documentRectSelection.includes(id) || false; + return state.documentRectSelection.selection.includes(id) || false; }); // Memoize the node and its children @@ -50,6 +50,7 @@ export function useSubscribeRangeSelection(id: string) { if (range.focus?.id === id) { return range.focus.selection; } + return getAmendInRangeNodeSelection(id, range, state.document); }); @@ -60,17 +61,17 @@ export function useSubscribeRangeSelection(id: string) { } function getAmendInRangeNodeSelection(id: string, range: RangeSelectionState, document: DocumentState) { - if (!range.anchor || !range.focus || range.anchor.id === range.focus.id) { + if (!range.anchor || !range.focus || range.anchor.id === range.focus.id || range.isForward === undefined) { return null; } - const isForward = selectionIsForward(range.anchor.selection); + const isNodeInRange = nodeInRange( id, { startId: range.anchor.id, endId: range.focus.id, }, - isForward, + range.isForward, document ); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Text/TextEvents.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Text/TextEvents.hooks.ts index d4af6150e2..f6d878a557 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Text/TextEvents.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Text/TextEvents.hooks.ts @@ -11,7 +11,7 @@ import { canHandleUpKey, } from '$app/utils/document/blocks/text/hotkey'; import { TextBlockKeyEventHandlerParams } from '$app/interfaces/document'; -import { ReactEditor } from "slate-react"; +import { ReactEditor } from 'slate-react'; export function useDefaultTextInputEvents(id: string) { const dispatch = useAppDispatch(); @@ -81,11 +81,11 @@ export function useDefaultTextInputEvents(id: string) { triggerEventKey: keyBoardEventKeyMap.Backspace, canHandle: canHandleBackspaceKey, handler: (...args: TextBlockKeyEventHandlerParams) => { - const [e, _] = args; + const [e, editor] = args; e.preventDefault(); void (async () => { if (!controller) return; - await dispatch(backspaceNodeThunk({ id, controller })); + await dispatch(backspaceNodeThunk({ id, controller, editor })); })(); }, }, diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Text/TextInput.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Text/TextInput.hooks.ts index ae41fe8bdb..635976f621 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Text/TextInput.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Text/TextInput.hooks.ts @@ -1,4 +1,4 @@ -import { createEditor, Descendant, Editor } from 'slate'; +import { createEditor, Descendant, Editor, Transforms } from 'slate'; import { withReact } from 'slate-react'; import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; @@ -8,12 +8,12 @@ import { useAppDispatch } from '$app/stores/store'; import { updateNodeDeltaThunk } from '$app_reducers/document/async-actions/blocks/text/update'; import { deltaToSlateValue, slateValueToDelta } from '$app/utils/document/blocks/common'; import { isSameDelta } from '$app/utils/document/blocks/text/delta'; -import { debounce } from '$app/utils/tool'; import { useSubscribeNode } from '$app/components/document/_shared/SubscribeNode.hooks'; import { useTextSelections } from '$app/components/document/_shared/Text/TextSelection.hooks'; export function useTextInput(id: string) { const { node } = useSubscribeNode(id); + const [editor] = useState(() => withReact(createEditor())); const isComposition = useRef(false); const { setLastActiveSelection, ...selectionProps } = useTextSelections(id, editor); @@ -24,16 +24,15 @@ export function useTextInput(id: string) { } return node.data.delta; }, [node]); + const [value, setValue] = useState(deltaToSlateValue(delta)); const { sync, receive } = useUpdateDelta(id, editor); - const [value, setValue] = useState(deltaToSlateValue(delta)); - // Update the editor's value when the node's delta changes. useEffect(() => { // If composition is in progress, do nothing. if (isComposition.current) return; - receive(delta); + receive(delta, setValue); }, [delta, receive]); // Update the node's delta when the editor's value changes. @@ -88,33 +87,30 @@ function useUpdateDelta(id: string, editor: Editor) { const dispatch = useAppDispatch(); const penddingRef = useRef(false); - // when user input, update the node's delta after 200ms - const debounceUpdate = useMemo(() => { - return debounce(() => { - if (!controller) return; - const delta = slateValueToDelta(editor.children); - void (async () => { - await dispatch( - updateNodeDeltaThunk({ - id, - delta, - controller, - }) - ); - // reset pendding flag - penddingRef.current = false; - })(); - }, 200); + const update = useCallback(() => { + if (!controller) return; + const delta = slateValueToDelta(editor.children); + void (async () => { + await dispatch( + updateNodeDeltaThunk({ + id, + delta, + controller, + }) + ); + // reset pendding flag + penddingRef.current = false; + })(); }, [controller, dispatch, editor, id]); const sync = useCallback(() => { // set pendding flag penddingRef.current = true; - debounceUpdate(); - }, [debounceUpdate]); + update(); + }, [update]); const receive = useCallback( - (delta: TextDelta[]) => { + (delta: TextDelta[], setValue: (children: Descendant[]) => void) => { // if pendding, do nothing if (penddingRef.current) return; @@ -123,18 +119,14 @@ function useUpdateDelta(id: string, editor: Editor) { const isSame = isSameDelta(delta, localDelta); if (isSame) return; + Transforms.deselect(editor); const slateValue = deltaToSlateValue(delta); editor.children = slateValue; + setValue(slateValue); }, [editor] ); - useEffect(() => { - return () => { - debounceUpdate.cancel(); - }; - }); - return { sync, receive, diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Text/TextSelection.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Text/TextSelection.hooks.ts index b98e891747..234bcf4837 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Text/TextSelection.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Text/TextSelection.hooks.ts @@ -1,14 +1,14 @@ -import { MouseEventHandler, useCallback, useEffect } from 'react'; +import { MouseEvent, useCallback, useEffect, useRef } from 'react'; import { BaseRange, Editor, Node, Path, Range, Transforms } from 'slate'; import { EditableProps } from 'slate-react/dist/components/editable'; import { useSubscribeRangeSelection } from '$app/components/document/_shared/SubscribeNode.hooks'; import { useAppDispatch } from '$app/stores/store'; -import { rangeSelectionActions } from '$app_reducers/document/slice'; import { TextSelection } from '$app/interfaces/document'; import { ReactEditor } from 'slate-react'; import { syncRangeSelectionThunk } from '$app_reducers/document/async-actions/range_selection'; -import { getCollapsedRange } from '$app/utils/document/blocks/common'; -import { getEditorEndPoint, selectionIsForward } from '$app/utils/document/blocks/text/delta'; +import { getNodeEndSelection } from '$app/utils/document/blocks/text/delta'; +import { slateValueToDelta } from '$app/utils/document/blocks/common'; +import { isEqual } from '$app/utils/tool'; export function useTextSelections(id: string, editor: ReactEditor) { const { rangeRef, currentSelection } = useSubscribeRangeSelection(id); @@ -16,15 +16,21 @@ export function useTextSelections(id: string, editor: ReactEditor) { useEffect(() => { if (!rangeRef.current) return; - const { isDragging, focus, anchor } = rangeRef.current; - if (isDragging || anchor?.id !== focus?.id || !currentSelection || !Range.isCollapsed(currentSelection as BaseRange)) + if (!currentSelection) { + ReactEditor.deselect(editor); + ReactEditor.blur(editor); return; + } + const { isDragging, focus } = rangeRef.current; + if (isDragging || focus?.id !== id) return; if (!ReactEditor.isFocused(editor)) { ReactEditor.focus(editor); } - Transforms.select(editor, currentSelection); - }, [currentSelection, editor, rangeRef]); + if (!isEqual(editor.selection, currentSelection)) { + Transforms.select(editor, currentSelection); + } + }, [currentSelection, editor, id, rangeRef]); const decorate: EditableProps['decorate'] = useCallback( (entry: [Node, Path]) => { @@ -48,48 +54,6 @@ export function useTextSelections(id: string, editor: ReactEditor) { [editor, currentSelection] ); - const onMouseDown: MouseEventHandler = useCallback( - (e) => { - const range = getCollapsedRange(id, editor.selection as TextSelection); - dispatch( - rangeSelectionActions.setRange({ - ...range, - isDragging: true, - }) - ); - }, - [dispatch, editor, id] - ); - - const onMouseMove: MouseEventHandler = useCallback( - (e) => { - if (!rangeRef.current) return; - const { isDragging, anchor } = rangeRef.current; - if (!isDragging || !anchor || ReactEditor.isFocused(editor)) return; - - const isForward = selectionIsForward(anchor.selection); - if (!isForward) { - Transforms.select(editor, getEditorEndPoint(editor)); - } - ReactEditor.focus(editor); - }, - [editor, rangeRef] - ); - - const onMouseUp: MouseEventHandler = useCallback( - (e) => { - if (!rangeRef.current) return; - const { isDragging } = rangeRef.current; - if (!isDragging) return; - dispatch( - rangeSelectionActions.setRange({ - isDragging: false, - }) - ); - }, - [dispatch, rangeRef] - ); - const setLastActiveSelection = useCallback( (lastActiveSelection: Range) => { const selection = lastActiveSelection as TextSelection; @@ -102,12 +66,33 @@ export function useTextSelections(id: string, editor: ReactEditor) { ReactEditor.deselect(editor); }, [editor]); + const onMouseMove = useCallback( + (e: MouseEvent) => { + if (!rangeRef.current) return; + const { isDragging, isForward, anchor } = rangeRef.current; + if (!isDragging || !anchor) return; + if (ReactEditor.isFocused(editor)) { + return; + } + + if (anchor.id === id) { + Transforms.select(editor, anchor.selection); + } else if (!isForward) { + const endSelection = getNodeEndSelection(slateValueToDelta(editor.children)); + Transforms.select(editor, { + anchor: endSelection.anchor, + focus: editor.selection?.focus || endSelection.focus, + }); + } + ReactEditor.focus(editor); + }, + [editor, id, rangeRef] + ); + return { decorate, - onMouseDown, - onMouseMove, - onMouseUp, onBlur, + onMouseMove, setLastActiveSelection, }; } 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 new file mode 100644 index 0000000000..7087cb9a73 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TurnInto/TurnInto.hooks.ts @@ -0,0 +1,49 @@ +import { useAppDispatch } from '$app/stores/store'; +import { useCallback, useContext } from 'react'; +import { DocumentControllerContext } from '$app/stores/effects/document/document_controller'; +import { BlockData, BlockType, NestedBlock } from '$app/interfaces/document'; +import { blockConfig } from '$app/constants/document/config'; +import { turnToBlockThunk } from '$app_reducers/document/async-actions'; + +export function useTurnInto({ node, onClose }: { node: NestedBlock; onClose?: () => void }) { + const dispatch = useAppDispatch(); + + const controller = useContext(DocumentControllerContext); + + const turnIntoBlock = useCallback( + async (type: BlockType, isSelected: boolean, data?: BlockData) => { + if (!controller || isSelected) { + onClose?.(); + return; + } + + const config = blockConfig[type]; + await dispatch( + turnToBlockThunk({ + id: node.id, + controller, + type, + data: { + ...config.defaultData, + delta: node?.data?.delta || [], + ...data, + }, + }) + ); + onClose?.(); + }, + [onClose, controller, dispatch, node] + ); + + const turnIntoHeading = useCallback( + (level: number, isSelected: boolean) => { + turnIntoBlock(BlockType.HeadingBlock, isSelected, { level }); + }, + [turnIntoBlock] + ); + + return { + turnIntoBlock, + turnIntoHeading, + }; +} 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 new file mode 100644 index 0000000000..11af496572 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TurnInto/index.tsx @@ -0,0 +1,138 @@ +import React, { useMemo } from 'react'; +import { BlockType } from '$app/interfaces/document'; + +import { + ArrowRight, + Check, + DataObject, + FormatListBulleted, + FormatListNumbered, + FormatQuote, + Lightbulb, + TextFields, + Title, + Functions, +} from '@mui/icons-material'; +import Popover, { PopoverProps } from '@mui/material/Popover'; +import { ListItemIcon, ListItemText, MenuItem } from '@mui/material'; +import { useSubscribeNode } from '$app/components/document/_shared/SubscribeNode.hooks'; +import { useTurnInto } from '$app/components/document/_shared/TurnInto/TurnInto.hooks'; + +const TurnIntoPopover = ({ + id, + onClose, + ...props +}: { + id: string; + onClose?: () => void; +} & PopoverProps) => { + const { node } = useSubscribeNode(id); + const { turnIntoHeading, turnIntoBlock } = useTurnInto({ node, onClose }); + + const options: { + type: BlockType; + title: string; + icon: React.ReactNode; + selected?: boolean; + onClick?: (type: BlockType, isSelected: boolean) => void; + }[] = useMemo( + () => [ + { + type: BlockType.TextBlock, + title: 'Text', + icon: , + }, + { + type: BlockType.HeadingBlock, + title: 'Heading 1', + icon: , + selected: node?.data?.level === 1, + onClick: (type: BlockType, isSelected: boolean) => { + turnIntoHeading(1, isSelected); + }, + }, + { + type: BlockType.HeadingBlock, + title: 'Heading 2', + icon: <Title />, + selected: node?.data?.level === 2, + onClick: (type: BlockType, isSelected: boolean) => { + turnIntoHeading(2, isSelected); + }, + }, + { + type: BlockType.HeadingBlock, + title: 'Heading 3', + icon: <Title />, + selected: node?.data?.level === 3, + onClick: (type: BlockType, isSelected: boolean) => { + turnIntoHeading(3, isSelected); + }, + }, + { + type: BlockType.TodoListBlock, + title: 'To-do list', + icon: <Check />, + }, + { + type: BlockType.BulletedListBlock, + title: 'Bulleted list', + icon: <FormatListBulleted />, + }, + { + type: BlockType.NumberedListBlock, + title: 'Numbered list', + icon: <FormatListNumbered />, + }, + { + type: BlockType.ToggleListBlock, + title: 'Toggle list', + icon: <ArrowRight />, + }, + { + type: BlockType.CodeBlock, + title: 'Code', + icon: <DataObject />, + }, + { + type: BlockType.QuoteBlock, + title: 'Quote', + icon: <FormatQuote />, + }, + { + type: BlockType.CalloutBlock, + title: 'Callout', + icon: <Lightbulb />, + }, + // { + // type: BlockType.EquationBlock, + // title: 'Block Equation', + // icon: <Functions />, + // }, + ], + [node?.data?.level, turnIntoHeading] + ); + + return ( + <Popover disableAutoFocus={true} onClose={onClose} {...props}> + {options.map((option) => { + const isSelected = option.type === node.type && option.selected !== false; + return ( + <MenuItem + className={'w-[100%]'} + key={option.title} + onClick={() => + option.onClick ? option.onClick(option.type, isSelected) : turnIntoBlock(option.type, isSelected) + } + > + <ListItemIcon>{option.icon}</ListItemIcon> + <ListItemText>{option.title}</ListItemText> + <ListItemIcon>{isSelected ? <Check /> : null}</ListItemIcon> + </MenuItem> + ); + })} + </Popover> + ); +}; + +export default TurnIntoPopover; 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 ff141e22ef..2601d14ccd 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts @@ -1,44 +1,9 @@ -import { BlockData, BlockType } from '$app/interfaces/document'; +import { BlockConfig, BlockType, SplitRelationship, TextAction, TextActionMenuProps } from '$app/interfaces/document'; -export enum SplitRelationship { - NextSibling, - FirstChild, -} /** * If the block type is not in the config, it will be thrown an error in development env */ -export const blockConfig: Record< - string, - { - /** - * Whether the block can have children - */ - canAddChild: boolean; - /** - * The regexps that will be used to match the markdown flag - */ - markdownRegexps?: RegExp[]; - - /** - * The default data of the block - */ - defaultData?: BlockData<any>; - - /** - * The props that will be passed to the text split function - */ - splitProps?: { - /** - * The relationship between the next line block and the current block - */ - nextLineRelationShip: SplitRelationship; - /** - * The type of the next line block - */ - nextLineBlockType: BlockType; - }; - } -> = { +export const blockConfig: Record<string, BlockConfig> = { [BlockType.TextBlock]: { canAddChild: true, defaultData: { @@ -169,5 +134,49 @@ export const blockConfig: Record< * ``` */ markdownRegexps: [/^(```)$/], + + textActionMenuProps: { + excludeItems: [TextAction.Code], + }, }, }; + +export const defaultTextActionProps: TextActionMenuProps = { + customItems: [ + TextAction.Turn, + TextAction.Bold, + TextAction.Italic, + TextAction.Underline, + TextAction.Strikethrough, + TextAction.Code, + TextAction.Equation, + ], + excludeItems: [], +}; + +export const multiLineTextActionProps: TextActionMenuProps = { + customItems: [TextAction.Bold, TextAction.Italic, TextAction.Underline, TextAction.Strikethrough, TextAction.Code], +}; + +export const multiLineTextActionGroups = [ + [ + TextAction.Bold, + TextAction.Italic, + TextAction.Underline, + TextAction.Strikethrough, + TextAction.Code, + TextAction.Equation, + ], +]; + +export const textActionGroups = [ + [TextAction.Turn], + [ + TextAction.Bold, + TextAction.Italic, + TextAction.Underline, + TextAction.Strikethrough, + TextAction.Code, + TextAction.Equation, + ], +]; diff --git a/frontend/appflowy_tauri/src/appflowy_app/constants/document/toolbar.ts b/frontend/appflowy_tauri/src/appflowy_app/constants/document/toolbar.ts deleted file mode 100644 index 61c9a88e06..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/constants/document/toolbar.ts +++ /dev/null @@ -1,25 +0,0 @@ - -export const iconSize = { width: 18, height: 18 }; - -export const command: Record<string, { title: string; key: string }> = { - bold: { - title: 'Bold', - key: '⌘ + B', - }, - underlined: { - title: 'Underlined', - key: '⌘ + U', - }, - italic: { - title: 'Italic', - key: '⌘ + I', - }, - code: { - title: 'Mark as code', - key: '⌘ + E', - }, - strikethrough: { - title: 'Strike through', - key: '⌘ + Shift + S or ⌘ + Shift + X', - }, -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts b/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts index 9f15a21213..f0a795d6ce 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts @@ -1,6 +1,6 @@ import { Editor } from 'slate'; import { RegionGrid } from '$app/utils/region_grid'; -import { ReactEditor } from "slate-react"; +import { ReactEditor } from 'slate-react'; export enum BlockType { PageBlock = 'page', @@ -11,6 +11,7 @@ export enum BlockType { NumberedListBlock = 'numbered_list', ToggleListBlock = 'toggle_list', CodeBlock = 'code', + EquationBlock = 'math_equation', EmbedBlock = 'embed', QuoteBlock = 'quote', CalloutBlock = 'callout', @@ -87,7 +88,7 @@ export interface NestedBlock<Type = any> { } export interface TextDelta { insert: string; - attributes?: Record<string, string | boolean>; + attributes?: Record<string, string | boolean | undefined>; } export enum BlockActionType { @@ -131,16 +132,21 @@ export interface DocumentState { children: Record<string, string[]>; } +export interface RectSelectionState { + selection: string[]; + isDragging: boolean; +} export interface RangeSelectionState { - isDragging?: boolean, - anchor?: PointState, - focus?: PointState, + anchor?: PointState; + focus?: PointState; + isForward?: boolean; + isDragging: boolean; + selection: string[]; } - export interface PointState { - id: string, - selection: TextSelection + id: string; + selection: TextSelection; } export enum ChangeType { @@ -161,3 +167,62 @@ export interface BlockPBValue { } export type TextBlockKeyEventHandlerParams = [React.KeyboardEvent<HTMLDivElement>, ReactEditor & Editor]; + +export enum SplitRelationship { + NextSibling, + FirstChild, +} +export enum TextAction { + Turn = 'turn', + Bold = 'bold', + Italic = 'italic', + Underline = 'underlined', + Strikethrough = 'strikethrough', + Code = 'code', + Equation = 'equation', +} +export interface TextActionMenuProps { + /** + * The custom items that will be covered in the default items + */ + customItems?: TextAction[]; + /** + * The items that will be excluded from the default items + */ + excludeItems?: TextAction[]; +} + +export interface BlockConfig { + /** + * Whether the block can have children + */ + canAddChild: boolean; + /** + * The regexps that will be used to match the markdown flag + */ + markdownRegexps?: RegExp[]; + + /** + * The default data of the block + */ + defaultData?: BlockData<any>; + + /** + * The props that will be passed to the text split function + */ + splitProps?: { + /** + * The relationship between the next line block and the current block + */ + nextLineRelationShip: SplitRelationship; + /** + * The type of the next line block + */ + nextLineBlockType: BlockType; + }; + + /** + * The props that will be passed to the text action menu + */ + textActionMenuProps?: TextActionMenuProps; +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/backspace.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/backspace.ts index 5feb8d75aa..10207b5799 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/backspace.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/backspace.ts @@ -4,6 +4,7 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { outdentNodeThunk } from './outdent'; import { turnToTextBlockThunk } from '$app_reducers/document/async-actions/blocks/text/turn_to'; import { mergeToPrevLineThunk } from '$app_reducers/document/async-actions/blocks/text/merge'; +import { ReactEditor } from 'slate-react'; /** * 1. If current node is not text block, turn it to text block @@ -14,8 +15,8 @@ import { mergeToPrevLineThunk } from '$app_reducers/document/async-actions/block */ export const backspaceNodeThunk = createAsyncThunk( 'document/backspaceNode', - async (payload: { id: string; controller: DocumentController }, thunkAPI) => { - const { id, controller } = payload; + async (payload: { id: string; controller: DocumentController; editor: ReactEditor }, thunkAPI) => { + const { id, controller, editor } = payload; const { dispatch, getState } = thunkAPI; const state = (getState() as { document: DocumentState }).document; const node = state.nodes[id]; @@ -33,6 +34,7 @@ export const backspaceNodeThunk = createAsyncThunk( // merge to previous line when parent is root if (parentIsRoot || nextNodeId) { // merge to previous line + ReactEditor.deselect(editor); await dispatch(mergeToPrevLineThunk({ id, controller, deleteCurrentNode: true })); return; } diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/split.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/split.ts index 534cf6b24d..f5e931773c 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/split.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/split.ts @@ -1,11 +1,11 @@ -import { DocumentState } from '$app/interfaces/document'; +import { DocumentState, SplitRelationship } from '$app/interfaces/document'; import { DocumentController } from '$app/stores/effects/document/document_controller'; import { createAsyncThunk } from '@reduxjs/toolkit'; import { setCursorBeforeThunk } from '../../cursor'; import { newBlock } from '$app/utils/document/blocks/common'; -import { blockConfig, SplitRelationship } from '$app/constants/document/config'; +import { blockConfig } from '$app/constants/document/config'; import { getSplitDelta } from '@/appflowy_app/utils/document/blocks/text/delta'; -import { ReactEditor } from "slate-react"; +import { ReactEditor } from 'slate-react'; export const splitNodeThunk = createAsyncThunk( 'document/splitNode', diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/format.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/format.ts new file mode 100644 index 0000000000..94078f5d09 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/format.ts @@ -0,0 +1,89 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { RootState } from '$app/stores/store'; +import { TextAction, TextDelta, TextSelection } from '$app/interfaces/document'; +import { getAfterRangeDelta, getBeforeRangeDelta, getRangeDelta } from '$app/utils/document/blocks/text/delta'; +import { DocumentController } from '$app/stores/effects/document/document_controller'; + +export const getFormatActiveThunk = createAsyncThunk<boolean, TextAction>( + 'document/getFormatActive', + async (format, thunkAPI) => { + const { getState } = thunkAPI; + const state = getState() as RootState; + const { document } = state; + const { selection, anchor, focus } = state.documentRangeSelection; + + const match = (delta: TextDelta[], format: TextAction) => { + return delta.every((op) => op.attributes?.[format] === true); + }; + return selection.every((id) => { + const node = document.nodes[id]; + let delta = node.data?.delta as TextDelta[]; + if (!delta) return false; + + if (id === anchor?.id) { + delta = getRangeDelta(delta, anchor.selection); + } else if (id === focus?.id) { + delta = getRangeDelta(delta, focus.selection); + } + return match(delta, format); + }); + } +); + +export const toggleFormatThunk = createAsyncThunk( + 'document/toggleFormat', + async (payload: { format: TextAction; controller: DocumentController; isActive: boolean }, thunkAPI) => { + const { getState } = thunkAPI; + const { format, controller, isActive } = payload; + const state = getState() as RootState; + const { document } = state; + const { selection, anchor, focus } = state.documentRangeSelection; + const ids = Array.from(new Set(selection)); + + const toggle = (delta: TextDelta[], format: TextAction) => { + return delta.map((op) => { + const attributes = { + ...op.attributes, + [format]: isActive ? undefined : true, + }; + return { + insert: op.insert, + attributes: attributes, + }; + }); + }; + + const splitDelta = (delta: TextDelta[], selection: TextSelection) => { + const before = getBeforeRangeDelta(delta, selection); + const after = getAfterRangeDelta(delta, selection); + let middle = getRangeDelta(delta, selection); + + middle = toggle(middle, format); + + return [...before, ...middle, ...after]; + }; + + const actions = ids.map((id) => { + const node = document.nodes[id]; + let delta = node.data?.delta as TextDelta[]; + if (!delta) return controller.getUpdateAction(node); + + if (id === anchor?.id) { + delta = splitDelta(delta, anchor.selection); + } else if (id === focus?.id) { + delta = splitDelta(delta, focus.selection); + } else { + delta = toggle(delta, format); + } + + return controller.getUpdateAction({ + ...node, + data: { + ...node.data, + delta, + }, + }); + }); + await controller.applyActions(actions); + } +); diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/range_selection.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/range_selection.ts index 996fccafe8..34ccbb0bfe 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/range_selection.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/range_selection.ts @@ -1,8 +1,10 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; -import { DocumentState, RangeSelectionState, TextSelection } from '$app/interfaces/document'; +import { DocumentState, TextSelection } from '$app/interfaces/document'; import { rangeSelectionActions } from '$app_reducers/document/slice'; -import { getNodeEndSelection, selectionIsForward } from '$app/utils/document/blocks/text/delta'; +import { getNodeBeginSelection, getNodeEndSelection } from '$app/utils/document/blocks/text/delta'; import { isEqual } from '$app/utils/tool'; +import { RootState } from '$app/stores/store'; +import { getNodesInRange } from '$app/utils/document/blocks/common'; const amendAnchorNodeThunk = createAsyncThunk( 'document/amendAnchorNode', @@ -15,22 +17,18 @@ const amendAnchorNodeThunk = createAsyncThunk( const { id } = payload; const { getState, dispatch } = thunkAPI; const nodes = (getState() as { document: DocumentState }).document.nodes; - const range = (getState() as { documentRangeSelection: RangeSelectionState }).documentRangeSelection; - const { anchor: anchorNode, isDragging, focus: focusNode } = range; + + const state = getState() as RootState; + const { isDragging, isForward, ...range } = state.documentRangeSelection; + const { anchor: anchorNode, focus: focusNode } = range; if (!isDragging || !anchorNode || anchorNode.id !== id) return; const isCollapsed = focusNode?.id === id && anchorNode?.id === id; if (isCollapsed) return; const selection = anchorNode.selection; - const isForward = selectionIsForward(selection); const node = nodes[id]; - const focus = isForward - ? getNodeEndSelection(node.data.delta).anchor - : { - path: [0, 0], - offset: 0, - }; + const focus = isForward ? getNodeEndSelection(node.data.delta).anchor : getNodeBeginSelection().anchor; if (isEqual(focus, selection.focus)) return; const newSelection = { anchor: selection.anchor, @@ -58,29 +56,64 @@ export const syncRangeSelectionThunk = createAsyncThunk( thunkAPI ) => { const { getState, dispatch } = thunkAPI; - const range = (getState() as { documentRangeSelection: RangeSelectionState }).documentRangeSelection; + const state = getState() as RootState; + const range = state.documentRangeSelection; + const isDragging = range.isDragging; const { id, selection } = payload; + const updateRange = { focus: { id, selection, }, }; - const isAnchor = range.anchor?.id === id; - if (isAnchor) { + + if (!isDragging && range.anchor?.id === id) { Object.assign(updateRange, { anchor: { id, - selection, + selection: { ...selection }, + }, + }); + dispatch(rangeSelectionActions.setRange(updateRange)); + return; + } + if (!range.anchor || range.anchor.id === id) { + Object.assign(updateRange, { + anchor: { + id, + selection: { + anchor: !range.anchor ? selection.anchor : range.anchor.selection.anchor, + focus: selection.focus, + }, }, }); } + dispatch(rangeSelectionActions.setRange(updateRange)); const anchorId = range.anchor?.id; - if (!isAnchor && anchorId) { + // more than one node is selected + if (anchorId && anchorId !== id) { dispatch(amendAnchorNodeThunk({ id: anchorId })); } } ); + +export const setRangeSelectionThunk = createAsyncThunk('document/setRangeSelection', async (payload, thunkAPI) => { + const { getState, dispatch } = thunkAPI; + const state = getState() as RootState; + const { anchor, focus, isForward } = state.documentRangeSelection; + const document = state.document; + if (!anchor || !focus || isForward === undefined) return; + const rangeIds = getNodesInRange( + { + startId: anchor.id, + endId: focus.id, + }, + isForward, + document + ); + dispatch(rangeSelectionActions.setSelection(rangeIds)); +}); 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 e91baf3869..5e307af906 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 @@ -1,16 +1,29 @@ -import { DocumentState, Node, RangeSelectionState } from '@/appflowy_app/interfaces/document'; +import { + DocumentState, + Node, + PointState, + RangeSelectionState, + RectSelectionState, +} from '@/appflowy_app/interfaces/document'; import { BlockEventPayloadPB } from '@/services/backend'; import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { parseValue, matchChange } from '$app/utils/document/subscribe'; +import { getNodesInRange } from '$app/utils/document/blocks/common'; const initialState: DocumentState = { nodes: {}, children: {}, }; -const rectSelectionInitialState: string[] = []; +const rectSelectionInitialState: RectSelectionState = { + selection: [], + isDragging: false, +}; -const rangeSelectionInitialState: RangeSelectionState = {}; +const rangeSelectionInitialState: RangeSelectionState = { + isDragging: false, + selection: [], +}; export const documentSlice = createSlice({ name: 'document', @@ -35,7 +48,6 @@ export const documentSlice = createSlice({ state.nodes = nodes; state.children = children; }, - /** This function listens for changes in the data layer triggered by the data API, and updates the UI state accordingly. @@ -67,14 +79,18 @@ export const rectSelectionSlice = createSlice({ reducers: { // update block selections updateSelections: (state, action: PayloadAction<string[]>) => { - return action.payload; + state.selection = action.payload; }, // set block selected setSelectionById: (state, action: PayloadAction<string>) => { const id = action.payload; - if (state.includes(id)) return; - state.push(id); + if (state.selection.includes(id)) return; + state.selection = [...state.selection, id]; + }, + + setDragging: (state, action: PayloadAction<boolean>) => { + state.isDragging = action.payload; }, }, }); @@ -83,13 +99,27 @@ export const rangeSelectionSlice = createSlice({ name: 'documentRangeSelection', initialState: rangeSelectionInitialState, reducers: { - setRange: (state, action: PayloadAction<RangeSelectionState>) => { + setRange: ( + state, + action: PayloadAction<{ + anchor?: PointState; + focus?: PointState; + }> + ) => { return { ...state, ...action.payload, }; }, - + setSelection: (state, action: PayloadAction<string[]>) => { + state.selection = action.payload; + }, + setDragging: (state, action: PayloadAction<boolean>) => { + state.isDragging = action.payload; + }, + setForward: (state, action: PayloadAction<boolean>) => { + state.isForward = action.payload; + }, clearRange: (state, _: PayloadAction) => { return rangeSelectionInitialState; }, diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/code/decorate.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/code/decorate.ts index 7944d7ac65..81e7ff7423 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/code/decorate.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/code/decorate.ts @@ -28,7 +28,7 @@ import 'prismjs/components/prism-php'; import 'prismjs/components/prism-sql'; import 'prismjs/components/prism-visual-basic'; -import { BaseRange, NodeEntry, Text, Path } from 'slate'; +import { BaseRange, NodeEntry, Text, Path, Range, Editor } from 'slate'; const push_string = ( token: string | Prism.Token, diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/common.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/common.ts index a53b109999..b01c7b4d91 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/common.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/common.ts @@ -151,9 +151,54 @@ export function getCollapsedRange(id: string, selection: TextSelection): RangeSe anchor: clone(point), focus: clone(point), isDragging: false, + selection: [], }; } +export function iterateNodes( + range: { + startId: string; + endId: string; + }, + isForward: boolean, + document: DocumentState, + callback: (nodeId?: string) => boolean +) { + const { startId, endId } = range; + let currentId = startId; + while (currentId && currentId !== endId) { + if (isForward) { + currentId = getNextLineId(document, currentId) || ''; + } else { + currentId = getPrevLineId(document, currentId) || ''; + } + if (callback(currentId)) { + break; + } + } +} +export function getNodesInRange( + range: { + startId: string; + endId: string; + }, + isForward: boolean, + document: DocumentState +) { + const nodeIds: string[] = []; + nodeIds.push(range.startId); + iterateNodes(range, isForward, document, (nodeId) => { + if (nodeId) { + nodeIds.push(nodeId); + return false; + } else { + return true; + } + }); + nodeIds.push(range.endId); + return nodeIds; +} + export function nodeInRange( id: string, range: { @@ -163,17 +208,13 @@ export function nodeInRange( isForward: boolean, document: DocumentState ) { - const { startId, endId } = range; - let currentId = startId; - while (currentId && currentId !== id && currentId !== endId) { - if (isForward) { - currentId = getNextLineId(document, currentId) || ''; - } else { - currentId = getPrevLineId(document, currentId) || ''; + let match = false; + iterateNodes(range, isForward, document, (nodeId) => { + if (nodeId === id) { + match = true; + return true; } - } - if (currentId === id) { - return true; - } - return false; + return false; + }); + return match; } diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/selection.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/selection.ts new file mode 100644 index 0000000000..3c8cf5b3da --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/selection.ts @@ -0,0 +1,22 @@ +export function isPointInBlock(target: HTMLElement | null) { + let node = target; + while (node) { + if (node.getAttribute('data-block-id')) { + return true; + } + node = node.parentElement; + } + return false; +} + +export function getBlockIdByPoint(target: HTMLElement | null) { + let node = target; + while (node) { + const id = node.getAttribute('data-block-id'); + if (id) { + return id; + } + node = node.parentElement; + } + return null; +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/delta.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/delta.ts index e5c259e383..077f06ad67 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/delta.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/delta.ts @@ -1,4 +1,4 @@ -import { Editor, Element, Location, Text } from 'slate'; +import { Editor, Element, Location, Text, Range } from 'slate'; import { SelectionPoint, TextDelta, TextSelection } from '$app/interfaces/document'; import * as Y from 'yjs'; import { getDeltaFromSlateNodes } from '$app/utils/document/blocks/common'; @@ -14,6 +14,86 @@ export function getDelta(editor: Editor, at: Location): TextDelta[] { }); } +export function getBeforeRangeDelta(delta: TextDelta[], range: TextSelection): TextDelta[] { + const anchor = Range.start(range); + const sliceNodes = delta.slice(0, anchor.path[1] + 1); + const sliceEnd = sliceNodes[sliceNodes.length - 1]; + const sliceEndText = sliceEnd.insert.slice(0, anchor.offset); + const sliceEndAttributes = sliceEnd.attributes; + const sliceEndNode = + sliceEndText.length > 0 + ? { + insert: sliceEndText, + attributes: sliceEndAttributes, + } + : null; + const sliceMiddleNodes = sliceNodes.slice(0, sliceNodes.length - 1); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return [...sliceMiddleNodes, sliceEndNode].filter((item) => item); +} + +export function getAfterRangeDelta(delta: TextDelta[], range: TextSelection): TextDelta[] { + const focus = Range.end(range); + const sliceNodes = delta.slice(focus.path[1], delta.length); + const sliceStart = sliceNodes[0]; + const sliceStartText = sliceStart.insert.slice(focus.offset); + const sliceStartAttributes = sliceStart.attributes; + const sliceStartNode = + sliceStartText.length > 0 + ? { + insert: sliceStartText, + attributes: sliceStartAttributes, + } + : null; + const sliceMiddleNodes = sliceNodes.slice(1, sliceNodes.length); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return [sliceStartNode, ...sliceMiddleNodes].filter((item) => item); +} + +export function getRangeDelta(delta: TextDelta[], range: TextSelection): TextDelta[] { + const anchor = Range.start(range); + const focus = Range.end(range); + const sliceNodes = delta.slice(anchor.path[1], focus.path[1] + 1); + if (anchor.path[1] === focus.path[1]) { + return sliceNodes.map((item) => { + const { insert, attributes } = item; + const text = insert.slice(anchor.offset, focus.offset); + return { + insert: text, + attributes, + }; + }); + } + const sliceStart = sliceNodes[0]; + const sliceEnd = sliceNodes[sliceNodes.length - 1]; + const sliceStartText = sliceStart.insert.slice(anchor.offset); + const sliceEndText = sliceEnd.insert.slice(0, focus.offset); + const sliceStartAttributes = sliceStart.attributes; + const sliceEndAttributes = sliceEnd.attributes; + const sliceStartNode = + sliceStartText.length > 0 + ? { + insert: sliceStartText, + attributes: sliceStartAttributes, + } + : null; + + const sliceEndNode = + sliceEndText.length > 0 + ? { + insert: sliceEndText, + attributes: sliceEndAttributes, + } + : null; + const sliceMiddleNodes = sliceNodes.slice(1, sliceNodes.length - 1); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return [sliceStartNode, ...sliceMiddleNodes, sliceEndNode].filter((item) => item); +} /** * get the selection between the beginning of the editor and the point * form 0 to point @@ -290,7 +370,8 @@ export function getPointOfCurrentLineBeginning(editor: Editor) { return beginPoint; } -export function selectionIsForward(selection: TextSelection) { +export function selectionIsForward(selection: TextSelection | null) { + if (!selection) return false; const { anchor, focus } = selection; if (!anchor || !focus) return false; return anchor.path[1] < focus.path[1] || (anchor.path[1] === focus.path[1] && anchor.offset < focus.offset); diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/format.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/format.ts deleted file mode 100644 index fd36928b76..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/format.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - Editor, - Transforms, - Text, - Node -} from 'slate'; - -export function toggleFormat(editor: Editor, format: string) { - const isActive = isFormatActive(editor, format) - Transforms.setNodes( - editor, - { [format]: isActive ? null : true }, - { match: Text.isText, split: true } - ) -} - -export const isFormatActive = (editor: Editor, format: string) => { - const [match] = Editor.nodes(editor, { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - match: (n: Node) => n[format] === true, - mode: 'all', - }) - return !!match -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/hotkey.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/hotkey.ts index 32d2adcd78..046573a32f 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/hotkey.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/hotkey.ts @@ -1,5 +1,4 @@ import isHotkey from 'is-hotkey'; -import { toggleFormat } from './format'; import { Editor, Range } from 'slate'; import { getAfterRangeAt, getBeforeRangeAt, pointInBegin, pointInEnd } from './delta'; import { keyBoardEventKeyMap } from '$app/constants/document/text_block'; @@ -13,16 +12,6 @@ const HOTKEYS: Record<string, string> = { 'mod+shift+S': 'strikethrough', }; -export function triggerHotkey(event: React.KeyboardEvent<HTMLDivElement>, editor: Editor) { - for (const hotkey in HOTKEYS) { - if (isHotkey(hotkey, event)) { - event.preventDefault(); - const format = HOTKEYS[hotkey]; - toggleFormat(editor, format); - } - } -} - export function canHandleBackspaceKey(event: React.KeyboardEvent<HTMLDivElement>, editor: Editor) { const isBackspaceKey = isHotkey('backspace', event); const selection = editor.selection; diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/toolbar.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/toolbar.ts deleted file mode 100644 index 52681474d5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text/toolbar.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Editor, Range } from 'slate'; -export function calcToolbarPosition(editor: Editor, toolbarDom: HTMLDivElement, blockRect: DOMRect) { - const { selection } = editor; - - if (!selection || Range.isCollapsed(selection) || Editor.string(editor, selection) === '') { - return; - } - - const domSelection = window.getSelection(); - let domRange; - if (domSelection?.rangeCount === 0) { - return; - } else { - domRange = domSelection?.getRangeAt(0); - } - - const rect = domRange?.getBoundingClientRect() || { top: 0, left: 0, width: 0, height: 0 }; - - const top = `${-toolbarDom.offsetHeight - 5 + (rect.top - blockRect.y)}px`; - const left = `${rect.left - blockRect.x - toolbarDom.offsetWidth / 2 + rect.width / 2}px`; - - return { - top, - left, - } - -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/toolbar.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/toolbar.ts new file mode 100644 index 0000000000..1ce16134f6 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/toolbar.ts @@ -0,0 +1,19 @@ +export function calcToolbarPosition(toolbarDom: HTMLDivElement) { + const domSelection = window.getSelection(); + let domRange; + if (domSelection?.rangeCount === 0) { + return; + } else { + domRange = domSelection?.getRangeAt(0); + } + + const rect = domRange?.getBoundingClientRect() || { top: 0, left: 0, width: 0, height: 0 }; + + let top = rect.top - toolbarDom.offsetHeight; + let left = rect.left - toolbarDom.offsetWidth / 2 + rect.width / 2; + + return { + top: top + 'px', + left: left + 'px', + }; +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.tsx b/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.tsx index 301c241081..db09ea8959 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.tsx @@ -3,9 +3,15 @@ import { createTheme, ThemeProvider } from '@mui/material'; import Root from '../components/document/Root'; import { DocumentControllerContext } from '../stores/effects/document/document_controller'; -const theme = createTheme({ +const muiTheme = createTheme({ typography: { fontFamily: ['Poppins'].join(','), + fontSize: 14, + }, + palette: { + primary: { + main: '#00BCF0', + }, }, }); @@ -14,7 +20,7 @@ export const DocumentPage = () => { if (!documentId || !documentData || !controller) return null; return ( - <ThemeProvider theme={theme}> + <ThemeProvider theme={muiTheme}> <DocumentControllerContext.Provider value={controller}> <Root documentData={documentData} /> </DocumentControllerContext.Provider> diff --git a/frontend/appflowy_tauri/src/services/backend/index.ts b/frontend/appflowy_tauri/src/services/backend/index.ts index d9d87d088c..07a9c14cca 100644 --- a/frontend/appflowy_tauri/src/services/backend/index.ts +++ b/frontend/appflowy_tauri/src/services/backend/index.ts @@ -4,3 +4,5 @@ export * from "./models/flowy-folder2"; export * from "./models/flowy-document2"; export * from "./models/flowy-net"; export * from "./models/flowy-error"; +export * from "./models/flowy-config"; + diff --git a/frontend/appflowy_tauri/src/styles/template.css b/frontend/appflowy_tauri/src/styles/template.css index d25f3384b2..5001b5650f 100644 --- a/frontend/appflowy_tauri/src/styles/template.css +++ b/frontend/appflowy_tauri/src/styles/template.css @@ -24,7 +24,6 @@ body { @apply bg-[transparent] } - .btn { @apply rounded-xl border border-gray-500 px-4 py-3; }