Răsfoiți Sursa

Merge branch 'feat/db' into refactor/db-properties-schema

Tienson Qin 1 an în urmă
părinte
comite
95eb0e7f4a
50 a modificat fișierele cu 966 adăugiri și 747 ștergeri
  1. 2 2
      android/app/build.gradle
  2. 2 1
      deps.edn
  3. 2 1
      deps/common/deps.edn
  4. 27 0
      deps/common/src/logseq/common/missionary_util.cljs
  5. 23 10
      deps/outliner/src/logseq/outliner/core.cljs
  6. 0 29
      deps/shui/src/logseq/shui/context.cljs
  7. 0 24
      deps/shui/src/logseq/shui/core.cljs
  8. 7 7
      deps/shui/src/logseq/shui/shortcut/v1.cljs
  9. 4 0
      deps/shui/src/logseq/shui/ui.cljs
  10. 4 4
      ios/App/App.xcodeproj/project.pbxproj
  11. 21 2
      resources/css/shui.css
  12. 1 1
      resources/forge.config.js
  13. 1 1
      resources/package.json
  14. 4 1
      src/electron/electron/window.cljs
  15. 4 4
      src/main/frontend/animations.css
  16. 0 15
      src/main/frontend/colors.cljs
  17. 102 101
      src/main/frontend/commands.cljs
  18. 38 48
      src/main/frontend/components/block.cljs
  19. 2 2
      src/main/frontend/components/block.css
  20. 23 24
      src/main/frontend/components/cmdk.cljs
  21. 2 1
      src/main/frontend/components/container.cljs
  22. 183 176
      src/main/frontend/components/content.cljs
  23. 5 3
      src/main/frontend/components/content.css
  24. 1 1
      src/main/frontend/components/datetime.cljs
  25. 148 91
      src/main/frontend/components/editor.cljs
  26. 30 7
      src/main/frontend/components/editor.css
  27. 1 1
      src/main/frontend/components/find_in_page.cljs
  28. 6 1
      src/main/frontend/components/page.cljs
  29. 2 4
      src/main/frontend/components/plugins.cljs
  30. 3 3
      src/main/frontend/components/query.cljs
  31. 8 23
      src/main/frontend/components/query_table.cljs
  32. 2 2
      src/main/frontend/components/reference.cljs
  33. 24 23
      src/main/frontend/components/right_sidebar.cljs
  34. 2 3
      src/main/frontend/components/shortcut.cljs
  35. 1 1
      src/main/frontend/components/shortcut_help.cljs
  36. 18 6
      src/main/frontend/extensions/code.cljs
  37. 3 3
      src/main/frontend/handler/editor.cljs
  38. 8 7
      src/main/frontend/handler/events.cljs
  39. 1 1
      src/main/frontend/mobile/graph_picker.cljs
  40. 0 46
      src/main/frontend/shui.cljs
  41. 0 3
      src/main/frontend/state.cljs
  42. 3 4
      src/main/frontend/ui.cljs
  43. 6 3
      src/main/frontend/util/cursor.cljs
  44. 1 1
      src/main/frontend/version.cljs
  45. 12 10
      src/main/frontend/worker/rtc/const.cljs
  46. 2 8
      src/main/frontend/worker/rtc/core.cljs
  47. 161 0
      src/main/frontend/worker/rtc/ws2.cljs
  48. 31 15
      src/resources/dicts/it.edn
  49. 23 16
      src/test/frontend/worker/fixtures.cljs
  50. 12 7
      src/test/frontend/worker/undo_redo_test.cljs

+ 2 - 2
android/app/build.gradle

@@ -7,8 +7,8 @@ android {
         applicationId "com.logseq.app"
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 82
-        versionName "0.10.8"
+        versionCode 83
+        versionName "0.10.9"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

+ 2 - 1
deps.edn

@@ -38,7 +38,8 @@
   logseq/publishing                     {:local/root "deps/publishing"}
   logseq/shui                           {:local/root "deps/shui"}
   metosin/malli                         {:mvn/version "0.10.0"}
-  com.cognitect/transit-cljs            {:mvn/version "0.8.280"}}
+  com.cognitect/transit-cljs            {:mvn/version "0.8.280"}
+  missionary/missionary                 {:mvn/version "b.39"}}
 
  :aliases {:cljs {:extra-paths ["src/dev-cljs/" "src/test/" "src/electron/"]
                   :extra-deps  {org.clojure/clojurescript        {:mvn/version "1.11.54"}

+ 2 - 1
deps/common/deps.edn

@@ -1,6 +1,7 @@
 {:paths ["src" "resources"]
  :deps {com.andrewmcveigh/cljs-time           {:git/url "https://github.com/logseq/cljs-time" ;; fork
-                                               :sha     "5704fbf48d3478eedcf24d458c8964b3c2fd59a9"}}
+                                               :sha     "5704fbf48d3478eedcf24d458c8964b3c2fd59a9"}
+        missionary/missionary                 {:mvn/version "b.39"}}
  :aliases
  {:test {:extra-paths ["test"]
          :extra-deps  {olical/cljs-test-runner   {:mvn/version "3.8.0"}

+ 27 - 0
deps/common/src/logseq/common/missionary_util.cljs

@@ -0,0 +1,27 @@
+(ns logseq.common.missionary-util
+  "Utils based on missionary."
+  (:require [missionary.core :as m]))
+
+
+
+(def ^:private retry-sentinel (js-obj))
+
+(def delays (reductions * 1000 (repeat 2)))
+
+(defn backoff
+  "Retry task when it throw exception `(get ex-data :missionary/retry)`"
+  [delays task]
+  (m/sp
+   (loop [[delay & delays] (seq delays)]
+     (let [r (try
+               (m/? task)
+               (catch :default e
+                 (if (and (some-> e ex-data :missionary/retry)
+                          (pos-int? delay))
+                   (do (m/? (m/sleep delay))
+                       (println :missionary/retry "after" delay "ms (" (ex-message e) ")")
+                       retry-sentinel)
+                   (throw e))))]
+       (if (identical? r retry-sentinel)
+         (recur delays)
+         r)))))

+ 23 - 10
deps/outliner/src/logseq/outliner/core.cljs

@@ -968,6 +968,11 @@
   [repo conn date-formatter blocks delete-opts]
   [:pre [(seq blocks)]]
   (let [top-level-blocks (filter-top-level-blocks blocks)
+        non-consecutive? (and (> (count top-level-blocks) 1) (seq (ldb/get-non-consecutive-blocks @conn top-level-blocks)))
+        reversed? (and (not non-consecutive?)
+                       (= (:db/id (:block/left (first top-level-blocks)))
+                          (:db/id (second top-level-blocks))))
+        top-level-blocks (if reversed? (reverse top-level-blocks) top-level-blocks)
         txs-state (ds/new-outliner-txs-state)
         block-ids (map (fn [b] [:block/uuid (:block/uuid b)]) top-level-blocks)
         start-block (first top-level-blocks)
@@ -978,7 +983,7 @@
          (= 1 (count top-level-blocks))
          (= start-node end-node))
       (delete-block repo conn txs-state start-node (assoc delete-opts :date-formatter date-formatter))
-      (let [non-consecutive? (seq (ldb/get-non-consecutive-blocks @conn top-level-blocks))]
+      (do
         (when-not non-consecutive?
           (let [sibling? (= (otree/-get-parent-id start-node conn)
                             (otree/-get-parent-id end-node conn))
@@ -1057,13 +1062,17 @@
           (str "Invalid blocks (without either parent or left): "
                (remove (fn [block] (and (:db/id (:block/parent block)) (:db/id (:block/left block)))) blocks)))
   (let [db @conn
-        blocks (filter-top-level-blocks blocks)
-        [target-block sibling?] (get-target-block db blocks target-block opts)
-        non-consecutive-blocks? (seq (ldb/get-non-consecutive-blocks db blocks))
-        blocks (if non-consecutive-blocks?
-                 (sort-non-consecutive-blocks db blocks)
-                 blocks)
-        original-position? (move-to-original-position? blocks target-block sibling? non-consecutive-blocks?)]
+        top-level-blocks (filter-top-level-blocks blocks)
+        [target-block sibling?] (get-target-block db top-level-blocks target-block opts)
+        non-consecutive? (and (> (count top-level-blocks) 1) (seq (ldb/get-non-consecutive-blocks db top-level-blocks)))
+        reversed? (and (not non-consecutive?)
+                       (= (:db/id (:block/left (first top-level-blocks)))
+                          (:db/id (second top-level-blocks))))
+        top-level-blocks (if reversed? (reverse top-level-blocks) top-level-blocks)
+        blocks (if non-consecutive?
+                 (sort-non-consecutive-blocks db top-level-blocks)
+                 top-level-blocks)
+        original-position? (move-to-original-position? blocks target-block sibling? non-consecutive?)]
     (when (and (not (contains? (set (map :db/id blocks)) (:db/id target-block)))
                (not original-position?))
       (let [parents (->> (ldb/get-block-parents db (:block/uuid target-block) {})
@@ -1128,8 +1137,12 @@
   (let [db @conn
         top-level-blocks (->> (map (fn [b] (d/entity db (:db/id b))) blocks)
                               filter-top-level-blocks)
-        non-consecutive-blocks (ldb/get-non-consecutive-blocks db top-level-blocks)]
-    (when (empty? non-consecutive-blocks)
+        non-consecutive? (and (> (count top-level-blocks) 1) (seq (ldb/get-non-consecutive-blocks @conn top-level-blocks)))
+        reversed? (and (not non-consecutive?)
+                       (= (:db/id (:block/left (first top-level-blocks)))
+                          (:db/id (second top-level-blocks))))
+        top-level-blocks (if reversed? (reverse top-level-blocks) top-level-blocks)]
+    (when-not non-consecutive?
       (let [first-block (d/entity db (:db/id (first top-level-blocks)))
             left (d/entity db (:db/id (:block/left first-block)))
             parent (:block/parent first-block)

+ 0 - 29
deps/shui/src/logseq/shui/context.cljs

@@ -1,29 +0,0 @@
-(ns logseq.shui.context)
-
-(defn inline->inline-block [inline block-config]
-  (fn [_context item]
-    (inline block-config item)))
-
-(defn inline->map-inline-block [inline block-config]
-  (let [inline* (inline->inline-block inline block-config)]
-    (fn [context col]
-      (map #(inline* context %) col))))
-
-(defn make-context [{:keys [block-config config inline int->local-time-2 blocks-container page-cp page] :as props}]
-  (merge props {;; Until components are converted over, they need to fallback to the old inline function
-                ;; Wrap the old inline function to allow for interception, but fallback to the old inline function
-                :inline-block (inline->inline-block inline block-config)
-                :map-inline-block (inline->map-inline-block inline block-config)
-                ;; Currently frontend component are provided an object map containing at least the following keys:
-                ;; These will be passed through in a whitelisted fashion so as to be able to track the dependencies
-                ;; back to the core application
-                ;; TODO: document the following
-                :block (:block block-config)  ;; the db entity of the current block
-                :block? (:block? block-config)
-                :editor-box (:editor-box block-config)
-                :id (:id block-config)
-                :mode? (:mode? block-config)
-                :query-result (:query-result block-config)
-                :sidebar? (:sidebar? block-config)
-                :uuid (:uuid block-config)
-                :whiteboard? (:whiteboard? block-config)}))

+ 0 - 24
deps/shui/src/logseq/shui/core.cljs

@@ -1,24 +0,0 @@
-(ns logseq.shui.core
-  (:require
-    [logseq.shui.context :as shui.context]
-    [logseq.shui.icon.v2 :as shui.icon.v2]
-    [logseq.shui.list-item.v1 :as shui.list-item.v1]
-    [logseq.shui.table.v2 :as shui.table.v2]
-    [logseq.shui.shortcut.v1 :as shui.shortcut.v1]))
-
-;; table component
-(def table shui.table.v2/root)
-(def table-v2 shui.table.v2/root)
-
-;; shortcut
-(def shortcut shui.shortcut.v1/root)
-
-;; icon
-(def icon shui.icon.v2/root)
-
-;; list-item
-(def list-item shui.list-item.v1/root)
-(def list-item-v1 shui.list-item.v1/root)
-
-;; context
-(def make-context shui.context/make-context)

+ 7 - 7
deps/shui/src/logseq/shui/shortcut/v1.cljs

@@ -1,6 +1,6 @@
 (ns logseq.shui.shortcut.v1
   (:require [clojure.string :as string]
-            [logseq.shui.ui :as ui]
+            [logseq.shui.base.core :as shui.base]
             [rum.core :as rum]
             [goog.userAgent]))
 
@@ -65,12 +65,12 @@
   [ks size {:keys [interactive?]}]
   (let [tiles (map print-shortcut-key ks)
         interactive? (true? interactive?)]
-    (ui/button {:variant (if interactive? :default :text)
-                :class   (str "bg-gray-03 text-gray-10 px-1.5 py-0 leading-4 h-5 rounded font-normal "
-                           (if interactive?
-                             "hover:bg-gray-04 active:bg-gray-03 hover:text-gray-12"
-                             "bg-transparent cursor-default active:bg-gray-03 hover:text-gray-11 opacity-80"))
-                :size    size}
+    (shui.base/button {:variant (if interactive? :default :text)
+                       :class (str "bg-gray-03 text-gray-10 px-1.5 py-0 leading-4 h-5 rounded font-normal "
+                                (if interactive?
+                                  "hover:bg-gray-04 active:bg-gray-03 hover:text-gray-12"
+                                  "bg-transparent cursor-default active:bg-gray-03 hover:text-gray-11 opacity-80"))
+                       :size size}
       (for [[index tile] (map-indexed vector tiles)]
         [:<>
          (when (< 0 index)

+ 4 - 0
deps/shui/src/logseq/shui/ui.cljs

@@ -1,6 +1,8 @@
 (ns logseq.shui.ui
   (:require [logseq.shui.util :as util]
             [logseq.shui.icon.v2 :as icon-v2]
+            [logseq.shui.shortcut.v1 :as shui.shortcut.v1]
+            [logseq.shui.list-item.v1 :as shui.list-item.v1]
             [logseq.shui.toaster.core :as toaster-core]
             [logseq.shui.select.core :as select-core]
             [logseq.shui.select.multi :as select-multi]
@@ -13,6 +15,8 @@
 (def link base-core/link)
 (def trigger-as base-core/trigger-as)
 (def trigger-child-wrap base-core/trigger-child-wrap)
+(def ^:todo shortcut shui.shortcut.v1/root)
+(def ^:todo list-item shui.list-item.v1/root)
 (def ^:export tabler-icon icon-v2/root)
 
 (def alert (util/lsui-wrap "Alert"))

+ 4 - 4
ios/App/App.xcodeproj/project.pbxproj

@@ -519,7 +519,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.10.8;
+				MARKETING_VERSION = 0.10.9;
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -546,7 +546,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.10.8;
+				MARKETING_VERSION = 0.10.9;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -571,7 +571,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.10.8;
+				MARKETING_VERSION = 0.10.9;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
@@ -598,7 +598,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.10.8;
+				MARKETING_VERSION = 0.10.9;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 21 - 2
resources/css/shui.css

@@ -253,8 +253,27 @@ html[data-theme=dark] {
 }
 
 .ui__dropdown-menu-content {
-  max-height: var(--radix-dropdown-menu-content-available-height);
-  overflow-y: auto;
+  @apply overflow-y-auto;
+
+  &[data-side=top] {
+    max-height: calc(var(--radix-dropdown-menu-content-available-height) - 40px);
+  }
+
+  &[data-side=bottom] {
+    max-height: calc(var(--radix-dropdown-menu-content-available-height) - 20px);
+  }
+}
+
+.ui__popover-content {
+  @apply overflow-y-auto;
+
+  &[data-side=top] {
+    max-height: calc(var(--radix-popover-content-available-height) - 40px);
+  }
+
+  &[data-side=bottom] {
+    max-height: calc(var(--radix-popover-content-available-height) - 20px);
+  }
 }
 
 .ui__popover-content {

+ 1 - 1
resources/forge.config.js

@@ -4,7 +4,7 @@ module.exports = {
   packagerConfig: {
     name: 'Logseq',
     icon: './icons/logseq_big_sur.icns',
-    buildVersion: 82,
+    buildVersion: 83,
     protocols: [
       {
         "protocol": "logseq",

+ 1 - 1
resources/package.json

@@ -1,7 +1,7 @@
 {
   "name": "Logseq",
   "productName": "Logseq",
-  "version": "0.10.8",
+  "version": "0.10.9",
   "main": "electron.js",
   "author": "Logseq",
   "license": "AGPL-3.0",

+ 4 - 1
src/electron/electron/window.cljs

@@ -104,7 +104,10 @@
   [^js win]
   (when (.isMinimized ^object win)
     (.restore win))
-  (.focus win))
+  ;; Ref: https://github.com/electron/electron/issues/8734
+  (.setVisibleOnAllWorkspaces win true)
+  (.focus win)
+  (.setVisibleOnAllWorkspaces win false))
 
 (defn get-graph-all-windows
   [graph-path] ;; graph-path == dir

+ 4 - 4
src/main/frontend/animations.css

@@ -33,11 +33,11 @@
   opacity: 0; /* make things invisible upon start */
   animation: fadeIn ease-in 1; /* call our keyframe named fadeIn, use animattion ease-in and repeat it only 1 time */
   animation-fill-mode: forwards; /* this makes sure that after animation is done we remain at the last keyframe value (opacity: 1) */
-  animation-duration: 1s;
-}
-
-.faster-fade-in {
   animation-duration: 0.5s;
+
+  &.faster {
+    animation-duration: 0.3s;
+  }
 }
 
 /* page transition */

+ 0 - 15
src/main/frontend/colors.cljs

@@ -12,21 +12,6 @@
   ([color value alpha?]
    (str "var(--rx-" (name color) "-" (cond-> value keyword? name) (if alpha? "-alpha" "") ")")))
 
-(defn linear-gradient [color-name color-stop gradient-level]
-  (let [color-index (.indexOf color-list (keyword color-name))
-        step (fn [dist]
-               (str "var(--rx-"
-                 (name (nth color-list (mod (+ color-index dist) (count color-list))))
-                 "-" (name color-stop) ")"))]
-    (case gradient-level
-      2 (str "linear-gradient(-45deg, " (step -1) " -50%, " (step 0) " 50%, " (step 1) " 150%)")
-      3 (str "linear-gradient(-45deg, " (step -1) " 0%, " (step 0) " 50%, " (step 1) " 100%)")
-      4 (str "linear-gradient(-45deg, " (step -2) " -16.66%, " (step -1) " 16.66%, " (step 0) " 50%, " (step 1) " 83.33%, " (step 2) " 116.66%)")
-      5 (str "linear-gradient(-45deg, " (step -2) " 0%, " (step -1) " 25%, " (step 0) " 50%, " (step 1) " 75%, " (step 2) " 100%)")
-      6 (str "linear-gradient(-45deg, " (step -3) " -10%, " (step -2) " 10%, " (step -1) " 30%, " (step 0) " 50%, " (step 1) " 70%, " (step 2) " 90%, " (step 3) " 110%)")
-      7 (str "linear-gradient(-45deg, " (step -3) " 0%, " (step -2) " 16.66%, " (step -1) " 33.33%, " (step 0) " 50%, " (step 1) " 66.66%, " (step 2) " 83.33%, " (step 3) " 100%)")
-      (str "linear-gradient(90deg, " (step 0) ", " (step 0) ")"))))
-
 (defn get-accent-color
   []
   (when-let [color (some-> js/document.documentElement

+ 102 - 101
src/main/frontend/commands.cljs

@@ -190,7 +190,7 @@
 (defn- headings
   []
   (mapv (fn [level]
-          (let [heading (str "h" level)]
+          (let [heading (str "Heading " level)]
             [heading (->heading level)])) (range 1 7)))
 
 (defonce *matched-commands (atom nil))
@@ -267,106 +267,107 @@
         embed-page (if db? db-based-embed-page file-based-embed-page)
         embed-block (if db? db-based-embed-block file-based-embed-block)]
     (->>
-     (concat
-    ;; basic
-      [["Page reference"
-        [[:editor/input page-ref/left-and-right-brackets {:backward-pos 2}]
-         [:editor/search-page]]
-        "Create a backlink to a page"
-        "BASIC"]
-       ["Page embed" (embed-page) "Embed a page here"]
-       ["Block reference" [[:editor/input block-ref/left-and-right-parens {:backward-pos 2}]
-                           [:editor/search-block :reference]] "Create a backlink to a block"]
-       ["Block embed" (embed-block) "Embed a block here" "Embed a block here"]
-       ["Link" (link-steps) "Create a HTTP link"]
-       ["Image link" (image-link-steps) "Create a HTTP link to a image"]
-       (when (state/markdown?)
-         ["Underline" [[:editor/input "<ins></ins>"
-                        {:last-pattern command-trigger
-                         :backward-pos 6}]] "Create a underline text decoration"])
-       (when-not db?
-         ["Template" [[:editor/input command-trigger nil]
-                      [:editor/search-template]] "Insert a created template here"])
-       (cond
-         (and (util/electron?) (config/local-file-based-graph? (state/get-current-repo)))
-
-         ["Upload an asset" [[:editor/click-hidden-file-input :id]] "Upload file types like image, pdf, docx, etc.)"])]
-
-       ;; ["Upload an image" [[:editor/click-hidden-file-input :id]]]
-
-      (headings)
-
-    ;; time & date
-
-      [["Tomorrow"
-        #(get-page-ref-text (date/tomorrow))
-        "Insert the date of tomorrow"
-        "TIME & DATE"]
-       ["Yesterday" #(get-page-ref-text (date/yesterday)) "Insert the date of yesterday"]
-       ["Today" #(get-page-ref-text (date/today)) "Insert the date of today"]
-       ["Current time" #(date/get-current-time) "Insert current time"]
-       ["Date picker" [[:editor/show-date-picker]] "Pick a date and insert here"]]
-
-      ;; order list
-      [["Number list"
-        [[:editor/clear-current-slash]
-         [:editor/toggle-own-number-list]]
-        "Number list"
-        "LIST TYPE"]
-       ["Number children" [[:editor/clear-current-slash]
-                           [:editor/toggle-children-number-list]] "Number children"]]
-
-    ;; task management
-      (get-statuses)
-      [["Deadline" [[:editor/clear-current-slash]
-                    [:editor/set-deadline]]]
-       ["Scheduled" [[:editor/clear-current-slash]
-                     [:editor/set-scheduled]]]]
-
-    ;; priority
-      (get-priorities)
-
-    ;; advanced
-
-      [["Query"
-        [[:editor/input "{{query }}" {:backward-pos 2}]
-         [:editor/exit]]
-        query-doc
-        "ADVANCED"]
-       ["Zotero" (zotero-steps) "Import Zotero journal article"]
-       ["Query function" [[:editor/input "{{function }}" {:backward-pos 2}]] "Create a query function"]
-       ["Calculator" [[:editor/input "```calc\n\n```" {:type "block"
-                                                       :backward-pos 4}]
-                      [:codemirror/focus]] "Insert a calculator"]
-       ["Draw" (fn []
-                 (let [file (draw/file-name)
-                       path (str common-config/default-draw-directory "/" file)
-                       text (page-ref/->page-ref path)]
-                   (p/let [_ (draw/create-draw-with-default-content path)]
-                     (println "draw file created, " path))
-                   text)) "Draw a graph with Excalidraw"]
-       ["Embed HTML " (->inline "html")]
-
-       ["Embed Video URL" [[:editor/input "{{video }}" {:last-pattern command-trigger
-                                                        :backward-pos 2}]]]
-
-       ["Embed Youtube timestamp" [[:youtube/insert-timestamp]]]
-
-       ["Embed Twitter tweet" [[:editor/input "{{tweet }}" {:last-pattern command-trigger
-                                                            :backward-pos 2}]]]
-       ["Add new property" [[:editor/clear-current-slash]
-                            [:editor/new-property]]]
-
-       ["Code block" [[:editor/input "```\n```\n" {:type            "block"
-                                                   :backward-pos    5
-                                                   :only-breakline? true}]
-                      [:editor/select-code-block-mode]] "Insert code block"]]
-
-      @*extend-slash-commands
-    ;; Allow user to modify or extend, should specify how to extend.
-
-      (state/get-commands)
-      (state/get-plugins-slash-commands))
+      (concat
+        ;; basic
+        [["Page reference"
+          [[:editor/input page-ref/left-and-right-brackets {:backward-pos 2}]
+           [:editor/search-page]]
+          "Create a backlink to a page"
+          "BASIC"]
+         ["Page embed" (embed-page) "Embed a page here"]
+         ["Block reference" [[:editor/input block-ref/left-and-right-parens {:backward-pos 2}]
+                             [:editor/search-block :reference]] "Create a backlink to a block"]
+         ["Block embed" (embed-block) "Embed a block here"]]
+
+        ;; format
+        [["Link" (link-steps) "Create a HTTP link" "FORMAT"]
+         ["Image link" (image-link-steps) "Create a HTTP link to a image"]
+         (when (state/markdown?)
+           ["Underline" [[:editor/input "<ins></ins>"
+                          {:last-pattern command-trigger
+                           :backward-pos 6}]] "Create a underline text decoration"])
+         ["Code block" [[:editor/input "```\n```\n" {:type "block"
+                                                     :backward-pos 5
+                                                     :only-breakline? true}]
+                        [:editor/select-code-block-mode]] "Insert code block"]]
+
+        (headings)
+
+        ;; time & date
+        [["Tomorrow"
+          #(get-page-ref-text (date/tomorrow))
+          "Insert the date of tomorrow"
+          "TIME & DATE"]
+         ["Yesterday" #(get-page-ref-text (date/yesterday)) "Insert the date of yesterday"]
+         ["Today" #(get-page-ref-text (date/today)) "Insert the date of today"]
+         ["Current time" #(date/get-current-time) "Insert current time"]
+         ["Date picker" [[:editor/show-date-picker]] "Pick a date and insert here"]]
+
+        ;; order list
+        [["Number list"
+          [[:editor/clear-current-slash]
+           [:editor/toggle-own-number-list]]
+          "Number list"
+          "LIST TYPE"]
+         ["Number children" [[:editor/clear-current-slash]
+                             [:editor/toggle-children-number-list]] "Number children"]]
+
+        ;; task management
+        (get-statuses)
+        [["Deadline" [[:editor/clear-current-slash]
+                      [:editor/set-deadline]]]
+         ["Scheduled" [[:editor/clear-current-slash]
+                       [:editor/set-scheduled]]]]
+
+        ;; priority
+        (get-priorities)
+
+        ;; advanced
+
+        [["Query"
+          [[:editor/input "{{query }}" {:backward-pos 2}]
+           [:editor/exit]]
+          query-doc
+          "ADVANCED"]
+         ["Zotero" (zotero-steps) "Import Zotero journal article"]
+         ["Query function" [[:editor/input "{{function }}" {:backward-pos 2}]] "Create a query function"]
+         ["Calculator" [[:editor/input "```calc\n\n```" {:type "block"
+                                                         :backward-pos 4}]
+                        [:codemirror/focus]] "Insert a calculator"]
+         ["Draw" (fn []
+                   (let [file (draw/file-name)
+                         path (str common-config/default-draw-directory "/" file)
+                         text (page-ref/->page-ref path)]
+                     (p/let [_ (draw/create-draw-with-default-content path)]
+                       (println "draw file created, " path))
+                     text)) "Draw a graph with Excalidraw"]
+
+         (cond
+           (and (util/electron?) (config/local-file-based-graph? (state/get-current-repo)))
+
+           ["Upload an asset" [[:editor/click-hidden-file-input :id]] "Upload file types like image, pdf, docx, etc.)"])
+
+         (when-not db?
+           ["Template" [[:editor/input command-trigger nil]
+                        [:editor/search-template]] "Insert a created template here"])
+
+         ["Embed HTML " (->inline "html")]
+
+         ["Embed Video URL" [[:editor/input "{{video }}" {:last-pattern command-trigger
+                                                          :backward-pos 2}]]]
+
+         ["Embed Youtube timestamp" [[:youtube/insert-timestamp]]]
+
+         ["Embed Twitter tweet" [[:editor/input "{{tweet }}" {:last-pattern command-trigger
+                                                              :backward-pos 2}]]]
+         ["Add new property" [[:editor/clear-current-slash]
+                              [:editor/new-property]]]]
+
+        @*extend-slash-commands
+        ;; Allow user to modify or extend, should specify how to extend.
+
+        (state/get-commands)
+        (state/get-plugins-slash-commands))
      (remove nil?)
      (util/distinct-by-last-wins first))))
 

+ 38 - 48
src/main/frontend/components/block.cljs

@@ -51,12 +51,10 @@
             [frontend.handler.export.common :as export-common-handler]
             [frontend.handler.property.util :as pu]
             [frontend.handler.db-based.property :as db-property-handler]
-            [frontend.handler.db-based.property.util :as db-pu]
             [frontend.mobile.util :as mobile-util]
             [frontend.mobile.intent :as mobile-intent]
             [frontend.modules.outliner.tree :as tree]
             [frontend.security :as security]
-            [frontend.shui :refer [get-shui-component-version make-shui-context]]
             [frontend.state :as state]
             [frontend.template :as template]
             [frontend.ui :as ui]
@@ -79,7 +77,6 @@
             [logseq.common.util.block-ref :as block-ref]
             [logseq.common.util.page-ref :as page-ref]
             [logseq.common.util.macro :as macro-util]
-            [logseq.shui.core :as shui-core]
             [medley.core :as medley]
             [promesa.core :as p]
             [reitit.frontend.easy :as rfe]
@@ -3228,51 +3225,44 @@
 (defn table
   [config {:keys [header groups col_groups]}]
 
-  (case (get-shui-component-version :table config)
-    2 (let [v2-config (cond-> config
-                        (config/db-based-graph? (state/get-current-repo))
-                        (assoc-in [:block :properties]
-                                  (db-pu/readable-properties (get-in config [:block :block/properties]))))]
-        (shui-core/table-v2 {:data (concat [[header]] groups)}
-                       (make-shui-context v2-config inline)))
-    1 (let [tr (fn [elm cols]
-                 (->elem
-                  :tr
-                  (mapv (fn [col]
-                          (->elem
-                           elm
-                           {:scope "col"
-                            :class "org-left"}
-                           (map-inline config col)))
-                        cols)))
-            tb-col-groups (try
-                            (mapv (fn [number]
-                                    (let [col-elem [:col {:class "org-left"}]]
-                                      (->elem
-                                       :colgroup
-                                       (repeat number col-elem))))
-                                  col_groups)
-                            (catch :default _e
-                              []))
-            head (when header
-                   [:thead (tr :th header)])
-            groups (mapv (fn [group]
-                           (->elem
-                            :tbody
-                            (mapv #(tr :td %) group)))
-                         groups)]
-        [:div.table-wrapper
-         (->elem
-          :table
-          {:class "table-auto"
-           :border 2
-           :cell-spacing 0
-           :cell-padding 6
-           :rules "groups"
-           :frame "hsides"}
-          (vec-cat
-           tb-col-groups
-           (cons head groups)))])))
+  (let [tr (fn [elm cols]
+             (->elem
+               :tr
+               (mapv (fn [col]
+                       (->elem
+                         elm
+                         {:scope "col"
+                          :class "org-left"}
+                         (map-inline config col)))
+                 cols)))
+        tb-col-groups (try
+                        (mapv (fn [number]
+                                (let [col-elem [:col {:class "org-left"}]]
+                                  (->elem
+                                    :colgroup
+                                    (repeat number col-elem))))
+                          col_groups)
+                        (catch :default _e
+                          []))
+        head (when header
+               [:thead (tr :th header)])
+        groups (mapv (fn [group]
+                       (->elem
+                         :tbody
+                         (mapv #(tr :td %) group)))
+                 groups)]
+    [:div.table-wrapper
+     (->elem
+       :table
+       {:class "table-auto"
+        :border 2
+        :cell-spacing 0
+        :cell-padding 6
+        :rules "groups"
+        :frame "hsides"}
+       (vec-cat
+         tb-col-groups
+         (cons head groups)))]))
 
 (defn logbook-cp
   [log]

+ 2 - 2
src/main/frontend/components/block.css

@@ -218,10 +218,10 @@
 }
 
 .block-title-wrap {
-  @apply inline-flex items-center;
+  @apply inline-flex items-center flex-wrap;
 
   &.as-heading {
-    @apply flex w-full;
+    @apply flex flex-1;
   }
 
   &:has(.dsl-query) {

+ 23 - 24
src/main/frontend/components/cmdk.cljs

@@ -17,7 +17,6 @@
    [frontend.util.page :as page-util]
    [goog.functions :as gfun]
    [goog.object :as gobj]
-   [logseq.shui.core :as shui-core]
    [logseq.shui.ui :as shui]
    [promesa.core :as p]
    [rum.core :as rum]
@@ -578,37 +577,37 @@
           (if (= show :more)
             [:div.flex.flex-row.gap-1.items-center
              "Show less"
-             (shui-core/shortcut "mod up" nil)]
+             (shui/shortcut "mod up" nil)]
             [:div.flex.flex-row.gap-1.items-center
              "Show more"
-             (shui-core/shortcut "mod down" nil)])])]
+             (shui/shortcut "mod down" nil)])])]
 
       [:div.search-results
        (for [item visible-items
              :let [highlighted? (= item highlighted-item)]]
-         (let [item (shui-core/list-item (assoc item
-                                           :query (when-not (= group :create) @(::input state))
-                                           :compact true
-                                           :rounded false
-                                           :hoverable @*mouse-active?
-                                           :highlighted highlighted?
+         (let [item (shui/list-item (assoc item
+                                      :query (when-not (= group :create) @(::input state))
+                                      :compact true
+                                      :rounded false
+                                      :hoverable @*mouse-active?
+                                      :highlighted highlighted?
                                       ;; for some reason, the highlight effect does not always trigger on a
                                       ;; boolean value change so manually pass in the dep
-                                           :on-highlight-dep highlighted-item
-                                           :on-click (fn [e]
-                                                       (reset! (::highlighted-item state) item)
-                                                       (handle-action :default state item)
-                                                       (when-let [on-click (:on-click item)]
-                                                         (on-click e)))
+                                      :on-highlight-dep highlighted-item
+                                      :on-click (fn [e]
+                                                  (reset! (::highlighted-item state) item)
+                                                  (handle-action :default state item)
+                                                  (when-let [on-click (:on-click item)]
+                                                    (on-click e)))
                                       ;; :on-mouse-enter (fn [e]
                                       ;;                   (when (not highlighted?)
                                       ;;                     (reset! (::highlighted-item state) (assoc item :mouse-enter-triggered-highlight true))))
-                                           :on-highlight (fn [ref]
-                                                           (reset! (::highlighted-group state) group)
-                                                           (when (and ref (.-current ref)
-                                                                      (not (:mouse-enter-triggered-highlight @(::highlighted-item state))))
-                                                             (scroll-into-view-when-invisible state (.-current ref)))))
-                                    nil)]
+                                      :on-highlight (fn [ref]
+                                                      (reset! (::highlighted-group state) group)
+                                                      (when (and ref (.-current ref)
+                                                              (not (:mouse-enter-triggered-highlight @(::highlighted-item state))))
+                                                        (scroll-into-view-when-invisible state (.-current ref)))))
+                      nil)]
            (if (= group :blocks)
              (ui/lazy-visible (fn [] item) {:trigger-once? true})
              item)))]]]))
@@ -756,10 +755,10 @@
   (rand-nth
    [[:div.flex.flex-row.gap-1.items-center.opacity-50.hover:opacity-100
      [:div "Type"]
-     (shui-core/shortcut "/")
+     (shui/shortcut "/")
      [:div "to filter search results"]]
     [:div.flex.flex-row.gap-1.items-center.opacity-50.hover:opacity-100
-     (shui-core/shortcut ["mod" "enter"])
+     (shui/shortcut ["mod" "enter"])
      [:div "to open search in the sidebar"]]]))
 
 (rum/defcs tip <
@@ -771,7 +770,7 @@
       filter
       [:div.flex.flex-row.gap-1.items-center.opacity-50.hover:opacity-100
        [:div "Type"]
-       (shui-core/shortcut "esc" {:tiled false})
+       (shui/shortcut "esc" {:tiled false})
        [:div "to clear search filter"]]
 
       :else

+ 2 - 1
src/main/frontend/components/container.cljs

@@ -838,7 +838,8 @@
                       (shui/popup-show! e
                         (fn [{:keys [id]}]
                           [:div {:on-click #(shui/popup-hide! id)} content])
-                        {:content-props {:class "w-[280px] ls-context-menu-content"}}))
+                        {:content-props {:class "w-[280px] ls-context-menu-content"}
+                         :as-dropdown? true}))
 
                     handled
                     (cond

+ 183 - 176
src/main/frontend/components/content.cljs

@@ -23,6 +23,7 @@
             [frontend.persist-db.browser :as db-browser]
             [frontend.state :as state]
             [frontend.ui :as ui]
+            [logseq.shui.ui :as shui]
             [frontend.util :as util]
             [frontend.util.url :as url-util]
             [goog.dom :as gdom]
@@ -39,7 +40,7 @@
 (rum/defc custom-context-menu-content
   []
   (let [repo (state/get-current-repo)]
-    [:.menu-links-wrapper
+    [:<>
      (ui/menu-background-color #(property-handler/batch-set-block-property! repo
                                                                             (state/get-selection-block-ids)
                                                                             (pu/get-pid :logseq.property/background-color)
@@ -47,77 +48,82 @@
                                #(property-handler/batch-remove-block-property! repo
                                                                                (state/get-selection-block-ids)
                                                                                (pu/get-pid :logseq.property/background-color)))
-
      (ui/menu-heading #(editor-handler/batch-set-heading! (state/get-selection-block-ids) %)
                       #(editor-handler/batch-set-heading! (state/get-selection-block-ids) true)
                       #(editor-handler/batch-remove-heading! (state/get-selection-block-ids)))
 
-     [:hr.menu-separator]
-
-     (ui/menu-link
-      {:key "cut"
-       :on-click #(editor-handler/cut-selection-blocks true)
-       :shortcut (ui/keyboard-shortcut-from-config :editor/cut)}
-      (t :editor/cut))
-     (ui/menu-link
-      {:key "delete"
-       :on-click #(do (editor-handler/delete-selection %)
-                      (state/hide-custom-context-menu!))
-       :shortcut (ui/keyboard-shortcut-from-config :editor/delete)}
-      (t :editor/delete-selection))
-     (ui/menu-link
-      {:key "copy"
-       :on-click editor-handler/copy-selection-blocks
-       :shortcut (ui/keyboard-shortcut-from-config :editor/copy)}
-      (t :editor/copy))
-     (ui/menu-link
-      {:key "copy as"
-       :on-click (fn [_]
-                   (let [block-uuids (editor-handler/get-selected-toplevel-block-uuids)]
-                     (state/set-modal!
-                      #(export/export-blocks block-uuids {:whiteboard? false}))))}
-      (t :content/copy-export-as))
-     (ui/menu-link
-      {:key "copy block refs"
-       :on-click editor-handler/copy-block-refs}
-      (t :content/copy-block-ref))
-     (ui/menu-link
-      {:key "copy block embeds"
-       :on-click editor-handler/copy-block-embeds}
-      (t :content/copy-block-emebed))
-
-     [:hr.menu-separator]
+     (shui/dropdown-menu-separator)
+
+     (shui/dropdown-menu-item
+       {:key "cut"
+        :on-click #(editor-handler/cut-selection-blocks true)}
+       (t :editor/cut)
+       (shui/dropdown-menu-shortcut (ui/keyboard-shortcut-from-config :editor/cut)))
+
+     (shui/dropdown-menu-item
+       {:key "delete"
+        :on-click #(do (editor-handler/delete-selection %)
+                       (state/hide-custom-context-menu!))}
+
+       (t :editor/delete-selection)
+       (shui/dropdown-menu-shortcut (ui/keyboard-shortcut-from-config :editor/delete)))
+
+     (shui/dropdown-menu-item
+       {:key "copy"
+        :on-click editor-handler/copy-selection-blocks}
+       (t :editor/copy)
+       (shui/dropdown-menu-shortcut (ui/keyboard-shortcut-from-config :editor/copy)))
+
+     (shui/dropdown-menu-item
+       {:key "copy as"
+        :on-click (fn [_]
+                    (let [block-uuids (editor-handler/get-selected-toplevel-block-uuids)]
+                      (state/set-modal!
+                        #(export/export-blocks block-uuids {:whiteboard? false}))))}
+       (t :content/copy-export-as))
+
+     (shui/dropdown-menu-item
+       {:key "copy block refs"
+        :on-click editor-handler/copy-block-refs}
+       (t :content/copy-block-ref))
+
+     (shui/dropdown-menu-item
+       {:key "copy block embeds"
+        :on-click editor-handler/copy-block-embeds}
+       (t :content/copy-block-emebed))
+
+     (shui/dropdown-menu-separator)
 
      (when (state/enable-flashcards?)
-       (ui/menu-link
+       (shui/dropdown-menu-item
         {:key "Make a Card"
          :on-click #(srs/batch-make-cards!)}
         (t :context-menu/make-a-flashcard)))
 
-     (ui/menu-link
+     (shui/dropdown-menu-item
       {:key "Toggle number list"
        :on-click #(state/pub-event! [:editor/toggle-own-number-list (state/get-selection-block-ids)])}
       (t :context-menu/toggle-number-list))
 
-     (ui/menu-link
-      {:key "cycle todos"
-       :on-click editor-handler/cycle-todos!
-       :shortcut (ui/keyboard-shortcut-from-config :editor/cycle-todo)}
-      (t :editor/cycle-todo))
+     (shui/dropdown-menu-item
+       {:key "cycle todos"
+        :on-click editor-handler/cycle-todos!}
+       (t :editor/cycle-todo)
+       (shui/dropdown-menu-shortcut (ui/keyboard-shortcut-from-config :editor/cycle-todo)))
 
-     [:hr.menu-separator]
+     (shui/dropdown-menu-separator)
 
-     (ui/menu-link
-      {:key "Expand all"
-       :on-click editor-handler/expand-all-selection!
-       :shortcut (ui/keyboard-shortcut-from-config :editor/expand-block-children)}
-      (t :editor/expand-block-children))
+     (shui/dropdown-menu-item
+       {:key "Expand all"
+        :on-click editor-handler/expand-all-selection!}
+       (t :editor/expand-block-children)
+       (shui/dropdown-menu-shortcut (ui/keyboard-shortcut-from-config :editor/expand-block-children)))
 
-     (ui/menu-link
-      {:key "Collapse all"
-       :on-click editor-handler/collapse-all-selection!
-       :shortcut (ui/keyboard-shortcut-from-config :editor/collapse-block-children)}
-      (t :editor/collapse-block-children))]))
+     (shui/dropdown-menu-item
+       {:key "Collapse all"
+        :on-click editor-handler/collapse-all-selection!}
+       (t :editor/collapse-block-children)
+       (shui/dropdown-menu-shortcut (ui/keyboard-shortcut-from-config :editor/collapse-block-children)))]))
 
 (defonce *template-including-parent? (atom nil))
 
@@ -174,13 +180,13 @@
                                                                                   (pu/get-pid :logseq.property/template-including-parent)
                                                                                   false))
                                           (state/hide-custom-context-menu!))))))))]
-         [:hr.menu-separator]])
-      (ui/menu-link
-       {:key "Make a Template"
-        :on-click (fn [e]
-                    (util/stop e)
-                    (reset! edit? true))}
-       (t :context-menu/make-a-template)))))
+         (shui/dropdown-menu-separator)])
+      (shui/dropdown-menu-item
+        {:key "Make a Template"
+         :on-click (fn [e]
+                     (util/stop e)
+                     (reset! edit? true))}
+        (t :context-menu/make-a-template)))))
 
 (rum/defc ^:large-vars/cleanup-todo block-context-menu-content <
   shortcut/disable-all-shortcuts
@@ -191,7 +197,7 @@
       (let [properties (:block/properties block)
             heading (or (pu/lookup properties :logseq.property/heading)
                         false)]
-        [:.menu-links-wrapper
+        [:<>
          (ui/menu-background-color #(property-handler/set-block-property! repo block-id
                                                                           (pu/get-pid :logseq.property/background-color)
                                                                           %)
@@ -203,24 +209,24 @@
                           #(editor-handler/set-heading! block-id true)
                           #(editor-handler/remove-heading! block-id))
 
-         [:hr.menu-separator]
+         (shui/dropdown-menu-separator)
 
-         (ui/menu-link
-          {:key      "Open in sidebar"
-           :on-click (fn [_e]
-                       (editor-handler/open-block-in-sidebar! block-id))
-           :shortcut ["⇧+click"]}
-          (t :content/open-in-sidebar))
+         (shui/dropdown-menu-item
+           {:key      "Open in sidebar"
+            :on-click (fn [_e]
+                        (editor-handler/open-block-in-sidebar! block-id))}
+           (t :content/open-in-sidebar)
+           (shui/dropdown-menu-shortcut "⇧+click"))
 
-         [:hr.menu-separator]
+         (shui/dropdown-menu-separator)
 
-         (ui/menu-link
+         (shui/dropdown-menu-item
           {:key      "Copy block ref"
            :on-click (fn [_e]
                        (editor-handler/copy-block-ref! block-id block-ref/->block-ref))}
           (t :content/copy-block-ref))
 
-         (ui/menu-link
+         (shui/dropdown-menu-item
           {:key      "Copy block embed"
            :on-click (fn [_e]
                        (editor-handler/copy-block-ref! block-id #(util/format "{{embed ((%s))}}" %)))}
@@ -228,7 +234,7 @@
 
          ;; TODO Logseq protocol mobile support
          (when (util/electron?)
-           (ui/menu-link
+           (shui/dropdown-menu-item
             {:key      "Copy block URL"
              :on-click (fn [_e]
                          (let [current-repo (state/get-current-repo)
@@ -237,157 +243,158 @@
                            (editor-handler/copy-block-ref! block-id tap-f)))}
             (t :content/copy-block-url)))
 
-         (ui/menu-link
+         (shui/dropdown-menu-item
           {:key      "Copy as"
            :on-click (fn [_]
                        (state/set-modal! #(export/export-blocks [block-id] {:whiteboard? false})))}
           (t :content/copy-export-as))
 
-         (ui/menu-link
-          {:key      "Cut"
-           :on-click (fn [_e]
-                       (editor-handler/cut-block! block-id))
-           :shortcut (ui/keyboard-shortcut-from-config :editor/cut)}
-          (t :editor/cut))
+         (shui/dropdown-menu-item
+           {:key "Cut"
+            :on-click (fn [_e]
+                        (editor-handler/cut-block! block-id))}
+           (t :editor/cut)
+           (shui/dropdown-menu-shortcut (ui/keyboard-shortcut-from-config :editor/cut)))
 
-         (ui/menu-link
-          {:key      "delete"
-           :on-click #(editor-handler/delete-block-aux! block)
-           :shortcut (ui/keyboard-shortcut-from-config :editor/delete)}
-          (t :editor/delete-selection))
+         (shui/dropdown-menu-item
+           {:key "delete"
+            :on-click #(editor-handler/delete-block-aux! block)}
+           (t :editor/delete-selection)
+           (shui/dropdown-menu-shortcut (ui/keyboard-shortcut-from-config :editor/delete)))
 
-         [:hr.menu-separator]
+         (shui/dropdown-menu-separator)
 
          (when-not db?
            (block-template block-id))
 
          (cond
            (srs/card-block? block)
-           (ui/menu-link
+           (shui/dropdown-menu-item
             {:key      "Preview Card"
              :on-click #(srs/preview (:db/id block))}
             (t :context-menu/preview-flashcard))
            (state/enable-flashcards?)
-           (ui/menu-link
+           (shui/dropdown-menu-item
             {:key      "Make a Card"
              :on-click #(srs/make-block-a-card! block-id)}
             (t :context-menu/make-a-flashcard))
            :else
            nil)
 
-         (ui/menu-link
+         (shui/dropdown-menu-item
           {:key "Toggle number list"
            :on-click #(state/pub-event! [:editor/toggle-own-number-list (state/get-selection-block-ids)])}
           (t :context-menu/toggle-number-list))
 
-         [:hr.menu-separator]
+         (shui/dropdown-menu-separator)
 
-         (ui/menu-link
-          {:key      "Expand all"
-           :on-click (fn [_e]
-                       (editor-handler/expand-all! block-id))
-           :shortcut (ui/keyboard-shortcut-from-config :editor/expand-block-children)}
-          (t :editor/expand-block-children))
+         (shui/dropdown-menu-item
+           {:key "Expand all"
+            :on-click (fn [_e]
+                        (editor-handler/expand-all! block-id))}
+           (t :editor/expand-block-children)
+           (shui/dropdown-menu-shortcut (ui/keyboard-shortcut-from-config :editor/expand-block-children)))
 
-         (ui/menu-link
-          {:key      "Collapse all"
-           :on-click (fn [_e]
-                       (editor-handler/collapse-all! block-id {}))
-           :shortcut (ui/keyboard-shortcut-from-config :editor/collapse-block-children)}
-          (t :editor/collapse-block-children))
+         (shui/dropdown-menu-item
+           {:key "Collapse all"
+            :on-click (fn [_e]
+                        (editor-handler/collapse-all! block-id {}))}
+           (t :editor/collapse-block-children)
+           (shui/dropdown-menu-shortcut (ui/keyboard-shortcut-from-config :editor/collapse-block-children)))
 
          (when (state/sub [:plugin/simple-commands])
            (when-let [cmds (state/get-plugins-commands-with-type :block-context-menu-item)]
              (for [[_ {:keys [key label] :as cmd} action pid] cmds]
-               (ui/menu-link
+               (shui/dropdown-menu-item
                 {:key      key
                  :on-click #(commands/exec-plugin-simple-command!
                              pid (assoc cmd :uuid block-id) action)}
                 label))))
 
          (when (state/sub [:ui/developer-mode?])
-           (ui/menu-link
-            {:key      "(Dev) Show block data"
-             :on-click (fn []
-                         (dev-common-handler/show-entity-data [:block/uuid block-id]))}
-            (t :dev/show-block-data)))
-
-         (when (state/sub [:ui/developer-mode?])
-           (ui/menu-link
-            {:key      "(Dev) Show block AST"
-             :on-click (fn []
-                         (let [block (db/pull [:block/uuid block-id])]
-                           (dev-common-handler/show-content-ast (:block/content block) (:block/format block))))}
-            (t :dev/show-block-ast)))
-         (when (state/sub [:ui/developer-mode?])
-           (ui/menu-link
-            {:key "(Dev) Show block content history"
-             :on-click
-             (fn []
-               (let [^object worker @db-browser/*worker]
-                 (p/let [result (.rtc-get-block-content-versions worker block-id)
-                         blocks-versions (bean/->clj result)]
-                   (prn :Dev-show-block-content-history)
-                   (doseq [[block-uuid versions] blocks-versions]
-                     (prn :block-uuid block-uuid)
-                     (pp/print-table [:content :created-at]
-                                     (map (fn [version]
-                                            {:created-at (tc/from-long (* (:created-at version) 1000))
-                                             :content (:value version)})
-                                          versions))))))}
-
-            "(Dev) Show block content history"))
-         (when (state/sub [:ui/developer-mode?])
-           (ui/menu-link
-            {:key "(Dev) Show block RTC log"
-             :on-click
-             (fn []
-               (let [^object worker @db-browser/*worker]
-                 (p/let [result (.rtc-get-block-update-log worker (str block-id))
-                         logs (transit/read transit-r result)]
-                   (prn :Dev-show-block-RTC-log block-id)
-                   (apply js/console.log logs))))}
-            "(Dev) Show block RTC log"))]))))
+           [:<>
+            (shui/dropdown-menu-separator)
+            (shui/dropdown-menu-sub
+              (shui/dropdown-menu-sub-trigger
+                "Developer tools")
+
+              (shui/dropdown-menu-sub-content
+                (shui/dropdown-menu-item
+                  {:key "(Dev) Show block data"
+                   :on-click (fn []
+                               (dev-common-handler/show-entity-data [:block/uuid block-id]))}
+                  (t :dev/show-block-data))
+                (shui/dropdown-menu-item
+                  {:key "(Dev) Show block AST"
+                   :on-click (fn []
+                               (let [block (db/pull [:block/uuid block-id])]
+                                 (dev-common-handler/show-content-ast (:block/content block) (:block/format block))))}
+                  (t :dev/show-block-ast))
+                (shui/dropdown-menu-item
+                  {:key "(Dev) Show block content history"
+                   :on-click
+                   (fn []
+                     (let [^object worker @db-browser/*worker]
+                       (p/let [result (.rtc-get-block-content-versions worker block-id)
+                               blocks-versions (bean/->clj result)]
+                         (prn :Dev-show-block-content-history)
+                         (doseq [[block-uuid versions] blocks-versions]
+                           (prn :block-uuid block-uuid)
+                           (pp/print-table [:content :created-at]
+                             (map (fn [version]
+                                    {:created-at (tc/from-long (* (:created-at version) 1000))
+                                     :content (:value version)})
+                               versions))))))}
+
+                  "(Dev) Show block content history")
+                (shui/dropdown-menu-item
+                  {:key "(Dev) Show block RTC log"
+                   :on-click
+                   (fn []
+                     (let [^object worker @db-browser/*worker]
+                       (p/let [result (.rtc-get-block-update-log worker (str block-id))
+                               logs (transit/read transit-r result)]
+                         (prn :Dev-show-block-RTC-log block-id)
+                         (apply js/console.log logs))))}
+                  "(Dev) Show block RTC log")))])]))))
 
 (rum/defc block-ref-custom-context-menu-content
   [block block-ref-id]
   (when (and block block-ref-id)
-    [:.menu-links-wrapper
-     (ui/menu-link
-      {:key "open-in-sidebar"
-       :on-click (fn []
-                   (state/sidebar-add-block!
-                    (state/get-current-repo)
-                    block-ref-id
-                    :block-ref))
-       :shortcut ["⇧+click"]}
-      (t :content/open-in-sidebar))
-     (ui/menu-link
-      {:key "copy"
-       :on-click (fn [] (editor-handler/copy-current-ref block-ref-id))}
-      (t :content/copy-ref))
-     (ui/menu-link
-      {:key "delete"
-       :on-click (fn [] (editor-handler/delete-current-ref! block block-ref-id))}
-      (t :content/delete-ref))
-     (ui/menu-link
-      {:key "replace-with-text"
-       :on-click (fn [] (editor-handler/replace-ref-with-text! block block-ref-id))}
-      (t :content/replace-with-text))
-     (ui/menu-link
-      {:key "replace-with-embed"
-       :on-click (fn [] (editor-handler/replace-ref-with-embed! block block-ref-id))}
-      (t :content/replace-with-embed))]))
+    [:<>
+     (shui/dropdown-menu-item
+       {:key "open-in-sidebar"
+        :on-click (fn []
+                    (state/sidebar-add-block!
+                      (state/get-current-repo)
+                      block-ref-id
+                      :block-ref))}
+       (t :content/open-in-sidebar)
+       (shui/dropdown-menu-shortcut ["⇧+click"]))
+     (shui/dropdown-menu-item
+       {:key "copy"
+        :on-click (fn [] (editor-handler/copy-current-ref block-ref-id))}
+       (t :content/copy-ref))
+     (shui/dropdown-menu-item
+       {:key "delete"
+        :on-click (fn [] (editor-handler/delete-current-ref! block block-ref-id))}
+       (t :content/delete-ref))
+     (shui/dropdown-menu-item
+       {:key "replace-with-text"
+        :on-click (fn [] (editor-handler/replace-ref-with-text! block block-ref-id))}
+       (t :content/replace-with-text))
+     (shui/dropdown-menu-item
+       {:key "replace-with-embed"
+        :on-click (fn [] (editor-handler/replace-ref-with-embed! block block-ref-id))}
+       (t :content/replace-with-embed))]))
 
 (rum/defc page-title-custom-context-menu-content
   [page]
   (when page
     (let [page-menu-options (page-menu/page-menu page)]
-      [:.menu-links-wrapper
+      [:<>
        (for [{:keys [title options]} page-menu-options]
-         (rum/with-key
-           (ui/menu-link options title)
-           title))])))
+         (shui/dropdown-menu-item options title))])))
 
 ;; TODO: content could be changed
 ;; Also, keyboard bindings should only be activated after

+ 5 - 3
src/main/frontend/components/content.css

@@ -19,9 +19,11 @@
 }
 
 .ls-context-menu-content {
-  @apply p-0;
-
   .menu-links-wrapper {
-    @apply border-none shadow-none;
+    @apply border-none shadow-none overflow-visible;
+  }
+
+  [role=separator] {
+    @apply opacity-80;
   }
 }

+ 1 - 1
src/main/frontend/components/datetime.cljs

@@ -170,7 +170,7 @@
       (shui/calendar
         {:mode "single"
          :initial-focus true
-         :show-week-number true
+         :show-week-number false
          :selected _date
          :on-select select-handler!
          :on-day-key-down (fn [^js d _ ^js e]

+ 148 - 91
src/main/frontend/components/editor.cljs

@@ -20,6 +20,7 @@
             [frontend.state :as state]
             [frontend.ui :as ui]
             [logseq.shui.ui :as shui]
+            [logseq.shui.popup.core :as shui-popup]
             [frontend.util :as util]
             [frontend.util.cursor :as cursor]
             [frontend.util.keycode :as keycode]
@@ -32,60 +33,64 @@
             [rum.core :as rum]
             [frontend.config :as config]))
 
-(rum/defc commands < rum/reactive
-  [id format]
-  (when (= :commands (state/sub :editor/action))
-    (let [matched (util/react *matched-commands)]
-      (ui/auto-complete
-       matched
-       {:get-group-name
-        (fn [item]
-          (when (= (count item) 4) (last item)))
-
-        :item-render
-        (fn [item]
-          (let [command-name (first item)
-                command-doc  (get item 2)
-                plugin-id    (get-in item [1 1 1 :pid])
-                doc          (when (state/show-command-doc?) command-doc)]
-            (cond
-              (or plugin-id (vector? doc))
-              [:div.has-help
-               command-name
-               (when doc (ui/tippy
-                          {:html            doc
-                           :interactive     true
+(rum/defcs commands < rum/reactive
+  (rum/local [] ::matched-commands)
+  [s id format]
+  (let [matched' (util/react *matched-commands)
+        *matched (::matched-commands s)
+        _ (when (state/get-editor-action)
+            (reset! *matched matched'))
+        matched @*matched]
+    (ui/auto-complete
+      matched
+      {:get-group-name
+       (fn [item]
+         (when (= (count item) 4) (last item)))
+
+       :item-render
+       (fn [item]
+         (let [command-name (first item)
+               command-doc (get item 2)
+               plugin-id (get-in item [1 1 1 :pid])
+               doc (when (state/show-command-doc?) command-doc)]
+           (cond
+             (or plugin-id (vector? doc))
+             [:div.has-help
+              command-name
+              (when doc (ui/tippy
+                          {:html doc
+                           :interactive true
                            :fixed-position? true
-                           :position        "right"}
+                           :position "right"}
 
                           [:small (svg/help-circle)]))
-               (when plugin-id
-                 [:small {:title (str plugin-id)} (ui/icon "puzzle")])]
-
-              (string? doc)
-              [:div {:title doc}
-               command-name]
-
-              :else
-              [:div command-name])))
-
-        :on-chosen
-        (fn [chosen-item]
-          (let [command (first chosen-item)]
-            (reset! commands/*current-command command)
-            (let [command-steps  (get (into {} matched) command)
-                  restore-slash? (or
+              (when plugin-id
+                [:small {:title (str plugin-id)} (ui/icon "puzzle")])]
+
+             (string? doc)
+             [:div {:title doc}
+              command-name]
+
+             :else
+             [:div command-name])))
+
+       :on-chosen
+       (fn [chosen-item]
+         (let [command (first chosen-item)]
+           (reset! commands/*current-command command)
+           (let [command-steps (get (into {} matched) command)
+                 restore-slash? (or
                                   (contains? #{"Today" "Yesterday" "Tomorrow" "Current time"} command)
                                   (and
-                                   (not (fn? command-steps))
-                                   (not (contains? (set (map first command-steps)) :editor/input))
-                                   (not (contains? #{"Date picker" "Template" "Deadline" "Scheduled" "Upload an image"} command))))]
-              (editor-handler/insert-command! id command-steps
-                                              format
-                                              {:restore? restore-slash?
-                                               :command  command}))))
-        :class
-        "black"}))))
+                                    (not (fn? command-steps))
+                                    (not (contains? (set (map first command-steps)) :editor/input))
+                                    (not (contains? #{"Date picker" "Template" "Deadline" "Scheduled" "Upload an image"} command))))]
+             (editor-handler/insert-command! id command-steps
+               format
+               {:restore? restore-slash?
+                :command command}))))
+       :class
+       "black"})))
 
 (rum/defc block-commands < rum/reactive
   [id format]
@@ -426,35 +431,34 @@
             (let [[id _on-submit on-cancel] (:rum/args state)]
               (on-cancel id)))})))
   [state _id on-submit _on-cancel]
-  (when (= :input (state/sub :editor/action))
-    (when-let [action-data (state/sub :editor/action-data)]
-      (let [{:keys [pos options]} action-data
-            input-value (get state ::input-value)]
-        (when (seq options)
-          (let [command (:command (first options))]
-            [:div.p-2.rounded-md.shadow-lg
-             (for [{:keys [id placeholder type autoFocus] :as input-item} options]
-               [:div.my-3 {:key id}
-                [:input.form-input.block.w-full.pl-2.sm:text-sm.sm:leading-5
-                 (merge
-                  (cond->
-                    {:key           (str "modal-input-" (name id))
-                     :id            (str "modal-input-" (name id))
-                     :type          (or type "text")
-                     :on-change     (fn [e]
-                                      (swap! input-value assoc id (util/evalue e)))
-                     :auto-complete (if (util/chrome?) "chrome-off" "off")}
-                    placeholder
-                    (assoc :placeholder placeholder)
-                    autoFocus
-                    (assoc :auto-focus true))
-                  (dissoc input-item :id))]])
-             (ui/button
-               "Submit"
-               :on-click
-               (fn [e]
-                 (util/stop e)
-                 (on-submit command @input-value pos)))]))))))
+  (when-let [action-data (state/get-editor-action-data)]
+    (let [{:keys [pos options]} action-data
+          input-value (get state ::input-value)]
+      (when (seq options)
+        (let [command (:command (first options))]
+          [:div.p-2.rounded-md
+           (for [{:keys [id placeholder type autoFocus] :as input-item} options]
+             [:div.my-3 {:key id}
+              [:input.form-input.block.w-full.pl-2.sm:text-sm.sm:leading-5
+               (merge
+                 (cond->
+                   {:key (str "modal-input-" (name id))
+                    :id (str "modal-input-" (name id))
+                    :type (or type "text")
+                    :on-change (fn [e]
+                                 (swap! input-value assoc id (util/evalue e)))
+                    :auto-complete (if (util/chrome?) "chrome-off" "off")}
+                   placeholder
+                   (assoc :placeholder placeholder)
+                   autoFocus
+                   (assoc :auto-focus true))
+                 (dissoc input-item :id))]])
+           (ui/button
+             "Submit"
+             :on-click
+             (fn [e]
+               (util/stop e)
+               (on-submit command @input-value pos)))])))))
 
 (rum/defc absolute-modal < rum/static
   [cp modal-name set-default-width? {:keys [top left rect]}]
@@ -748,6 +752,11 @@
                         )} query)]
      [:p "input key: " (shui/badge (some-> keydown-e (.-key)))]]))
 
+(defn- exist-editor-commands-popup?
+  []
+  (some->> (shui-popup/get-popups)
+    (some #(some-> % (:id) (str) (string/starts-with? ":editor.commands")))))
+
 ;; TODO: [WIP]
 (rum/defc shui-models
   [id format action _data]
@@ -756,6 +765,55 @@
       (let [{:keys [left top rect]} (cursor/get-caret-pos (state/get-input))
             pos [(+ left (:left rect) -20) (+ top (:top rect) 20)]]
         (let [pid (case action
+                    :commands
+                    (shui/popup-show!
+                      pos
+                      (commands id format)
+                      {:id :editor.commands/commands
+                       :align :start
+                       :root-props {:onOpenChange
+                                    #(when-not %
+                                       (when (= :commands (state/get-editor-action))
+                                         (state/clear-editor-action!)))}
+                       :content-props {:onOpenAutoFocus #(.preventDefault %)
+                                       :onCloseAutoFocus #(.preventDefault %)
+                                       :data-editor-popup-ref "commands"}
+                       :force-popover? true})
+
+                    :datepicker
+                    (shui/popup-show!
+                      pos (datetime-comp/date-picker id format nil)
+                      {:id :editor.commands/datepicker
+                       :align :start
+                       :root-props {:onOpenChange #(when-not % (state/clear-editor-action!))}
+                       :content-props {:onOpenAutoFocus #(.preventDefault %)
+                                       :data-editor-popup-ref "datepicker"}
+                       :force-popover? true})
+
+                    :input
+                    (shui/popup-show!
+                      pos (input id
+                            (fn [command m]
+                              (editor-handler/handle-command-input command id format m))
+                            (fn []
+                              (editor-handler/handle-command-input-close id)))
+                      {:id :editor.commands/input
+                       :align :start
+                       :root-props {:onOpenChange #(when-not % (state/clear-editor-action!))}
+                       :content-props {:onOpenAutoFocus #(.preventDefault %)
+                                       :onCloseAutoFocus #(.preventDefault %)
+                                       :data-editor-popup-ref "input"}})
+
+                    :select-code-block-mode
+                    (shui/popup-show!
+                      pos (code-block-mode-picker id format)
+                      {:id :editor.commands/code-block-mode-picker
+                       :align :start
+                       :root-props {:onOpenChange #(when-not % (state/clear-editor-action!))}
+                       :content-props {:onOpenAutoFocus #(.preventDefault %)
+                                       :data-editor-popup-ref "code-block-mode-picker"}
+                       :force-popover? true})
+
                     :editor.action/ask-ai
                     (shui/popup-show!
                       pos (editor-action-query-wrap
@@ -764,12 +822,10 @@
                               (ask-ai-content query
                                 {:id id :format format :action action :keydown-e keydown-e}))
                             {:sub-input-keydown? true})
-                      {:align :start
-                       :root-props {:onOpenChange
-                                    #(when-not %
-                                       (state/clear-editor-action!))}
-                       :content-props {:onOpenAutoFocus
-                                       #(.preventDefault %)}
+                      {:id :editor.commands/ask-ai
+                       :align :start
+                       :root-props {:onOpenChange #(when-not % (state/clear-editor-action!))}
+                       :content-props {:onOpenAutoFocus #(.preventDefault %)}
                        :force-popover? true})
 
                     ;; TODO: try remove local model state
@@ -786,7 +842,7 @@
     [:<>
      (shui-models id format action nil)
      (cond
-       (= action :commands)
+       (= action :commands-classic)
        (animated-modal "commands" (commands id format) true)
 
        (= action :block-commands)
@@ -808,13 +864,13 @@
        (animated-modal "property-value-search" (property-value-search id) true)
 
        ;; date-picker in editing-mode
-       (= :datepicker action)
+       (= :datepicker-classic action)
        (animated-modal "date-picker" (datetime-comp/date-picker id format nil) false)
 
-       (= :select-code-block-mode action)
+       (= :select-code-block-mode-classic action)
        (animated-modal "select-code-block-mode" (code-block-mode-picker id format) true)
 
-       (= :input action)
+       (= :input-classic action)
        (animated-modal "input" (input id
                                  (fn [command m]
                                    (editor-handler/handle-command-input command id format m))
@@ -878,9 +934,10 @@
                                     (if-let [on-key-down (:on-key-down config)]
                                       (on-key-down e)
                                       (when (and (= (util/ekey e) "Escape") on-hide)
-                                        (on-hide content :esc))))
-               :auto-focus        true
-               :class             heading-class}
+                                        (when-not (exist-editor-commands-popup?)
+                                          (on-hide content :esc)))))
+               :auto-focus true
+               :class heading-class}
                (some? parent-block)
                (assoc :parentblockid (str (:block/uuid parent-block)))
 

+ 30 - 7
src/main/frontend/components/editor.css

@@ -11,15 +11,11 @@
 }
 
 .editor-inner {
-  position: relative;
-  display: flex;
+  @apply relative flex;
 
   textarea {
-    border: none;
-    border-radius: 0;
-    background: transparent;
-    padding: 0;
-    resize: none;
+    @apply border-none rounded-none bg-transparent p-0 resize-none;
+
     box-shadow: none;
   }
 }
@@ -90,3 +86,30 @@ pre {
     width: 135px;
   }
 }
+
+.ui__popover-content, .ui__dropdown-menu-content {
+  &[data-editor-popup-ref] {
+    @apply p-1 w-72;
+
+    &[data-side=top] {
+      position: relative;
+      top: -18px;
+    }
+  }
+
+  &[data-editor-popup-ref=commands] {
+    @apply px-1 py-1 w-72;
+
+    &[data-side=top] {
+      max-height: min(calc(var(--radix-popover-content-available-height) - 60px), 460px);
+    }
+
+    &[data-side=bottom] {
+      max-height: min(calc(var(--radix-popover-content-available-height) - 20px), 480px);
+    }
+  }
+
+  &[data-editor-popup-ref=datepicker] {
+    @apply w-auto;
+  }
+}

+ 1 - 1
src/main/frontend/components/find_in_page.cljs

@@ -50,7 +50,7 @@
       :on-hide (fn []
                  (search-handler/electron-exit-find-in-page!)))))
   [{:keys [matches match-case? q]}]
-  [:div#search-in-page.flex.flex-row.absolute.top-10.right-4.shadow-lg.px-2.py-1.faster-fade-in.items-center
+  [:div#search-in-page.flex.flex-row.absolute.top-10.right-4.shadow-lg.px-2.py-1.faster.fade-in.items-center
 
    (search-input q matches)
 

+ 6 - 1
src/main/frontend/components/page.cljs

@@ -150,7 +150,12 @@
 (rum/defc add-button
   [args]
   [:div.flex-1.flex-col.rounded-sm.add-button-link-wrap
-   {:on-click (fn [] (editor-handler/api-insert-new-block! "" args))}
+   {:on-click (fn [] (editor-handler/api-insert-new-block! "" args))
+    :on-key-down (fn [e]
+                    (when (= "Enter" (util/ekey e))
+                      (editor-handler/api-insert-new-block! "" args))
+                    (util/stop e))
+    :tab-index 0}
    [:div.flex.flex-row
     [:div.block {:style {:height      20
                          :width       20

+ 2 - 4
src/main/frontend/components/plugins.cljs

@@ -620,8 +620,7 @@
                                      (js/apis.openPath root))}}])
 
                     [{:hr true :key "dropdown-more"}
-                     {:title (auto-check-for-updates-control)
-                      :options {:no-padding? true}}])]
+                     {:title (auto-check-for-updates-control)}])]
 
         (ui/button
           (ui/icon "dots-vertical")
@@ -1081,8 +1080,7 @@
                            :icon (ui/icon "download")})]
 
                        [{:hr true :key "dropdown-more"}
-                        {:title (auto-check-for-updates-control)
-                         :options {:no-padding? true}}])
+                        {:title (auto-check-for-updates-control)}])
                   (remove nil?)))]
 
     [:div.toolbar-plugins-manager

+ 3 - 3
src/main/frontend/components/query.cljs

@@ -51,7 +51,7 @@
            view-f
            result
            group-by-page?]}]
-  (let [{:keys [->hiccup ->elem inline-text page-cp map-inline inline]} config
+  (let [{:keys [->hiccup ->elem inline-text page-cp map-inline]} config
         *query-error query-error-atom
         only-blocks? (:block/uuid (first result))
         blocks-grouped-by-page? (and group-by-page?
@@ -78,10 +78,10 @@
            (util/hiccup-keywordize result))
 
          page-list?
-         (query-table/result-table config current-block result {:page? true} map-inline page-cp ->elem inline-text inline)
+         (query-table/result-table config current-block result {:page? true} map-inline page-cp ->elem inline-text)
 
          table?
-         (query-table/result-table config current-block result {:page? false} map-inline page-cp ->elem inline-text inline)
+         (query-table/result-table config current-block result {:page? false} map-inline page-cp ->elem inline-text)
 
          (and (seq result) (or only-blocks? blocks-grouped-by-page?))
          (->hiccup result

+ 8 - 23
src/main/frontend/components/query_table.cljs

@@ -8,19 +8,16 @@
             [frontend.format.block :as block]
             [frontend.handler.common :as common-handler]
             [frontend.handler.property :as property-handler]
-            [frontend.shui :refer [get-shui-component-version make-shui-context]]
             [frontend.state :as state]
             [frontend.util :as util]
             [frontend.util.clock :as clock]
             [frontend.handler.file-based.property :as file-property-handler]
-            [logseq.shui.core :as shui]
             [medley.core :as medley]
             [rum.core :as rum]
             [promesa.core :as p]
             [logseq.graph-parser.text :as text]
             [logseq.db.frontend.property :as db-property]
             [frontend.handler.property.util :as pu]
-            [frontend.handler.db-based.property.util :as db-pu]
             [logseq.db.frontend.content :as db-content]))
 
 ;; Util fns
@@ -290,7 +287,7 @@
                                          :db-graph? db-graph?}))]))]))]]]))
 
 (rum/defc result-table < rum/reactive
-  [config current-block result {:keys [page?] :as options} map-inline page-cp ->elem inline-text inline]
+  [config current-block result {:keys [page?] :as options} map-inline page-cp ->elem inline-text]
   (when current-block
     (let [db-graph? (config/db-based-graph? (state/get-current-repo))
           result' (cond-> (if page? result (attach-clock-property result))
@@ -298,27 +295,15 @@
                     ((fn [res]
                        (map #(if (:block/content %)
                                (update % :block/content
-                                       db-content/special-id-ref->page-ref
-                                       ;; Lookup here instead of initial query as advanced queries
-                                       ;; won't usually have a ref's name
-                                       (map (fn [m] (db/entity (:db/id m))) (:block/refs %)))
+                                 db-content/special-id-ref->page-ref
+                                 ;; Lookup here instead of initial query as advanced queries
+                                 ;; won't usually have a ref's name
+                                 (map (fn [m] (db/entity (:db/id m))) (:block/refs %)))
                                %)
-                            res))))
+                         res))))
           columns (get-columns current-block result' {:page? page?})
           ;; Sort state needs to be in sync between final result and sortable title
           ;; as user needs to know if there result is sorted
           sort-state (get-sort-state current-block {:db-graph? db-graph?})
-          sort-result (sort-result result' (assoc sort-state :page? page?))
-          table-version (get-shui-component-version :table config)]
-      (case table-version
-        2 (let [v2-columns (mapv #(if (uuid? %) (db-pu/get-property-name %) %) columns)
-                v2-config (cond-> config
-                            db-graph?
-                            (assoc-in [:block :properties]
-                                      (db-pu/readable-properties (get-in config [:block :block/properties]))))
-                result-as-text (for [row result']
-                                 (for [column columns]
-                                   (build-column-text row column)))]
-            (shui/table-v2 {:data (conj [[v2-columns]] result-as-text)}
-                           (make-shui-context v2-config inline)))
-        1 (result-table-v1 config current-block sort-result sort-state columns options map-inline page-cp ->elem inline-text)))))
+          sort-result (sort-result result' (assoc sort-state :page? page?))]
+      (result-table-v1 config current-block sort-result sort-state columns options map-inline page-cp ->elem inline-text))))

+ 2 - 2
src/main/frontend/components/reference.cljs

@@ -126,7 +126,7 @@
 
 (rum/defc references-inner
   [page-name filters filtered-ref-blocks]
-  [:div.references-blocks.faster-fade-in
+  [:div.references-blocks.faster.fade-in
    (let [ref-hiccup (block/->hiccup filtered-ref-blocks
                                     {:id page-name
                                      :ref? true
@@ -293,7 +293,7 @@
   [state page]
   (let [n-ref (get state ::n-ref)]
     (when page
-      [:div.references.page-unlinked.mt-6.flex-1.flex-row.faster-fade-in
+      [:div.references.page-unlinked.mt-6.flex-1.flex-row.faster.fade-in
        [:div.content.flex-1
         (ui/foldable
          [:h2.font-medium (t :unlinked-references/reference-count @n-ref)]

+ 24 - 23
src/main/frontend/components/right_sidebar.cljs

@@ -143,15 +143,11 @@
 (defonce *drag-from
   (atom nil))
 
-(rum/defc x-menu-content
-  [db-id idx type collapsed? block-count toggle-fn as-dropdown?]
-  (let [menu-content (if as-dropdown? shui/dropdown-menu-content shui/context-menu-content)
-        menu-item (if as-dropdown? shui/dropdown-menu-item shui/context-menu-content)
-        multi-items? (> block-count 1)]
-
-    (menu-content
-     {:on-click toggle-fn :class "w-48" :align "end"}
-
+(rum/defc actions-menu-content
+  [db-id idx type collapsed? block-count]
+  (let [multi-items? (> block-count 1)
+        menu-item shui/dropdown-menu-item]
+    [:<>
      (menu-item {:on-click #(state/sidebar-remove-block! idx)} (t :right-side-bar/pane-close))
      (when multi-items? (menu-item {:on-click #(state/sidebar-remove-rest! db-id)} (t :right-side-bar/pane-close-others)))
      (when multi-items? (menu-item {:on-click (fn []
@@ -167,7 +163,7 @@
      (when (= type :page) [:hr.menu-separator])
      (when (= type :page)
        (let [page  (db/entity db-id)]
-         (menu-item {:href (rfe/href :page {:name (str (:block/uuid page))})} (t :right-side-bar/pane-open-as-page)))))))
+         (menu-item {:href (rfe/href :page {:name (str (:block/uuid page))})} (t :right-side-bar/pane-open-as-page))))]))
 
 (rum/defc drop-indicator
   [idx drag-to]
@@ -211,6 +207,10 @@
              [:.flex.flex-row.justify-between.pr-2.sidebar-item-header.color-level.rounded-t-md
               {:class         (when collapsed? "rounded-b-md")
                :draggable     true
+               :on-context-menu #(shui/popup-show! %
+                                   (actions-menu-content db-id idx block-type collapsed? block-count)
+                                   {:as-dropdown? true
+                                    :content-props {:on-click (fn [] (shui/popup-hide!))}})
                :on-drag-start (fn [event]
                                 (editor-handler/block->data-transfer! (:block/name (db/entity db-id)) event true)
                                 (reset! *drag-from idx))
@@ -234,25 +234,26 @@
                [:div.ml-1.font-medium.overflow-hidden.whitespace-nowrap
                 title]]
               [:.item-actions.flex.items-center
-               (shui/dropdown-menu
-                 (shui/dropdown-menu-trigger
-                   {:as-child true}
-                   (shui/button
-                     {:title   (t :right-side-bar/pane-more)
-                      :class   "px-3"
-                      :variant :text}
-                     (ui/icon "dots")))
-                 (x-menu-content db-id idx block-type collapsed? block-count #() true))
+               (shui/button
+                 {:title (t :right-side-bar/pane-more)
+                  :class "px-3"
+                  :variant :text
+                  :on-click #(shui/popup-show!
+                               (.-target %)
+                               (actions-menu-content db-id idx block-type collapsed? block-count)
+                               {:as-dropdown? true
+                                :content-props {:on-click (fn [] (shui/popup-hide!))}})}
+                 (ui/icon "dots"))
 
                (shui/button
-                 {:title    (t :right-side-bar/pane-close)
-                  :variant  :text
+                 {:title (t :right-side-bar/pane-close)
+                  :variant :text
                   :class "px-3"
                   :on-click #(state/sidebar-remove-block! idx)}
                  (ui/icon "x"))]]
 
-             [:div {:role            "region"
-                    :id              (str "sidebar-panel-content-" idx)
+             [:div {:role "region"
+                    :id (str "sidebar-panel-content-" idx)
                     :aria-labelledby (str "sidebar-panel-header-" idx)
                     :class           (util/classnames [{:hidden  collapsed?
                                                         :initial (not collapsed?)

+ 2 - 3
src/main/frontend/components/shortcut.cljs

@@ -15,8 +15,7 @@
             [frontend.modules.shortcut.data-helper :as dh]
             [frontend.util :as util]
             [frontend.modules.shortcut.utils :as shortcut-utils]
-            [frontend.modules.shortcut.config :as shortcut-config]
-            [logseq.shui.core :as shui-core])
+            [frontend.modules.shortcut.config :as shortcut-config])
   (:import [goog.events KeyHandler]))
 
 (defonce categories
@@ -477,6 +476,6 @@
 
                         (not unset?)
                         [:code.flex.items-center.bg-transparent
-                         (shui-core/shortcut
+                         (shui/shortcut
                            (string/join " | " (map #(dh/binding-for-display id %) binding))
                            {:size :md :interactive? true})])]]))))])])]]))

+ 1 - 1
src/main/frontend/components/shortcut_help.cljs

@@ -8,7 +8,7 @@
             [logseq.common.util.page-ref :as page-ref]
             [rum.core :as rum]
             [frontend.components.shortcut :as shortcut]
-            [logseq.shui.core :as shui]))
+            [logseq.shui.ui :as shui]))
 
 (rum/defc trigger-table []
   [:table

+ 18 - 6
src/main/frontend/extensions/code.cljs

@@ -155,8 +155,8 @@
 (set! js/window -CodeMirror CodeMirror)
 
 
-(defn- all-tokens-by-cursur
-  "All tokens from the beginning of the document to the cursur(inclusive)."
+(defn- all-tokens-by-cursor
+  "All tokens from the beginning of the document to the cursor(inclusive)."
   [cm]
   (let [cur (.getCursor cm)
         line (.-line cur)
@@ -236,7 +236,7 @@
 (defn- doc-state-at-cursor
   "Parse tokens into document state of last token."
   [cm]
-  (let [tokens (all-tokens-by-cursur cm)
+  (let [tokens (all-tokens-by-cursor cm)
         {:keys [current-config-path state-stack]} (tokens->doc-state tokens)
         doc-state (first state-stack)]
     [current-config-path doc-state]))
@@ -448,14 +448,26 @@
 
         (.addEventListener element "keydown" (fn [e]
                                                (let [key-code (.-code e)
-                                                     meta-or-ctrl-pressed? (or (.-ctrlKey e) (.-metaKey e))]
-                                                 (when meta-or-ctrl-pressed?
+                                                     meta-or-ctrl-pressed? (or (.-ctrlKey e) (.-metaKey e))
+                                                     shifted? (.-shiftKey e)]
+                                                 (cond
+                                                   meta-or-ctrl-pressed?
                                                    ;; prevent default behavior of browser
                                                    ;; Cmd + [ => Go back in browser, outdent in CodeMirror
                                                    ;; Cmd + ] => Go forward in browser, indent in CodeMirror
                                                    (case key-code
                                                      "BracketLeft" (util/stop e)
                                                      "BracketRight" (util/stop e)
+                                                     nil)
+                                                   shifted?
+                                                   (case key-code
+                                                     "Enter"
+                                                    (when-let [blockid (some-> (.-target e) (.closest "[blockid]") (.getAttribute "blockid"))]
+                                                      (code-handler/save-code-editor!)
+                                                      (js/setTimeout
+                                                        #(editor-handler/api-insert-new-block! ""
+                                                           {:block-uuid (uuid blockid)
+                                                            :sibling? true}) 32))
                                                      nil)))))
         (.addEventListener element "pointerdown"
                            (fn [e]
@@ -543,4 +555,4 @@
              textarea-ref (.querySelector block-node "textarea")]
          (when-let [codemirror-ref (gobj/get textarea-ref codemirror-ref-name)]
            (.focus codemirror-ref))))
-     100)))
+     256)))

+ 3 - 3
src/main/frontend/handler/editor.cljs

@@ -1788,8 +1788,7 @@
   (state/set-editor-show-input! nil)
   (when-let [saved-cursor (state/get-editor-last-pos)]
     (when-let [input (gdom/getElement id)]
-      (.focus input)
-      (cursor/move-cursor-to input saved-cursor))))
+      (cursor/move-cursor-to input saved-cursor true))))
 
 (defn handle-command-input [command id format m]
   ;; TODO: Add error handling for when user doesn't provide a required field.
@@ -3263,7 +3262,8 @@
         (state/selection?)
         (select-up-down direction)
 
-        ;; if there is an edit-input-id set, we are probably still on editing mode, that is not fully initialized
+        ;; if there is an edit-input-id set, we are probably still on editing mode,
+        ;; that is not fully initialized
         (not (state/get-edit-input-id))
         (select-first-last direction)))
     nil))

+ 8 - 7
src/main/frontend/handler/events.cljs

@@ -295,9 +295,9 @@
                    state)}
   [block shown-properties all-properties]
   (let [query-properties (rum/react *query-properties)]
-    [:div.p-4
-     [:div.font-bold (t :query/config-property-settings)]
-     [:div.flex
+    [:div
+     [:h1.font-semibold.-mt-2.mb-2.text-lg (t :query/config-property-settings)]
+     [:a.flex
       {:title "Refresh list of columns"
        :on-click
        (fn []
@@ -310,8 +310,8 @@
              shown? (if (nil? property-value)
                       (contains? shown-properties property)
                       property-value)]
-         [:div.flex.flex-row.m-2.justify-between.align-items
-          [:div (if (keyword? property) (db-pu/get-property-name property) (name property))]
+         [:div.flex.flex-row.my-2.justify-between.align-items
+          [:div (if (uuid? property) (db-pu/get-property-name property) (name property))]
           [:div.mt-1 (ui/toggle shown?
                                 (fn []
                                   (let [value (not shown?)]
@@ -339,8 +339,9 @@
                            (set block-properties)
                            (set all-properties))
         shown-properties (set/intersection (set all-properties) shown-properties)]
-    (state/set-modal! (query-properties-settings block shown-properties all-properties)
-      {:center? true})))
+    (shui/dialog-open!
+      (query-properties-settings block shown-properties all-properties)
+      {})))
 
 (defmethod handle :modal/show-cards [_]
   (state/set-modal! srs/global-cards {:id :srs

+ 1 - 1
src/main/frontend/mobile/graph_picker.cljs

@@ -125,7 +125,7 @@
 
        ;; step 1
        :new-graph
-       [:div.flex.flex-col.w-full.space-y-3.faster-fade-in
+       [:div.flex.flex-col.w-full.space-y-3.faster.fade-in
         [:input.form-input.block
          {:auto-focus  true
           :ref         *input-ref

+ 0 - 46
src/main/frontend/shui.cljs

@@ -1,46 +0,0 @@
-(ns frontend.shui
-  "Glue between frontend code and deps/shui for convenience"
-  (:require
-    [frontend.colors :as colors]
-    [frontend.date :refer [int->local-time-2]]
-    [frontend.db :as db]
-    [frontend.db.utils :as db-utils]
-    [frontend.handler.search :as search-handler]
-    [frontend.state :as state]
-    [frontend.handler.property.util :as pu]
-    [logseq.shui.context :refer [make-context]]))
-
-
-(def default-versions {:logseq.property.table/version 1})
-
-(defn get-shui-component-version
-  "Returns the version of the shui component, checking first
-  the block properties, then the global config, then the defaults."
-  [component-name block-config]
-  (let [version-key (keyword (str "logseq.property." (name component-name))
-                             "version")]
-    (js/parseFloat
-      (or (pu/lookup (get-in block-config [:block :block/properties]) version-key)
-          (get-in (state/get-config) [version-key])
-          (get-in default-versions [version-key])
-          1))))
-
-(defn make-shui-context
-  ([] (make-shui-context nil nil))
-  ([block-config inline]
-   (make-context {:block-config block-config
-                  :config (state/get-config)
-                  :inline inline
-                  :int->local-time-2 int->local-time-2
-                  :search search-handler/search
-                  ;; We need some variable from the state to carry over
-                  :state state/state
-                  :get-current-repo state/get-current-repo
-                  :color-accent (state/get-color-accent)
-                  ;; Pass over ability to look up entities
-                  :entity db-utils/entity
-                  :get-block-and-children db/get-block-and-children
-                  :get-block-children db/get-block-children
-                  :get-page-blocks-no-cache db/get-page-blocks-no-cache
-                  :get-page db/get-page
-                  :linear-gradient colors/linear-gradient})))

+ 0 - 3
src/main/frontend/state.cljs

@@ -2301,9 +2301,6 @@ Similar to re-frame subscriptions"
    (r/cached-derived-atom (:db/async-queries @state) [(get-current-repo) ::async-query (str k)]
                           (fn [s] (contains? s (str k))))))
 
-(defn get-color-accent []
-  (get @state :ui/radix-color))
-
 (defn set-color-accent! [color]
   (swap! state assoc :ui/radix-color color)
   (storage/set :ui/radix-color color)

+ 3 - 4
src/main/frontend/ui.cljs

@@ -7,7 +7,6 @@
             ["react-tippy" :as react-tippy]
             ["react-transition-group" :refer [CSSTransition TransitionGroup]]
             ["@emoji-mart/data" :as emoji-data]
-            ;; ["@emoji-mart/react" :as Picker]
             ["emoji-mart" :as emoji-mart]
             [cljs-bean.core :as bean]
             [clojure.string :as string]
@@ -33,10 +32,10 @@
             [goog.functions :refer [debounce]]
             [goog.object :as gobj]
             [lambdaisland.glogi :as log]
+            [logseq.shui.icon.v2 :as shui.icon.v2]
             [medley.core :as medley]
             [promesa.core :as p]
             [rum.core :as rum]
-            [logseq.shui.core :as shui-core]
             [logseq.shui.ui :as shui]))
 
 (declare icon)
@@ -195,7 +194,7 @@
                        (string/split #" "))
                    sequence)]
     [:span.keyboard-shortcut
-     (shui-core/shortcut sequence opts)]))
+     (shui/shortcut sequence opts)]))
 
 (rum/defc menu-link
   [{:keys [only-child? no-padding? class shortcut] :as options} child]
@@ -1033,7 +1032,7 @@
              :options               {:theme (when (= (state/sub :ui/theme) "dark") "dark")}
              :on-tweet-load-success #(reset! *loading? false)})]]))
 
-(def icon shui-core/icon)
+(def icon shui.icon.v2/root)
 
 (rum/defc button-inner
   [text & {:keys [theme background variant href size class intent small? icon icon-props disabled? button-props]

+ 6 - 3
src/main/frontend/util/cursor.cljs

@@ -63,9 +63,12 @@
 (defn set-selection-to [input n m]
   (.setSelectionRange input n m))
 
-(defn move-cursor-to [input n]
-  (.focus input)
-  (.setSelectionRange input n n))
+(defn move-cursor-to
+  ([input n] (move-cursor-to input n false))
+  ([input n delay?]
+   (.setSelectionRange input n n)
+   (let [focus #(.focus input)]
+     (if delay? (js/setTimeout focus 16) (focus)))))
 
 (defn move-cursor-forward
   ([input]

+ 1 - 1
src/main/frontend/version.cljs

@@ -1,3 +1,3 @@
 (ns ^:no-doc frontend.version)
 
-(defonce version "0.10.8")
+(defonce version "0.10.9")

+ 12 - 10
src/main/frontend/worker/rtc/const.cljs

@@ -97,36 +97,38 @@
      [:multi {:dispatch :op :decode/string #(update % :op keyword)}
       [:move
        (apply conj
-              [:map {:closed true}
+              [:map
                [:op :keyword]
                [:self :uuid]
                [:parents [:sequential :uuid]]
                [:left [:maybe :uuid]]   ;nil when it's :no-order block
-               [:content {:optional true} :string]]
+               [:content {:optional true} :string]
+               [:hash {:optional true} :int]]
               general-attrs-schema-coll)]
       [:remove
-       [:map {:closed true}
+       [:map
         [:op :keyword]
         [:block-uuid :uuid]]]
       [:update-attrs
        (apply conj
-              [:map {:closed true}
+              [:map
                [:op :keyword]
                [:self :uuid]
                [:parents {:optional true} [:sequential :uuid]]
                [:left {:optional true} [:maybe :uuid]] ;nil when it's :no-order block
-               [:content {:optional true} :string]]
+               [:content {:optional true} :string]
+               [:hash {:optional true} :int]]
               general-attrs-schema-coll)]
       [:update-page
        (apply conj
-              [:map {:closed true}
+              [:map
                [:op :keyword]
                [:self :uuid]
                [:page-name :string]
                [:original-name :string]]
               general-attrs-schema-coll)]
       [:remove-page
-       [:map {:closed true}
+       [:map
         [:op :keyword]
         [:block-uuid :uuid]]]]]]
    [:ex-data {:optional true} [:map [:type :keyword]]]
@@ -217,12 +219,12 @@
       [:action :string]
       [:graph-uuid :string]
       [:block-uuids [:sequential :uuid]]]]
-    ["query-blocks"
+    ["query-block-tree"
      [:map
       [:req-id :string]
       [:action :string]
-      [:graph-uuid :uuid]
-      [:block-uuids [:sequential :uuid]]]]
+      [:graph-uuid :string]
+      [:root-block-uuid :uuid]]]
     ["update-assets"
      [:map
       [:req-id :string]

+ 2 - 8
src/main/frontend/worker/rtc/core.cljs

@@ -28,7 +28,8 @@
             [logseq.outliner.core :as outliner-core]
             [logseq.outliner.transaction :as outliner-tx]
             [malli.core :as m]
-            [malli.util :as mu]))
+            [malli.util :as mu]
+            [frontend.worker.rtc.ws2]))
 
 ;;                     +-------------+
 ;;                     |             |
@@ -1116,13 +1117,6 @@
           (prn ::<get-block-content-versions :ex-message ex-message :ex-data ex-data)
           (bean/->js versions))))))
 
-;; (defn- <query-page-blocks
-;;   [state page-block-uuid]
-;;   (go
-;;     (when (some-> state :*graph-uuid deref)
-;;       (<! (ws/<send&receive state {:action "query-blocks" :graph-uuid @(:*graph-uuid state)
-;;                                    :block-uuids [page-block-uuid]})))))
-
 (defn init-state
   [ws data-from-ws-chan token user-uuid dev-mode?]
   ;; {:post [(m/validate state-schema %)]}

+ 161 - 0
src/main/frontend/worker/rtc/ws2.cljs

@@ -0,0 +1,161 @@
+(ns frontend.worker.rtc.ws2
+  "Websocket wrapped by missionary.
+  based on
+  https://github.com/ReilySiegel/missionary-websocket/blob/master/src/com/reilysiegel/missionary/websocket.cljs"
+  {:clj-kondo/ignore true}
+  (:require [frontend.worker.rtc.const :as rtc-const]
+            [logseq.common.missionary-util :as c.m]
+            [missionary.core :as m]))
+
+(defn- get-state
+  [ws]
+  (case (.-readyState ws)
+    0 :connecting
+    1 :open
+    2 :closing
+    3 :closed))
+
+(defn- open-ws-task
+  [url]
+  (fn [s! f!]
+    (try
+      (let [ws (js/WebSocket. url)]
+        (set! (.-onopen ws)
+              (fn [_]
+                (let [close-dfv (m/dfv)
+                      mbx (m/mbx)]
+                  (set! (.-onopen ws) nil)
+                  (set! (.-onmessage ws) (fn [e] (mbx (.-data e))))
+                  (set! (.-onclose ws) (fn [e]
+                                         (set! (.-onclose ws) nil)
+                                         (close-dfv e)))
+                  (s! [mbx ws close-dfv]))))
+        (set! (.-onclose ws)
+              (fn [e]
+                (set! (.-onopen ws) nil)
+                (set! (.-onclose ws) nil)
+                (f! e)))
+        (fn canceller []
+          ;; canceller will be called(no gua) even this task succeed
+          ;; should only cancel :connecting state websocket
+          ;; see also some explanations from lib author about canceller:
+          ;; https://clojurians.slack.com/archives/CL85MBPEF/p1714323302110269
+          (when (= :connecting (get-state ws))
+            (.close ws))))
+      (catch :default e
+        (f! e) #(do)))))
+
+(defn- handle-close
+  [x]
+  (if (instance? js/CloseEvent x)
+    (throw x)
+    x))
+
+(defn- create-ws-task
+  [url]
+  (m/sp
+   (if-let [[mbx ws close-dfv] (m/? (m/timeout (open-ws-task url) 10000))]
+     {:raw-ws ws
+      :send (fn [data]
+              (m/sp
+               (handle-close
+                (m/?
+                 (m/race close-dfv
+                         (m/sp (while (< 4096 (.-bufferedAmount ws))
+                                 (m/? (m/sleep 50)))
+                               (.send ws data)))))))
+      :recv-flow
+      (m/stream
+       (m/ap
+        (loop []
+          (m/amb
+           (handle-close
+            (m/? (m/race close-dfv mbx)))
+           (recur)))))}
+     (throw (ex-info "open ws timeout(10s)" {:missionary/retry true})))))
+
+
+
+(defn- closed?
+  [m-ws]
+  (contains? #{:closing :closed} (get-state (:raw-ws m-ws))))
+
+(defn create-ws-flow
+  "Return a missionary-webocket flow.
+  Always produce NOT-closed websockets if possible.
+  If open&connect websocket failed, will retry with backoff(`c.m/delays`)
+  TODO: retry ASAP once network condition changed"
+  [url]
+  (let [*last-m-ws (atom nil)
+        new-m-ws-task
+        (c.m/backoff
+         (take 10 c.m/delays)
+         (m/sp (let [r (try
+                         (m/? (create-ws-task url))
+                         (catch js/CloseEvent e
+                           (throw (ex-info "failed to open ws conn" {:missionary/retry true} e))))]
+                 (reset! *last-m-ws r)
+                 r)))]
+    (m/stream
+     (m/ap
+       (loop []
+         (m/amb
+          (if-let [m-ws @*last-m-ws]
+            (if (closed? m-ws)
+              (m/? new-m-ws-task)
+              m-ws)
+            (m/? new-m-ws-task))
+          (recur)))))))
+
+(defn close
+  [m-ws]
+  (.close (:raw-ws m-ws)))
+
+(defn send-task
+  "return m-ws"
+  [ws-flow message]
+  (m/sp
+    (let [m-ws (m/? (m/reduce (fn [_ m-ws] (when m-ws (reduced m-ws))) ws-flow))
+          decoded-message (rtc-const/data-to-ws-coercer message)
+          message-str (js/JSON.stringify (clj->js (rtc-const/data-to-ws-encoder decoded-message)))]
+      (m/? ((:send m-ws) message-str))
+      m-ws)))
+
+(defn recv-flow
+  [m-ws]
+  (m/eduction
+   (map #(js->clj (js/JSON.parse %) :keywordize-keys true))
+   (:recv-flow m-ws)))
+
+(defn send&recv-task
+  [ws-flow message & {:keys [timeout-ms] :or {timeout-ms 10000}}]
+  (assert (pos-int? timeout-ms))
+  (let [req-id (str (random-uuid))
+        message (assoc message :req-id req-id)]
+    (m/sp
+      (let [m-ws (m/? (send-task ws-flow message))]
+        (m/? (m/timeout
+              (m/reduce
+               (fn [_ v]
+                 (when (= req-id (:req-id v))
+                   (reduced v)))
+               (recv-flow m-ws))
+              timeout-ms))))))
+
+(comment
+  (do
+    (def url "wss://ws-dev.logseq.com/rtc-sync?token=????")
+    (def ws-flow (create-ws-flow url)))
+  (def cancel1
+    ((m/reduce conj (m/eduction (take 1) ws-flow)) #(prn :s %) #(js/console.log %)))
+  (cancel1)
+
+  (do
+    (def cancel ((m/sp
+                   (m/? (send&recv-task ws-flow {:action "list-graphs"} :timeout-ms 1000))
+                   (m/? (send&recv-task ws-flow {:action "list-graphs"})))
+                 #(prn :s %) #(js/console.log :f %)))
+    (cancel))
+
+
+  )

+ 31 - 15
src/resources/dicts/it.edn

@@ -253,13 +253,13 @@
  :command.editor/select-block-down "Seleziona blocco sotto"
  :command.editor/delete-selection "Elimina i blocchi selezionati"
  :command.editor/expand-block-children "Espandi"
- :command.editor/collapse-block-children "Collassa"
+ :command.editor/collapse-block-children "Comprimi"
  :command.editor/indent "Indenta blocco"
  :command.editor/outdent "Diminuisci indentazione"
  :command.editor/copy "Copia (copia una selezione o un riferimento di blocco)"
  :command.editor/cut "Taglia"
  :command.editor/undo "Annulla"
- :command.editor/redo "Rifai"
+ :command.editor/redo "Ripeti"
  :command.editor/select-all-blocks "Seleziona tutti i blocchi"
  :command.editor/zoom-in "Ingrandisci blocco di modifica / Avanti altrimenti"
  :command.editor/zoom-out "Rimpicciolisci il blocco di modifica / Indietro altrimenti"
@@ -363,7 +363,7 @@
  :color/purple "Viola"
  :color/red "Rosso"
  :color/yellow "Giallo"
- :command.command-palette/toggle "Cerca una comando"
+ :command.command-palette/toggle "Cerca un comando"
  :command.dev/show-block-ast "(Dev) Mostra AST del blocco"
  :command.dev/show-block-data "(Dev) Mostra dati del blocco"
  :command.dev/show-page-ast "(Dev) Mostra AST della pagina"
@@ -386,8 +386,8 @@
  :command.sidebar/close-top "Chiudi l'elemento in alto nel pannello laterale destra"
  :command.ui/clear-all-notifications "Cancella tutte le notifiche"
  :command.ui/install-plugins-from-file "Installa plugin da plugins.edn"
- :command.whiteboard/bring-forward "Sposta in avanti"
- :command.whiteboard/bring-to-front "Sposta in fronte"
+ :command.whiteboard/bring-forward "Porta avanti"
+ :command.whiteboard/bring-to-front "Porta in primo piano"
  :command.whiteboard/connector "Connettore"
  :command.whiteboard/ellipse "Ellisse"
  :command.whiteboard/eraser "Gomma"
@@ -400,11 +400,11 @@
  :command.whiteboard/rectangle "Rettangolo"
  :command.whiteboard/reset-zoom "Reimposta zoom"
  :command.whiteboard/select "Seleziona zoom"
- :command.whiteboard/send-backward "Sposta indietro"
- :command.whiteboard/send-to-back "Sposta in retro"
+ :command.whiteboard/send-backward "Porta indietro"
+ :command.whiteboard/send-to-back "Porta in secondo piano"
  :command.whiteboard/text "Testo"
  :command.whiteboard/toggle-grid "Attiva/disattiva griglia"
- :command.whiteboard/ungroup "Dissocia selezione"
+ :command.whiteboard/ungroup "Separa selezione"
  :command.whiteboard/unlock "Sblocca selezione"
  :command.whiteboard/zoom-in "Aumenta zoom"
  :command.whiteboard/zoom-out "Diminuisci zoom"
@@ -696,8 +696,8 @@
  :whiteboard/link-to-any-page-or-block "Collega a qualsiasi pagina o blocco"
  :whiteboard/lock "Blocca"
  :whiteboard/medium "Medio"
- :whiteboard/move-to-back "Sposta sul retro"
- :whiteboard/move-to-front "Sposta sul fronte"
+ :whiteboard/move-to-back "Sposta indietro"
+ :whiteboard/move-to-front "Sposta avanti"
  :whiteboard/new-block "Nuovo blocco:"
  :whiteboard/new-block-no-colon "Nuovo blocco"
  :whiteboard/new-page "Nuova pagina:"
@@ -713,7 +713,7 @@
  :whiteboard/paste "Incolla"
  :whiteboard/paste-as-link "Incolla come link"
  :whiteboard/rectangle "Rettangolo"
- :whiteboard/redo "Rifai"
+ :whiteboard/redo "Ripeti"
  :whiteboard/references "Riferimento"
  :whiteboard/reload "Ricarica"
  :whiteboard/remove-link "Rimuovi link"
@@ -726,7 +726,7 @@
  :whiteboard/shape "Forma"
  :whiteboard/shape-quick-links "Link rapidi a forma"
  :whiteboard/small "Piccolo"
- :whiteboard/snap-to-grid "Aderisci alla griglia"
+ :whiteboard/snap-to-grid "Aggancia alla griglia"
  :whiteboard/start-typing-to-search "Inizia a scrivere per cercare..."
  :whiteboard/stroke-type "Tratto"
  :whiteboard/text "Testo"
@@ -734,8 +734,8 @@
  :whiteboard/toggle-pen-mode "Attiva/disattiva modalità penna"
  :whiteboard/triangle "Triangolo"
  :whiteboard/twitter-url "Indirizzo Twitter"
- :whiteboard/undo "Rifare"
- :whiteboard/ungroup "Annulla raggruppamento"
+ :whiteboard/undo "Annulla"
+ :whiteboard/ungroup "Separa selezione"
  :whiteboard/unlock "Blocca"
  :whiteboard/website-url "Link sito internet"
  :whiteboard/youtube-url "Link YouTube"
@@ -746,4 +746,20 @@
  :window/exit-fullscreen "Esci da schermo intero"
  :window/maximize "Massimizza"
  :window/minimize "Minimizza"
- :window/restore "Ripristina"}
+ :window/restore "Ripristina"
+ :command.editor/toggle-block-children "Espandi/Comprimi"
+ :command.ui/accent-color-reset "Reimposta colore principale"
+ :command.ui/accent-colors-picker "Seleziona colore principale"
+ :command.whiteboard/clone-down "Clona sotto"
+ :command.whiteboard/clone-left "Clona a sinistra"
+ :command.whiteboard/clone-right "Clona a destra"
+ :command.whiteboard/clone-up "Clone sopra"
+ :linked-references/filter-directions "Click per includere e Shift-Click per escludere. Clicca di nuovo per rimuovere."
+ :linked-references/filter-excludes "Esclude: "
+ :linked-references/filter-heading "Filtro"
+ :linked-references/filter-includes "Include: "
+ :linked-references/reference-count (fn [filtered-count total] (str (when filtered-count (str filtered-count " di ")) total (if (= total 1) " Riferimento Collegato" " Riferimenti Collegati")))
+ :linked-references/unexpected-error "Riferimenti Collegati: Errore inatteso. Per favore, re-indicizza prima il grafo."
+ :settings-page/git-commit-on-close "Esegui commit Git alla chiusura della finestra"
+ :unlinked-references/reference-count (fn [total] (str total (if (= total 1) " Riferimento Scollegato" " Riferimenti Scollegati")))
+ :whiteboard/reference-count (fn [refs-count] (if (= refs-count 1) "Riferimento" "Riferimenti"))}

+ 23 - 16
src/test/frontend/worker/fixtures.cljs

@@ -1,23 +1,11 @@
 (ns frontend.worker.fixtures
-  (:require [datascript.core :as d]
+  (:require ["fs" :as fs-node]
+            [datascript.core :as d]
             [frontend.db.conn :as conn]
             [frontend.test.helper :as test-helper]
             [frontend.worker.db-listener :as worker-db-listener]
-            [frontend.worker.undo-redo :as worker-undo-redo]))
-
-
-(defn listen-test-db-to-gen-undo-ops-fixture
-  [f]
-  (let [test-db-conn (conn/get-db test-helper/test-db-name-db-version false)]
-    (assert (some? test-db-conn))
-    (worker-undo-redo/clear-undo-redo-stack)
-    (worker-db-listener/listen-db-changes! test-helper/test-db-name-db-version test-db-conn
-                                           {:handler-keys [:gen-undo-ops
-                                                           ;; :sync-db-to-main-thread
-                                                           ]})
-
-    (f)
-    (d/unlisten! test-db-conn :frontend.worker.db-listener/listen-db-changes!)))
+            [frontend.worker.undo-redo :as worker-undo-redo]
+            [logseq.db.sqlite.util :as sqlite-util]))
 
 
 (defn listen-test-db-fixture
@@ -31,3 +19,22 @@
 
       (f)
       (d/unlisten! test-db-conn :frontend.worker.db-listener/listen-db-changes!))))
+
+(def ^:private *tx-log-name-index (atom 0))
+(defn listen-test-db-to-write-tx-log-json-file
+  "Write {:tx-log <tx-data-coll> :init-db <init-db>} to file 'tx-log-<index>.json'"
+  [f]
+  (let [test-db-conn (conn/get-db test-helper/test-db-name-db-version false)
+        init-db @test-db-conn
+        *tx-log (atom [])]
+    (d/listen! test-db-conn :collect-tx-data
+               (fn [{:keys [tx-data]}]
+                 (swap! *tx-log conj tx-data)))
+    (try
+      (f)
+      (finally
+        (let [file-name (str "tx-log-" @*tx-log-name-index ".json")]
+          (println "saving " file-name " ...")
+          (fs-node/writeFileSync file-name (sqlite-util/write-transit-str {:tx-log @*tx-log :init-db init-db}))
+          (swap! *tx-log-name-index inc))))
+    (d/unlisten! test-db-conn :collect-tx-data)))

+ 12 - 7
src/test/frontend/worker/undo_redo_test.cljs

@@ -27,7 +27,8 @@
 
 (use-fixtures :each
   start-and-destroy-db
-  worker-fixtures/listen-test-db-to-gen-undo-ops-fixture)
+  (worker-fixtures/listen-test-db-fixture [:gen-undo-ops])
+  worker-fixtures/listen-test-db-to-write-tx-log-json-file)
 
 
 (def ^:private gen-non-exist-block-uuid gen/uuid)
@@ -38,14 +39,18 @@
                   [non-exist-frequency gen-non-exist-block-uuid]]))
 
 (defn- gen-parent-left-pair
-  [db]
-  (gen/frequency [[9 (t.gen/gen-available-parent-left-pair db {:page-uuid page-uuid})]
-                  [1 (gen/vector gen-non-exist-block-uuid 2)]]))
+  [db self-uuid]
+  (gen/such-that
+   (fn [[parent left]]
+     (and (not= self-uuid left)
+          (not= self-uuid parent)))
+   (gen/frequency [[9 (t.gen/gen-available-parent-left-pair db {:page-uuid page-uuid})]
+                   [1 (gen/vector gen-non-exist-block-uuid 2)]])))
 
 (defn- gen-move-block-op
   [db]
   (gen/let [block-uuid (gen-block-uuid db)
-            [parent left] (gen-parent-left-pair db)]
+            [parent left] (gen-parent-left-pair db block-uuid)]
     [:frontend.worker.undo-redo/move-block
      {:block-uuid block-uuid
       :block-origin-left left
@@ -60,7 +65,7 @@
 (defn- gen-remove-block-op
   [db]
   (gen/let [block-uuid (gen-block-uuid db {:non-exist-frequency 90})
-            [parent left] (gen-parent-left-pair db)
+            [parent left] (gen-parent-left-pair db block-uuid)
             content gen/string-alphanumeric]
     [:frontend.worker.undo-redo/remove-block
      {:block-uuid block-uuid
@@ -118,7 +123,7 @@
     :frontend.worker.undo-redo/move-block
     (assert (= (:block-origin-left (second op))
                (:block/uuid (:block/left (d/entity current-db [:block/uuid (:block-uuid (second op))]))))
-            {:op op :tx-data (:tx-data tx) :x (keys tx)})
+            {:op op :entity (into {} (d/entity current-db [:block/uuid (:block-uuid (second op))]))})
 
     :frontend.worker.undo-redo/update-block
     (assert (some? (d/entity current-db [:block/uuid (:block-uuid (second op))]))