Browse Source

feat: collape/expand

Weihua Lu 4 years ago
parent
commit
afb93eca99

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

@@ -174,10 +174,10 @@
   [parent-state parent-id]
   [:div#mobile-editor-toolbar.bg-base-2.fix-ios-fixed-bottom
    [:button.bottom-action
-    {:on-click #(editor-handler/indent-outdent parent-state true)}
+    {:on-click #(editor-handler/indent-outdent true)}
     svg/indent-block]
    [:button.bottom-action
-    {:on-click #(editor-handler/indent-outdent parent-state false)}
+    {:on-click #(editor-handler/indent-outdent false)}
     svg/outdent-block]
    [:button.bottom-action
     {:on-click (editor-handler/move-up-down true)}

+ 8 - 6
src/main/frontend/db/model.cljs

@@ -837,12 +837,14 @@
       blocks)))
 
 (defn has-children?
-  [repo block-id]
-  (let [db (conn/get-conn repo)]
-    (when-let [block (db-utils/entity [:block/uuid block-id])]
-      ;; perf: early stop
-      (let [result (d/datoms db :avet :block/parent (:db/id block))]
-        (boolean (seq result))))))
+  ([block-id]
+   (has-children? (state/get-current-repo) block-id))
+  ([repo block-id]
+   (let [db (conn/get-conn repo)]
+     (when-let [block (db-utils/entity [:block/uuid block-id])]
+       ;; perf: early stop
+       (let [result (d/datoms db :avet :block/parent (:db/id block))]
+         (boolean (seq result)))))))
 
 ;; TODO: improve perf
 (defn with-children-refs

+ 173 - 64
src/main/frontend/handler/editor.cljs

@@ -1,5 +1,6 @@
 (ns frontend.handler.editor
   (:require [frontend.state :as state]
+            [clojure.walk :as w]
             [lambdaisland.glogi :as log]
             [frontend.db.model :as db-model]
             [frontend.db.utils :as db-utils]
@@ -950,7 +951,8 @@
                     [(conj r [id (assoc block :level (inc parent-level))])
                      (assoc-in state [(:db/id block) :level] (inc parent-level))]
                     [(conj r [id block])
-                     state])) [{} level-blocks-map] level-blocks-map)]
+                     state]))
+                [{} level-blocks-map] level-blocks-map)]
     level-blocks-map))
 
 (defn- blocks-vec->tree
@@ -1673,61 +1675,39 @@
                       :data [block]}]
             (db/refresh! repo opts)))))))
 
-(defn expand!
-  []
-  (when-let [current-block (state/get-edit-block)]
-    (remove-block-property! (:block/uuid current-block) :collapsed)))
-
-(defn collapse!
-  []
-  (when-let [current-block (state/get-edit-block)]
-    (set-block-property! (:block/uuid current-block) :collapsed true)))
-
-;; TODO:
-(defn cycle-collapse!
-  [e]
-  )
-
 ;; selections
 (defn on-tab
   "direction = :left|:right, only indent or outdent when blocks are siblings"
   [direction]
-  (fn [e]
-    (when-let [repo (state/get-current-repo)]
-      (let [blocks-dom-nodes (state/get-selection-blocks)
-            blocks (seq blocks-dom-nodes)]
-        (cond
-          (seq blocks)
-          (do
-            (let [lookup-refs (->> (map (fn [block] (when-let [id (dom/attr block "blockid")]
-                                                     [:block/uuid (medley/uuid id)])) blocks)
-                                   (remove nil?))
-                  blocks (db/pull-many repo '[*] lookup-refs)
-                  blocks (reorder-blocks blocks)
-                  end-node (get-top-level-end-node blocks)
-                  end-node-parent (tree/-get-parent end-node)
-                  top-level-nodes (->> (filter #(= (get-in end-node-parent [:data :db/id])
-                                                   (get-in % [:block/parent :db/id])) blocks)
-                                       (map outliner-core/block))]
-              (outliner-core/indent-outdent-nodes top-level-nodes (= direction :right))
-              (let [opts {:key :block/change
-                          :data blocks}]
-                (db/refresh! repo opts)
-                (let [blocks (doall
-                              (map
-                               (fn [block]
-                                 (when-let [id (gobj/get block "id")]
-                                   (when-let [block (gdom/getElement id)]
-                                     (dom/add-class! block "selected noselect")
-                                     block)))
-                               blocks-dom-nodes))]
-                  (state/set-selection-blocks! blocks)))))
-
-          (gdom/getElement "date-time-picker")
-          nil
-
-          :else
-          (cycle-collapse! e))))))
+  (when-let [repo (state/get-current-repo)]
+    (let [blocks-dom-nodes (state/get-selection-blocks)
+          blocks (seq blocks-dom-nodes)]
+      (cond
+        (seq blocks)
+        (do
+          (let [lookup-refs (->> (map (fn [block] (when-let [id (dom/attr block "blockid")]
+                                                    [:block/uuid (medley/uuid id)])) blocks)
+                                 (remove nil?))
+                blocks (db/pull-many repo '[*] lookup-refs)
+                blocks (reorder-blocks blocks)
+                end-node (get-top-level-end-node blocks)
+                end-node-parent (tree/-get-parent end-node)
+                top-level-nodes (->> (filter #(= (get-in end-node-parent [:data :db/id])
+                                                 (get-in % [:block/parent :db/id])) blocks)
+                                     (map outliner-core/block))]
+            (outliner-core/indent-outdent-nodes top-level-nodes (= direction :right))
+            (let [opts {:key :block/change
+                        :data blocks}]
+              (db/refresh! repo opts)
+              (let [blocks (doall
+                            (map
+                             (fn [block]
+                               (when-let [id (gobj/get block "id")]
+                                 (when-let [block (gdom/getElement id)]
+                                   (dom/add-class! block "selected noselect")
+                                   block)))
+                             blocks-dom-nodes))]
+                (state/set-selection-blocks! blocks)))))))))
 
 (defn- get-link
   [format link label]
@@ -2365,9 +2345,9 @@
         (delete-and-update input (dec current-pos) current-pos)))))
 
 (defn indent-outdent
-  [state indent?]
+  [indent?]
   (state/set-editor-op! :indent-outdent)
-  (let [{:keys [block block-parent-id value config]} (get-state)]
+  (let [{:keys [block]} (get-state)]
     (when block
       (let [current-node (outliner-core/block block)]
         (outliner-core/indent-outdent-nodes [current-node] indent?)
@@ -2378,16 +2358,25 @@
 
 (defn keydown-tab-handler
   [direction]
-  (fn [state e]
-    (let [input (state/get-input)
-          pos (:pos (util/get-caret-pos input))]
-      (when (and (not (state/get-editor-show-input))
-                 (not (state/get-editor-show-date-picker?))
-                 (not (state/get-editor-show-template-search?)))
-        (indent-outdent state (not (= :left direction)))
-        (and input pos
-             (when-let [input (state/get-input)]
-               (util/move-cursor-to input pos)))))))
+  (fn [e]
+    (cond
+      (state/editing?)
+      (let [input (state/get-input)
+            pos (:pos (util/get-caret-pos input))]
+        (when (and (not (state/get-editor-show-input))
+                   (not (state/get-editor-show-date-picker?))
+                   (not (state/get-editor-show-template-search?)))
+          (indent-outdent (not (= :left direction)))
+          (and input pos
+               (when-let [input (state/get-input)]
+                 (util/move-cursor-to input pos)))))
+
+      (state/selection?)
+      (do
+        (util/stop e)
+        (on-tab direction))
+
+      :else nil)))
 
 (defn keydown-not-matched-handler
   [format]
@@ -2805,3 +2794,123 @@
     (save-current-block! {:force? true})
     (util/forward-kill-word input)
     (state/set-edit-content! (state/get-edit-input-id) (.-value input))))
+
+(defn tree-seq-with-level
+  [branch? children root]
+  (let [walk (fn walk [level node]
+               (lazy-seq
+                (cons (assoc node :block/level level)
+                      (when (branch? node)
+                        (mapcat (partial walk (inc level)) (children node))))))]
+    (walk 1 root)))
+
+(defn all-blocks-with-level
+  "Return all blocks associated with correct level
+   if :collapse? true, return without any collapsed children
+   for example:
+   - a
+    - b (collapsed)
+     - c
+     - d
+    - e
+   return:
+    blocks
+    [{:block a :level 1}
+     {:block b :level 2}
+     {:block e :level 2}]"
+  [{:keys [collapse?] :or {collapse? false}}]
+  (let [page (state/get-current-page)]
+    (cond->>
+     (-> page
+         (db/get-page-blocks-no-cache)
+         (tree/blocks->vec-tree page))
+
+     collapse?
+     (w/postwalk
+      (fn [x]
+        (if (and (map? x) (-> x :block/properties :collapsed))
+          (assoc x :block/children [])
+          x)))
+
+     :default
+     (mapcat (fn [x] (tree-seq-with-level map? :block/children x)))
+
+     :default
+     (map (fn [x] (dissoc x :block/children))))))
+
+(defn collapse-block! [block-id]
+  (when (db-model/has-children? block-id)
+    (set-block-property! block-id :collapsed true)))
+
+(defn expand-block! [block-id]
+  (remove-block-property! block-id :collapsed))
+
+(defn expand!
+  []
+  (cond
+    (state/editing?)
+    (when-let [block-id (:block/uuid (state/get-edit-block))]
+      (expand-block! block-id))
+
+    (state/selection?)
+    (do
+      (->> (get-selected-blocks-with-children)
+           (map (fn [dom]
+                  (-> (dom/attr dom "blockid")
+                      medley/uuid
+                      expand-block!)))
+           doall)
+      (clear-selection! nil))
+
+    :else
+    ;; expand one level
+    (let [blocks-with-level (all-blocks-with-level {})
+          max-level (apply max (map :block/level blocks-with-level))]
+      (loop [level 1]
+        (if (> level max-level)
+          nil
+          (let [blocks-to-expand (->> blocks-with-level
+                                 (filter (fn [b] (= (:block/level b) level)))
+                                 (filter (fn [{:block/keys [properties]}]
+                                           (contains? properties :collapsed))))]
+            (if (empty? blocks-to-expand)
+              (recur (inc level))
+              (doseq [{:block/keys [uuid]} blocks-to-expand]
+                (expand-block! uuid)))))))))
+
+
+(defn collapse!
+  []
+  (cond
+    (state/editing?)
+    (when-let [block-id (:block/uuid (state/get-edit-block))]
+      (collapse-block! block-id))
+
+    (state/selection?)
+    (do
+      (->> (get-selected-blocks-with-children)
+           (map (fn [dom]
+                  (-> (dom/attr dom "blockid")
+                      medley/uuid
+                      collapse-block!)))
+           doall)
+      (clear-selection! nil))
+
+    :else
+    ;; collapse by one level from outside
+    (let [blocks-with-level
+          (all-blocks-with-level {:collapse? true})
+          max-level (apply max (map :block/level blocks-with-level))]
+      (loop [level (dec max-level)]
+        (if (zero? level)
+          nil
+          (let [blocks-to-collapse
+                (->> blocks-with-level
+                     (filter (fn [b] (= (:block/level b) level)))
+                     (remove (fn [b]
+                               (or (not (db-model/has-children? (:block/uuid b)))
+                                   (-> b :block/properties :collapsed)))))]
+            (if (empty? blocks-to-collapse)
+              (recur (dec level))
+              (doseq [{:block/keys [uuid]} blocks-to-collapse]
+                (collapse-block! uuid)))))))))

+ 18 - 26
src/main/frontend/modules/shortcut/config.cljs

@@ -59,14 +59,6 @@
     {:desc    "Delete / Delete forwards"
      :binding "delete"
      :fn      editor-handler/editor-delete}
-    :editor/indent
-    {:desc    "Indent block"
-     :binding "tab"
-     :fn      (editor-handler/keydown-tab-handler :right)}
-    :editor/outdent
-    {:desc    "Outdent block"
-     :binding "shift+tab"
-     :fn      (editor-handler/keydown-tab-handler :left)}
     :editor/new-block
     {:desc    "Create new block"
      :binding "enter"
@@ -79,14 +71,6 @@
     {:desc    "Rotate the TODO state of the current item"
      :binding "mod+enter"
      :fn      editor-handler/cycle-todo!}
-    :editor/expand-block-children
-    {:desc    "Expand"
-     :binding "mod+down"
-     :fn      editor-handler/expand!}
-    :editor/collapse-block-children
-    {:desc    "Collapse"
-     :binding "mod+up"
-     :fn      editor-handler/collapse!}
     :editor/follow-link
     {:desc    "Follow link under cursor"
      :binding "mod+o"
@@ -199,6 +183,22 @@
     {:desc    "Delete selected blocks"
      :binding ["backspace" "delete"]
      :fn      editor-handler/delete-selection}
+    :editor/expand-block-children
+    {:desc    "Expand"
+     :binding "mod+down"
+     :fn      editor-handler/expand!}
+    :editor/collapse-block-children
+    {:desc    "Collapse"
+     :binding "mod+up"
+     :fn      editor-handler/collapse!}
+    :editor/indent
+    {:desc    "Indent block"
+     :binding "tab"
+     :fn      (editor-handler/keydown-tab-handler :right)}
+    :editor/outdent
+    {:desc    "Outdent block"
+     :binding "shift+tab"
+     :fn      (editor-handler/keydown-tab-handler :left)}
     :editor/copy
     {:desc    "Copy"
      :binding "mod+c"
@@ -297,14 +297,6 @@
      :binding "t w"
      :fn      ui-handler/toggle-wide-mode!}
     ;; :ui/toggle-between-page-and-file route-handler/toggle-between-page-and-file!
-    :ui/fold
-    {:desc    "Fold blocks (when not in edit mode)"
-     :binding "tab"
-     :fn      (editor-handler/on-tab :right)}
-    :ui/un-fold
-    {:desc    "Unfold blocks (when not in edit mode)"
-     :binding "shift+tab"
-     :fn      (editor-handler/on-tab :left)}
     :git/commit
     {:desc    "Git commit message"
      :binding "g c"
@@ -319,8 +311,8 @@
     :editor/new-line
     :editor/indent
     :editor/outdent
-    :ui/fold
-    :ui/un-fold
+    :editor/collapse-block-children
+    :editor/expand-block-children
     :go/search
     :go/search-in-page
     :editor/undo