Cycle expand

pull/645/head
Tienson Qin 2020-06-26 17:47:00 +08:00
parent 8e245296a3
commit ba4d5d6a64
7 changed files with 257 additions and 232 deletions

View File

@ -29,9 +29,6 @@
(defonce *heading-children
(atom {}))
(defonce *mouse
(atom {}))
(defonce *dragging?
(atom false))
(defonce *dragging-heading
@ -390,16 +387,13 @@
(defonce *control-show? (atom {}))
(rum/defcs heading-control < rum/reactive
(rum/local false ::collapsed?)
[state config uuid heading-id level start-level body children heading dummy?]
(let [collapsed-atom? (get state ::collapsed?)
toggle-collapsed? (state/sub [:ui/collapsed-headings heading-id])
has-child? (or (seq children)
(seq body))
collapsed? (or (and
toggle-collapsed?
has-child?)
@collapsed-atom?)
[state config heading uuid heading-id level start-level body children dummy? collapsed-atom]
(let [has-child? (and
(not (:pre-heading? heading))
(or (seq children)
(seq body)))
collapsed? (and has-child?
(rum/react collapsed-atom))
control-show (util/react (rum/cursor *control-show? heading-id))
dark? (= "dark" (state/sub :ui/theme))]
[:div.hd-control.mr-2.flex.flex-row.items-center
@ -414,11 +408,10 @@
:class "transition ease-in-out duration-150"
:on-click (fn [e]
(util/stop e)
(let [id (str "ls-heading-" uuid)]
(if collapsed?
(expand/expand! (:id config) id)
(expand/collapse! (:id config) id))
(reset! collapsed-atom? (not collapsed?))))}
(if collapsed?
(expand/expand! heading)
(expand/collapse! heading))
(reset! collapsed-atom (not collapsed?)))}
(cond
(and control-show collapsed?)
(svg/caret-right)
@ -428,59 +421,33 @@
:else
[:span ""])]
[:a
(cond->
{:id (str "dot-" uuid)
:draggable true
:on-drag-start (fn [event]
(handler/highlight-heading! uuid)
(.setData (gobj/get event "dataTransfer")
"heading-uuid"
uuid)
(.setData (gobj/get event "dataTransfer")
"heading-dom-id"
heading-id)
(reset! *dragging? true)
(reset! *dragging-heading heading))
;; :on-drag-end (fn [event]
;; (reset! *dragging? false)
;; (reset! *mouse {}))
:style {:width 16
:height 16}
:headingid (str uuid)}
(not dummy?)
(assoc :href (str "/page/" uuid)
:on-click (fn [e]
(util/stop e)
(when (gobj/get e "shiftKey")
(state/sidebar-add-block!
(state/get-current-repo)
(:db/id heading)
:heading
heading)
(handler/show-right-sidebar)))))
(if collapsed?
[:svg {:height 16
:width 16
:fill "currentColor"
:display "inline-block"}
[:circle {:cx 8
:cy 8
:r 5
:stroke (if dark? "#1d6577" "#cbd7de")
:stroke-width 5
:fill "none"}]
[:circle {:cx 8
:cy 8
:r 3}]]
[:svg {:height 16
:width 16
:fill "currentColor"
:display "inline-block"}
[:circle {:cx 8
:cy 8
:r 3}]])]]))
[:a (if (not dummy?)
{:href (str "/page/" uuid)
:on-click (fn [e]
(util/stop e)
(when (gobj/get e "shiftKey")
(state/sidebar-add-block!
(state/get-current-repo)
(:db/id heading)
:heading
heading)
(handler/show-right-sidebar)))})
[:span.bullet-container.cursor
{:id (str "dot-" uuid)
:draggable true
:on-drag-start (fn [event]
(handler/highlight-heading! uuid)
(.setData (gobj/get event "dataTransfer")
"heading-uuid"
uuid)
(.setData (gobj/get event "dataTransfer")
"heading-dom-id"
heading-id)
(reset! *dragging? true)
(reset! *dragging-heading heading))
:headingid (str uuid)
:class (if collapsed? "bullet-closed")}
[:span.bullet]]]]))
(defn- build-id
[config ref? sidebar?]
@ -507,10 +474,7 @@
:width (- 700 margin-left)}
(if top?
{:top 0}
{:bottom bottom}))
:on-mouse-move (fn [event]
(let [client-x (gobj/get event "clientX")]
(reset! *mouse {:client-x client-x})))}]))
{:bottom bottom}))}]))
(declare heading-container)
(rum/defc heading-checkbox
@ -544,24 +508,24 @@
(heading-checkbox t (str "mr-1 cursor")))
marker-cp (when-not pre-heading?
(if (contains? #{"DOING" "IN-PROGRESS" "WAIT" "WAITING"} marker)
[:span {:class (str "task-status " (string/lower-case marker))
:style {:margin-right 3.5}}
(string/upper-case marker)]))
[:span {:class (str "task-status " (string/lower-case marker))
:style {:margin-right 3.5}}
(string/upper-case marker)]))
priority (when-not pre-heading?
(if priority
[:span {:class "priority"
:style {:margin-right 3.5}}
(util/format "[#%s]" (str priority))]))
[:span {:class "priority"
:style {:margin-right 3.5}}
(util/format "[#%s]" (str priority))]))
tags (when-not pre-heading?
(when-not (empty? tags)
(->elem
:span
{:class "heading-tags"}
(mapv (fn [{:keys [db/id tag/name]}]
[:a.tag.mx-1 {:key (str "tag-" id)
:href (str "/tag/" name)}
(str "#" name)])
tags))))]
(->elem
:span
{:class "heading-tags"}
(mapv (fn [{:keys [db/id tag/name]}]
[:a.tag.mx-1 {:key (str "tag-" id)
:href (str "/tag/" name)}
(str "#" name)])
tags))))]
(when level
(let [element (if (<= level 6)
(keyword (str "h" level))
@ -602,7 +566,7 @@
(.getData (gobj/get event "dataTransfer") attr))
(rum/defc heading-content-or-editor < rum/reactive
[config {:heading/keys [uuid title level body meta content dummy? page format repo children pre-heading? idx] :as heading} edit-input-id heading-id slide?]
[config {:heading/keys [uuid title level body meta content dummy? page format repo children pre-heading? collapsed? idx] :as heading} edit-input-id heading-id slide?]
(let [edit? (state/sub [:editor/editing? edit-input-id])]
(if edit?
[:div {:id (str "editor-" edit-input-id)}
@ -666,7 +630,7 @@
(dnd-separator heading 0 -4 false true))
(when (and (not pre-heading?) (seq body))
[:div.heading-body
[:div.heading-body {:style {:display (if collapsed? "none" "")}}
(for [child body]
(let [block (block config child)]
(rum/with-key (heading-child block)
@ -688,17 +652,20 @@
{:did-update (fn [state]
(util/code-highlight!)
state)}
[config {:heading/keys [uuid title level body meta content dummy? page format repo children idx] :as heading}]
[config {:heading/keys [uuid title level body meta content dummy? page format repo children collapsed? pre-heading? idx] :as heading}]
(let [ref? (boolean (:ref? config))
sidebar? (boolean (:sidebar? config))
slide? (boolean (:slide? config))
unique-dom-id (build-id config ref? sidebar?)
edit-input-id (str "edit-heading-" unique-dom-id uuid)
heading-id (str "ls-heading-" unique-dom-id uuid)
has-child? (or (seq children)
(seq body))
has-child? (boolean
(and
(not pre-heading?)
(or (seq children)
(seq body))))
start-level (or (:start-level config) 1)
collapsed-atom (atom collapsed?)
drag-attrs {:on-drag-over (fn [event]
(util/stop event)
(when-not (dnd-same-heading? uuid)
@ -734,7 +701,11 @@
:on-mouse-over (fn [e]
(util/stop e)
(when has-child?
(swap! *control-show? assoc heading-id true)))
(swap! *control-show? assoc heading-id true))
(when-let [parent (gdom/getElement heading-id)]
(let [node (.querySelector parent ".bullet-container")
closed? (d/has-class? node "bullet-closed")]
(reset! collapsed-atom closed?))))
:on-mouse-out (fn [e]
(util/stop e)
(when has-child?
@ -745,10 +716,13 @@
{:id heading-id
:style {:position "relative"}
:class (str uuid
(if dummy? " dummy"))
(when dummy? " dummy")
(when (and collapsed? has-child?) " collapsed")
(when pre-heading? " pre-heading"))
:headingid (str uuid)
:repo repo
:level level}
:level level
:haschild (str has-child?)}
(not slide?)
(merge drag-attrs))
@ -756,12 +730,13 @@
[:div.flex-1.flex-row.py-1
(when-not slide?
(heading-control config uuid heading-id level start-level body children heading dummy?))
(heading-control config heading uuid heading-id level start-level body children dummy? collapsed-atom))
(heading-content-or-editor config heading edit-input-id heading-id slide?)]
(when (seq children)
[:div.heading-children {:style {:margin-left 33}}
[:div.heading-children {:style {:margin-left 31
:display (if collapsed? "none" "")}}
(for [child children]
(let [child (dissoc child :heading/meta)]
(rum/with-key (heading-container config child)

View File

@ -68,7 +68,8 @@
(util/stop e)
(let [encoded-page-name (get-page-name state)
id encoded-page-name]
(expand/toggle-all! id)))))
(expand/cycle!)
(handler/re-render-root!)))))
;; (mixins/perf-measure-mixin "Page")
[state {:keys [repo] :as option}]
(let [repo (or repo (state/get-current-repo))

View File

@ -168,6 +168,7 @@
:heading/last-modified-at {}
:heading/body {}
:heading/pre-heading? {}
:heading/collapsed? {}
;; :heading/children {:db/valueType :db.type/ref
;; :db/cardinality :db.cardinality/many}
@ -346,7 +347,8 @@
(let [kv? (and (vector? k) (= :kv (first k)))
k (vec (cons repo k))
;; TODO: refactor
use-cache? false]
use-cache? false
]
(when-let [conn (if files-db?
(deref (get-files-conn repo))
(get-conn repo))]
@ -505,23 +507,16 @@
(let [new-result (->
(if (keyword? query)
(get-key-value repo-url query)
;; TODO: Datascript query performance
;; (if files-db?
;; (apply d/q query (d/db (get-conn)) inputs)
;; (profile
;; "Query"
;; (doall (apply d/q query (d/db (get-conn)) inputs))))
(apply d/q query (d/db (get-conn)) inputs)
)
;; TODO: Improve Datascript query performance
(if files-db?
(apply d/q query (d/db (get-conn)) inputs)
(profile
"Query"
(doall (apply d/q query (d/db (get-conn)) inputs)))))
transform-fn)]
(set-new-result! handler-key new-result)
;; (if files-db?
;; (set-new-result! handler-key new-result)
;; (profile
;; (str "set new result " handler-key)
;; (set-new-result! handler-key new-result)))
))))))))))
(profile
(str "set new result " handler-key)
(set-new-result! handler-key new-result))))))))))))
(defn pull-heading
[id]
@ -1452,7 +1447,64 @@
(when-let [heading (entity repo [:heading/uuid (:heading/uuid heading)])]
(get-in heading [:heading/meta :end-pos]))))))
(defn get-heading-ids
[heading]
(let [ids (atom [])
_ (walk/prewalk
(fn [form]
(when (map? form)
(when-let [id (:heading/uuid form)]
(swap! ids conj id)))
form)
heading)]
@ids))
(defn collapse-heading!
[heading]
(let [repo (:heading/repo heading)]
(transact! repo
[{:heading/uuid (:heading/uuid heading)
:heading/collapsed? true}])))
(defn collapse-headings!
[heading-ids]
(let [repo (state/get-current-repo)]
(transact! repo
(map
(fn [id]
{:heading/uuid id
:heading/collapsed? true})
heading-ids))))
(defn expand-heading!
[heading]
(let [repo (:heading/repo heading)]
(transact! repo
[{:heading/uuid (:heading/uuid heading)
:heading/collapsed? false}])))
(defn expand-headings!
[heading-ids]
(let [repo (state/get-current-repo)]
(transact! repo
(map
(fn [id]
{:heading/uuid id
:heading/collapsed? false})
heading-ids))))
(defn get-collapsed-headings
[]
(d/q
'[:find ?content
:where
[?h :heading/content ?content]
[?h :heading/collapsed? true]]
(get-conn)))
(comment
(defn debug!
[]
(let [repos (->> (get-in @state/state [:me :repos])

View File

@ -5,103 +5,95 @@
[frontend.util :as util]
[clojure.string :as string]
[medley.core :as medley]
[frontend.state :as state]))
[frontend.state :as state]
[frontend.db :as db]))
(defn get-headings
[id]
;; TODO: dommy/by-id will fail if id includes `=`
(when-let [node (gdom/getElement id)]
(some-> (d/sel node [".ls-heading"])
(array-seq))))
(defn- hide!
[element]
(d/set-style! element :display "none"))
(defn- get-level
[node]
(-> (d/attr node "level")
(util/parse-int)))
(defn get-heading-children
[headings heading]
(let [heading-id (gobj/get heading "id")
level (get-level heading)
nodes (->> headings
;; drop preceding nodes
(drop-while (fn [node]
(not= heading-id (gobj/get node "id"))))
;; drop self
(next)
;; take the children
(take-while (fn [node]
(> (get-level node) level))))]
nodes))
(defn collapse-non-heading!
[id]
(when-let [node (gdom/getElement id)]
(doseq [node (d/sel node [".heading-body"])]
(d/hide! node))))
(defn expand-non-heading!
[id]
(when-let [node (gdom/getElement id)]
(doseq [node (d/sel node [".heading-body"])]
(d/show! node))))
(defn- show!
[element]
(d/set-style! element :display ""))
(defn collapse!
[headings-id heading-id]
(let [all-headings (get-headings headings-id)]
(collapse-non-heading! heading-id)
[heading]
(let [heading-id (str "ls-heading-" (:heading/uuid heading))]
(when-let [node (gdom/getElement heading-id)]
(let [root-level (d/attr node "level")]
(let [children (get-heading-children all-headings node)]
(doseq [node children]
(let [child-level (d/attr node "level")]
(when (and root-level
child-level
(= 1 (- (util/parse-int child-level)
(util/parse-int root-level))))
(d/add-class! node "is-collapsed"))
(d/hide! node))))))))
(d/add-class! node "collapsed")
(when-let [e (.querySelector node ".heading-body")]
(hide! e))
(when-let [e (.querySelector node ".heading-children")]
(hide! e))
(db/collapse-heading! heading))))
(defn expand!
[headings-id heading-id]
(let [all-headings (get-headings headings-id)]
(state/expand-heading! heading-id)
(expand-non-heading! heading-id)
[heading]
(let [heading-id (str "ls-heading-" (:heading/uuid heading))]
(when-let [node (gdom/getElement heading-id)]
(d/remove-class! node "is-collapsed")
(let [root-level (d/attr node "level")]
(let [children (get-heading-children all-headings node)]
(doseq [node children]
(let [child-level (d/attr node "level")
collapsed? (d/has-class? node "is-collapsed")
next-child? (and collapsed?
root-level
child-level
(= 1 (- (util/parse-int child-level)
(util/parse-int root-level))))]
(when next-child?
(d/remove-class! node "is-collapsed"))
(when (or (not collapsed?)
next-child?)
(d/show! node)))))))))
;; (d/remove-class! node "collapsed")
(when-let [e (.querySelector node ".heading-body")]
(show! e))
(when-let [e (.querySelector node ".heading-children")]
(show! e))
(db/expand-heading! heading)
;; (doseq [element (d/by-class node "cycle-collapsed")]
;; (d/remove-class! element "cycle-collapsed")
;; (show! element))
)))
(defn set-bullet-closed!
[element]
(when element
(when-let [node (.querySelector element ".bullet-container")]
(d/add-class! node "bullet-closed"))))
;; ;; Collapse acts like TOC
(defn toggle-all!
[id]
;; default to level 2
(let [all-headings (get-headings id)
headings all-headings]
(when (seq headings)
(let [toggle-state (:ui/toggle-state @state/state)]
(doseq [heading headings]
(let [heading-id (gobj/get heading "id")
level (util/parse-int (d/attr heading "level"))]
(if toggle-state
(expand! id heading-id)
(when (= level 2)
(collapse! id heading-id)
(state/collapse-heading! heading-id)))))
(when toggle-state
(state/clear-collapsed-headings!))
(state/ui-toggle-state!)))))
;; Collapse acts like TOC
;; There are three modes to cycle:
;; 1. Collapse all headings which levels are greater than 2
;; 2. Hide all heading's body (user can still see the heading title)
;; 3. Show everything
(defn cycle!
[]
(let [mode (state/next-collapse-mode)
get-headings (fn []
(let [elements (d/by-class "ls-heading")
result (group-by (fn [e]
(let [level (d/attr e "level")]
(and level
(> (util/parse-int level) 2)))) elements)]
[(get result true) (get result false)]))]
(case mode
:show-all
(do
(doseq [element (d/by-class "ls-heading")]
(show! element))
(let [elements (d/by-class "heading-body")]
(doseq [element elements]
(show! element)))
(doseq [element (d/by-class "bullet-closed")]
(d/remove-class! element "bullet-closed"))
(doseq [element (d/by-class "heading-children")]
(show! element)))
:hide-heading-body
(let [elements (d/by-class "heading-body")]
(doseq [element elements]
(d/set-style! element :display "none")
(when-let [parent (util/rec-get-heading-node element)]
(set-bullet-closed! parent))))
:hide-heading-children
(let [[elements top-level-elements] (get-headings)
level-2-elements (filter (fn [e]
(let [level (d/attr e "level")]
(and level
(= (util/parse-int level) 2)
(not (d/has-class? e "pre-heading")))))
top-level-elements)]
(doseq [element elements]
(hide! element))
(doseq [element level-2-elements]
(when (= "true" (d/attr element "haschild"))
(set-bullet-closed! element)))))
(state/cycle-collapse!)))

View File

@ -9,21 +9,9 @@
[cljs-time.coerce :as tc]
[cljs-time.core :as t]))
(defn- get-heading-ids
[heading]
(let [ids (atom [])
_ (walk/prewalk
(fn [form]
(when (map? form)
(when-let [id (:heading/uuid form)]
(swap! ids conj id)))
form)
heading)]
@ids))
(defn- remove-heading-child!
[target-heading parent-heading]
(let [child-ids (set (get-heading-ids target-heading))]
(let [child-ids (set (db/get-heading-ids target-heading))]
(db/get-heading-content-rec
parent-heading
(fn [{:heading/keys [uuid level content]}]
@ -195,7 +183,7 @@
(= direction :up)
(let [offset-heading-id (if nested?
(:heading/uuid to-heading)
(last (get-heading-ids to-heading)))
(last (db/get-heading-ids to-heading)))
offset-end-pos (get-end-pos
(db/entity repo [:heading/uuid offset-heading-id]))]
(rebuild-dnd-headings repo target-file target-child?
@ -207,7 +195,7 @@
(= direction :down)
(let [offset-heading-id (if nested?
(:heading/uuid to-heading)
(last (get-heading-ids to-heading)))
(last (db/get-heading-ids to-heading)))
target-start-pos (get-start-pos target-heading)]
(rebuild-dnd-headings repo target-file target-child?
target-start-pos
@ -358,7 +346,7 @@
:else
(let [offset-heading-id (if nested?
(:heading/uuid to-heading)
(last (get-heading-ids to-heading)))
(last (db/get-heading-ids to-heading)))
offset-end-pos (get-end-pos
(db/entity repo [:heading/uuid offset-heading-id]))]
(rebuild-dnd-headings repo to-file target-child?
@ -406,7 +394,7 @@
(utf8/substring to-file-content separate-pos))))
target-delete-tx (map (fn [id]
[:db.fn/retractEntity [:heading/uuid id]])
(get-heading-ids target-heading))
(db/get-heading-ids target-heading))
[target-modified-time to-modified-time]
(let [modified-at (tc/to-long (t/now))]
[[[:db/add (:db/id (:heading/page target-heading)) :page/last-modified-at modified-at]
@ -427,7 +415,7 @@
:else
(let [offset-heading-id (if nested?
(:heading/uuid to-heading)
(last (get-heading-ids to-heading)))
(last (db/get-heading-ids to-heading)))
offset-end-pos (get-end-pos
(db/entity to-heading-repo [:heading/uuid offset-heading-id]))]
(rebuild-dnd-headings to-heading-repo to-file target-child?

View File

@ -29,7 +29,8 @@
:search/result nil
:ui/theme (or (storage/get :ui/theme) "black")
:ui/toggle-state false
;; :show-all, :hide-heading-body, :hide-heading-children
:ui/cycle-collapse :show-all
:ui/collapsed-headings {}
:ui/sidebar-collapsed-blocks {}
:ui/root-component nil
@ -110,9 +111,21 @@
(when (= (get-current-repo) (:url repo))
(set-current-repo! (:url (first (get-repos))))))
(defn ui-toggle-state!
(defn next-collapse-mode
[]
(update-state! :ui/toggle-state not))
(case (:ui/cycle-collapse @state)
:show-all
:hide-heading-body
:hide-heading-body
:hide-heading-children
:hide-heading-children
:show-all))
(defn cycle-collapse!
[]
(set-state! :ui/cycle-collapse (next-collapse-mode)))
(defn get-edit-heading
[]
@ -149,11 +162,15 @@
(defn collapse-heading!
[heading-id]
(set-state! [:ui/collapsed-headings heading-id] true))
(set-state! [:ui/collapsed-headings heading-id] (atom true)))
(defn get-heading-collapsed-state
[heading-id]
(get-in @state [:ui/collapsed-headings heading-id]))
(defn expand-heading!
[heading-id]
(set-state! [:ui/collapsed-headings heading-id] false))
(set-state! [:ui/collapsed-headings heading-id] (atom false)))
(defn clear-collapsed-headings!
[]

View File

@ -514,7 +514,7 @@
(.getDate local-date-time)
0 0 0 0)))
(defn- rec-get-heading-node
(defn rec-get-heading-node
[node]
(if (and node (d/has-class? node "ls-heading"))
node