浏览代码

Merge branch 'enhance/refacotr-ui-x-menu' of https://github.com/logseq/logseq into enhance/refacotr-ui-x-menu

charlie 1 年之前
父节点
当前提交
1bad9a8246

+ 5 - 0
fastlane/metadata/android/en-US/full_description.txt

@@ -0,0 +1,5 @@
+Logseq is a knowledge management and collaboration platform. It focuses on privacy, longevity, and user control. Logseq offers a range of powerful tools for knowledge management, collaboration, PDF annotation, and task management with support for multiple file formats, including Markdown and Org-mode, and various features for organizing and structuring your notes.
+
+Logseq's Whiteboard feature lets you organize your knowledge and ideas using a spatial canvas with shapes, drawings, website embeds, and connectors. You can visually group and link your notes and external media (such as videos and images), enabling visual thinkers to compose, remix, annotate, and connect content from their knowledge base and emerging thoughts in a new way.
+
+In addition to its core features, Logseq has a growing ecosystem of plugins and themes that enable a wide range of workflows and customization options. Mobile apps are also available, providing access to most of the features of the desktop application. Whether you're a student, a professional, or anyone who values a clear and organized approach to managing your ideas and notes, Logseq is an excellent choice for anyone looking to improve their productivity and streamline their workflow.

+ 1 - 0
fastlane/metadata/android/en-US/short_description.txt

@@ -0,0 +1 @@
+A privacy-first, open-source platform for knowledge management and collaboration

+ 4 - 1
src/main/frontend/components/block.cljs

@@ -1353,7 +1353,10 @@
                   url)]
                   url)]
         (if (and (coll? src)
         (if (and (coll? src)
                  (= (first src) "youtube-player"))
                  (= (first src) "youtube-player"))
-          (youtube/youtube-video (last src) nil)
+          (let [t (re-find #"&t=(\d+)" url)
+                opts (when (seq t)
+                       {:start (nth t 1)})]
+            (youtube/youtube-video (last src) opts))
           (when src
           (when src
             (let [width (min (- (util/get-width) 96) 560)
             (let [width (min (- (util/get-width) 96) 560)
                   height (int (* width (/ (if (string/includes? src "player.bilibili.com")
                   height (int (* width (/ (if (string/includes? src "player.bilibili.com")

+ 39 - 19
src/main/frontend/components/git.cljs

@@ -1,6 +1,7 @@
 (ns frontend.components.git
 (ns frontend.components.git
   (:require [rum.core :as rum]
   (:require [rum.core :as rum]
             [frontend.ui :as ui]
             [frontend.ui :as ui]
+            [promesa.core :as p]
             [frontend.util :as util]
             [frontend.util :as util]
             [clojure.string :as string]
             [clojure.string :as string]
             [frontend.handler.shell :as shell]
             [frontend.handler.shell :as shell]
@@ -36,23 +37,42 @@
 
 
      [:div.mt-5.sm:mt-4.flex
      [:div.mt-5.sm:mt-4.flex
       (ui/button
       (ui/button
-        "Submit"
-        {:on-click (fn []
-                     (let [username @username
-                           email @email]
-                       (when (and (not (string/blank? username))
-                                  (not (string/blank? email)))
-                         (shell/set-git-username-and-email username email))))})]]))
+       "Submit"
+       {:on-click (fn []
+                    (let [username @username
+                          email @email]
+                      (when (and (not (string/blank? username))
+                                 (not (string/blank? email)))
+                        (shell/set-git-username-and-email username email))))})]]))
 
 
-(rum/defc file-specific-version
-  [path hash content]
-  [:div.w-full.sm:max-w-lg {:style {:width 700}}
-   [:div.font-bold.mb-4 (str path (util/format " (%s)" hash)) ]
-   [:pre content]
-   (ui/button "Revert"
-     :on-click (fn []
-                 (file/alter-file (state/get-current-repo)
-                                  path
-                                  content
-                                  {:re-render-root? true
-                                   :skip-compare? true})))])
+(rum/defc file-version-selector
+  [versions path get-content]
+  (let
+   [[content set-content!] (rum/use-state  nil)
+    [hash  set-hash!] (rum/use-state   "HEAD")]
+    (rum/use-effect! (fn [] (p/let [c (get-content hash path)] (set-content! c)) [hash path]))
+    [:div.flex
+     [:div.overflow-y-auto {:class "w-48 max-h-[calc(85vh_-_4rem)] "}
+      [:div.font-bold "File history - " path]
+      (for [line  versions]
+        (let [[hash title time] (string/split line "$$$")
+              hash (subs hash 8)]
+          [:div.my-4 {:key hash}
+           [:hr]
+           [:div.mb-2
+            [:a.font-medium.mr-1.block
+             {:on-click (fn []  (set-hash!  hash))}
+             hash]
+            title]
+           [:div.opacity-50 time]]))]
+     [:div.flex-1.p-4
+      [:div.w-full.sm:max-w-lg {:style {:width 700}}
+       [:div.font-bold.mb-4 (str path (util/format " (%s)" hash))]
+       [:pre content]
+       (ui/button "Revert"
+                  :on-click (fn []
+                              (file/alter-file (state/get-current-repo)
+                                               path
+                                               content
+                                               {:re-render-root? true
+                                                :skip-compare? true})))]]]))

+ 87 - 9
src/main/frontend/components/page.cljs

@@ -542,27 +542,41 @@
 (defonce *n-hops (atom nil))
 (defonce *n-hops (atom nil))
 (defonce *focus-nodes (atom []))
 (defonce *focus-nodes (atom []))
 (defonce *graph-reset? (atom false))
 (defonce *graph-reset? (atom false))
+(defonce *graph-forcereset? (atom false))
 (defonce *journal? (atom nil))
 (defonce *journal? (atom nil))
 (defonce *orphan-pages? (atom true))
 (defonce *orphan-pages? (atom true))
 (defonce *builtin-pages? (atom nil))
 (defonce *builtin-pages? (atom nil))
 (defonce *excluded-pages? (atom true))
 (defonce *excluded-pages? (atom true))
 (defonce *show-journals-in-page-graph? (atom nil))
 (defonce *show-journals-in-page-graph? (atom nil))
+(defonce *link-dist (atom 70))
+(defonce *charge-strength (atom -600))
+(defonce *charge-range (atom 600))
 
 
 (rum/defc ^:large-vars/cleanup-todo graph-filters < rum/reactive
 (rum/defc ^:large-vars/cleanup-todo graph-filters < rum/reactive
-  [graph settings n-hops]
+  [graph settings forcesettings n-hops]
   (let [{:keys [journal? orphan-pages? builtin-pages? excluded-pages?]
   (let [{:keys [journal? orphan-pages? builtin-pages? excluded-pages?]
          :or {orphan-pages? true}} settings
          :or {orphan-pages? true}} settings
+        {:keys [link-dist charge-strength charge-range]} forcesettings
         journal?' (rum/react *journal?)
         journal?' (rum/react *journal?)
         orphan-pages?' (rum/react *orphan-pages?)
         orphan-pages?' (rum/react *orphan-pages?)
         builtin-pages?' (rum/react *builtin-pages?)
         builtin-pages?' (rum/react *builtin-pages?)
         excluded-pages?' (rum/react *excluded-pages?)
         excluded-pages?' (rum/react *excluded-pages?)
+        link-dist'  (rum/react *link-dist)
+        charge-strength'  (rum/react *charge-strength)
+        charge-range'  (rum/react *charge-range)
         journal? (if (nil? journal?') journal? journal?')
         journal? (if (nil? journal?') journal? journal?')
         orphan-pages? (if (nil? orphan-pages?') orphan-pages? orphan-pages?')
         orphan-pages? (if (nil? orphan-pages?') orphan-pages? orphan-pages?')
         builtin-pages? (if (nil? builtin-pages?') builtin-pages? builtin-pages?')
         builtin-pages? (if (nil? builtin-pages?') builtin-pages? builtin-pages?')
         excluded-pages? (if (nil? excluded-pages?') excluded-pages? excluded-pages?')
         excluded-pages? (if (nil? excluded-pages?') excluded-pages? excluded-pages?')
+        link-dist (if (nil? link-dist') link-dist link-dist')
+        charge-strength (if (nil? charge-strength') charge-strength charge-strength')
+        charge-range (if (nil? charge-range') charge-range charge-range')
         set-setting! (fn [key value]
         set-setting! (fn [key value]
                        (let [new-settings (assoc settings key value)]
                        (let [new-settings (assoc settings key value)]
                          (config-handler/set-config! :graph/settings new-settings)))
                          (config-handler/set-config! :graph/settings new-settings)))
+        set-forcesetting! (fn [key value]
+                       (let [new-forcesettings (assoc forcesettings key value)]
+                         (config-handler/set-config! :graph/forcesettings new-forcesettings)))
         search-graph-filters (state/sub :search/graph-filters)
         search-graph-filters (state/sub :search/graph-filters)
         focus-nodes (rum/react *focus-nodes)]
         focus-nodes (rum/react *focus-nodes)]
     [:div.absolute.top-4.right-4.graph-filters
     [:div.absolute.top-4.right-4.graph-filters
@@ -670,6 +684,64 @@
                [:a.opacity-70.opacity-100 {:on-click #(route-handler/go-to-search! :graph)}
                [:a.opacity-70.opacity-100 {:on-click #(route-handler/go-to-search! :graph)}
                 "Click to search"])]))
                 "Click to search"])]))
          {:search-filters search-graph-filters})
          {:search-filters search-graph-filters})
+        (graph-filter-section
+         [:span.font-medium "Forces"]
+         (fn [open?]
+           (filter-expand-area
+            open?
+            [:div
+             [:p.text-sm.opacity-70.px-4
+              (let [;; c1 (count (:nodes graph))
+                    ;; s1 (if (> c1 1) "s" "")
+                    c2 (count (:links graph))
+                    s2 (if (> c2 1) "s" "")
+                    ]
+                ;; (util/format "%d page%s, %d link%s" c1 s1 c2 s2)
+                (util/format "%d link%s" c2 s2))]
+             [:div.p-6
+              [:div.flex.flex-col.mb-2
+                [:p {:title "Link Distance"}
+                "Link Distance"]
+                (ui/tippy {:html [:div.pr-3 link-dist]}
+                          (ui/slider (/ link-dist 10)
+                                     {:min 1   ;; 10
+                                      :max 18  ;; 180
+                                      ;; :on-change #(reset! *link-dist (int %))}))]
+                                      :on-change #(let [value (int %)]
+                                                    (reset! *link-dist (* value 10))
+                                                    (set-forcesetting! :link-dist (* value 10)))}))]
+                                      ;; :on-change #(let [value (util/evalue %)]
+                                      ;;                (reset! *link-dist value)
+                                      ;;                (set-forcesetting! :link-dist value))}))]
+              [:div.flex.flex-col.mb-2
+                [:p {:title "Charge Strength"}
+                "Charge Strength"]
+                (ui/tippy {:html [:div.pr-3 charge-strength]}
+                          (ui/slider (/ charge-strength 100)
+                                     {:min -10  ;;-1000
+                                      :max 10   ;;1000
+                                      :on-change #(let [value (int %)]
+                                                    (reset! *charge-strength (* value 100))
+                                                    (set-forcesetting! :charge-strength (* value 100)))}))]
+              [:div.flex.flex-col.mb-2
+                [:p {:title "Charge Range"}
+                "Charge Range"]
+                (ui/tippy {:html [:div.pr-3 charge-range]}
+                          (ui/slider (/ charge-range 100)
+                                     {:min 5    ;;500
+                                      :max 40   ;;4000
+                                      :on-change #(let [value (int %)]
+                                                    (reset! *charge-range (* value 100))
+                                                    (set-forcesetting! :charge-range (* value 100)))}))]
+
+
+              [:a.opacity-70.opacity-100 {:on-click (fn []
+                                                      (swap! *graph-forcereset? not)
+                                                      (reset! *link-dist 70)
+                                                      (reset! *charge-strength -600)
+                                                      (reset! *charge-range 600))}
+               "Reset Forces"]]]))
+         {})
         (graph-filter-section
         (graph-filter-section
          [:span.font-medium "Export"]
          [:span.font-medium "Export"]
          (fn [open?]
          (fn [open?]
@@ -699,11 +771,15 @@
          (reset! last-node-position [node (.-x event) (.-y event)]))))
          (reset! last-node-position [node (.-x event) (.-y event)]))))
 
 
 (rum/defc global-graph-inner < rum/reactive
 (rum/defc global-graph-inner < rum/reactive
-  [graph settings theme]
+  [graph settings forcesettings theme]
   (let [[width height] (rum/react layout)
   (let [[width height] (rum/react layout)
         dark? (= theme "dark")
         dark? (= theme "dark")
         n-hops (rum/react *n-hops)
         n-hops (rum/react *n-hops)
+        link-dist (rum/react *link-dist)
+        charge-strength (rum/react *charge-strength)
+        charge-range (rum/react *charge-range)
         reset? (rum/react *graph-reset?)
         reset? (rum/react *graph-reset?)
+        forcereset? (rum/react *graph-forcereset?)
         focus-nodes (when n-hops (rum/react *focus-nodes))
         focus-nodes (when n-hops (rum/react *focus-nodes))
         graph (if (and (integer? n-hops)
         graph (if (and (integer? n-hops)
                        (seq focus-nodes)
                        (seq focus-nodes)
@@ -722,11 +798,15 @@
                       :width (- width 24)
                       :width (- width 24)
                       :height (- height 48)
                       :height (- height 48)
                       :dark? dark?
                       :dark? dark?
+                      :link-dist link-dist
+                      :charge-strength charge-strength
+                      :charge-range charge-range
                       :register-handlers-fn
                       :register-handlers-fn
                       (fn [graph]
                       (fn [graph]
                         (graph-register-handlers graph *focus-nodes *n-hops dark?))
                         (graph-register-handlers graph *focus-nodes *n-hops dark?))
-                      :reset? reset?})
-     (graph-filters graph settings n-hops)]))
+                      :reset? reset?
+                      :forcereset? forcereset?})
+     (graph-filters graph settings forcesettings n-hops)]))
 
 
 (defn- filter-graph-nodes
 (defn- filter-graph-nodes
   [nodes filters]
   [nodes filters]
@@ -741,21 +821,19 @@
      (mixins/listen state js/window "resize"
      (mixins/listen state js/window "resize"
                     (fn [_e]
                     (fn [_e]
                       (reset! layout [js/window.innerWidth js/window.innerHeight])))))
                       (reset! layout [js/window.innerWidth js/window.innerHeight])))))
-  {:will-mount (fn [state]
-                 (state/set-search-mode! :graph)
-                 state)
-   :will-unmount (fn [state]
+  {:will-unmount (fn [state]
                    (reset! *n-hops nil)
                    (reset! *n-hops nil)
                    (reset! *focus-nodes [])
                    (reset! *focus-nodes [])
                    (state/set-search-mode! :global)
                    (state/set-search-mode! :global)
                    state)}
                    state)}
   [state]
   [state]
   (let [settings (state/graph-settings)
   (let [settings (state/graph-settings)
+        forcesettings (state/graph-forcesettings)
         theme (state/sub :ui/theme)
         theme (state/sub :ui/theme)
         graph (graph-handler/build-global-graph theme settings)
         graph (graph-handler/build-global-graph theme settings)
         search-graph-filters (state/sub :search/graph-filters)
         search-graph-filters (state/sub :search/graph-filters)
         graph (update graph :nodes #(filter-graph-nodes % search-graph-filters))]
         graph (update graph :nodes #(filter-graph-nodes % search-graph-filters))]
-    (global-graph-inner graph settings theme)))
+    (global-graph-inner graph settings forcesettings theme)))
 
 
 (rum/defc page-graph-inner < rum/reactive
 (rum/defc page-graph-inner < rum/reactive
   [_page graph dark?]
   [_page graph dark?]

+ 2 - 0
src/main/frontend/components/page_menu.cljs

@@ -75,6 +75,8 @@
           _ (state/sub :auth/id-token)
           _ (state/sub :auth/id-token)
           file-sync-graph-uuid (and (user-handler/logged-in?)
           file-sync-graph-uuid (and (user-handler/logged-in?)
                                     (file-sync-handler/enable-sync?)
                                     (file-sync-handler/enable-sync?)
+                                    ;; FIXME: Sync state is not cleared when switching to a new graph
+                                    (file-sync-handler/current-graph-sync-on?)
                                     (file-sync-handler/get-current-graph-uuid))]
                                     (file-sync-handler/get-current-graph-uuid))]
       (when (and page (not block?))
       (when (and page (not block?))
         (->>
         (->>

+ 2 - 2
src/main/frontend/extensions/graph.cljs

@@ -51,9 +51,9 @@
   {:did-update pixi/render!
   {:did-update pixi/render!
    :should-update (fn [old-state new-state]
    :should-update (fn [old-state new-state]
                     (not= (select-keys (first (:rum/args old-state))
                     (not= (select-keys (first (:rum/args old-state))
-                                       [:nodes :links :dark?])
+                                       [:nodes :links :dark? :link-dist :charge-strength :charge-range])
                           (select-keys (first (:rum/args new-state))
                           (select-keys (first (:rum/args new-state))
-                                       [:nodes :links :dark?])))
+                                       [:nodes :links :dark? :link-dist :charge-strength :charge-range])))
    :will-unmount (fn [state]
    :will-unmount (fn [state]
                    (reset! pixi/*graph-instance nil)
                    (reset! pixi/*graph-instance nil)
                    state)}
                    state)}

+ 26 - 8
src/main/frontend/extensions/graph/pixi.cljs

@@ -54,20 +54,35 @@
    :edge {:color "#A5B4FC"}})
    :edge {:color "#A5B4FC"}})
 
 
 (defn layout!
 (defn layout!
-  [nodes links]
+  "Node forces documentation can be read in more detail here https://d3js.org/d3-force"
+  [nodes links link-dist charge-strength charge-range]
   (let [nodes-count (count nodes)
   (let [nodes-count (count nodes)
         simulation (forceSimulation nodes)]
         simulation (forceSimulation nodes)]
-    (-> simulation
+    (-> simulation 
         (.force "link"
         (.force "link"
+                ;; The link force pushes linked nodes together or apart according to the desired link distance. 
+                ;; The strength of the force is proportional to the difference between the linked nodes distance 
+                ;; and the target distance, similar to a spring force.
                 (-> (forceLink)
                 (-> (forceLink)
                     (.id (fn [d] (.-id d)))
                     (.id (fn [d] (.-id d)))
-                    (.distance 180)
+                    (.distance link-dist)
                     (.links links)))
                     (.links links)))
         (.force "charge"
         (.force "charge"
+                ;; The many-body (or n-body) force applies mutually amongst all nodes. 
+                ;; It can be used to simulate gravity or electrostatic charge.
                 (-> (forceManyBody)
                 (-> (forceManyBody)
-                    (.distanceMax (if (> nodes-count 500) 4000 600))
+                    ;; The minimum distance between nodes over which this force is considered. 
+                    ;; A minimum distance establishes an upper bound on the strength of the force between two nearby nodes, avoiding instability.
+                    (.distanceMin 1)
+                    ;; The maximum distance between nodes over which this force is considered.
+                    ;; Specifying a finite maximum distance improves performance and produces a more localized layout.
+                    (.distanceMax charge-range)
+                    ;; For a cluster of nodes that is far away, the charge force can be approximated by treating the cluster as a single, larger node.
+                    ;; The theta parameter determines the accuracy of the approximation
                     (.theta 0.5)
                     (.theta 0.5)
-                    (.strength -600)))
+                    ;; A positive value causes nodes to attract each other, similar to gravity, 
+                    ;; while a negative value causes nodes to repel each other, similar to electrostatic charge.
+                    (.strength charge-strength)))
         (.force "collision"
         (.force "collision"
                 (-> (forceCollide)
                 (-> (forceCollide)
                     (.radius (+ 8 18))
                     (.radius (+ 8 18))
@@ -75,7 +90,10 @@
         (.force "x" (-> (forceX 0) (.strength 0.02)))
         (.force "x" (-> (forceX 0) (.strength 0.02)))
         (.force "y" (-> (forceY 0) (.strength 0.02)))
         (.force "y" (-> (forceY 0) (.strength 0.02)))
         (.force "center" (forceCenter))
         (.force "center" (forceCenter))
-        (.velocityDecay 0.8))
+        ;; The decay factor is akin to atmospheric friction; after the application of any forces during a tick, 
+        ;; each node’s velocity is multiplied by 1 - decay. As with lowering the alpha decay rate, 
+        ;; less velocity decay may converge on a better solution, but risks numerical instabilities and oscillation.
+        (.velocityDecay 0.5))
     (reset! *simulation simulation)
     (reset! *simulation simulation)
     simulation))
     simulation))
 
 
@@ -167,7 +185,7 @@
     (when @*graph-instance
     (when @*graph-instance
       (clear-nodes! (:graph @*graph-instance))
       (clear-nodes! (:graph @*graph-instance))
       (destroy-instance!))
       (destroy-instance!))
-    (let [{:keys [nodes links style hover-style height register-handlers-fn dark?]} (first (:rum/args state))
+    (let [{:keys [nodes links style hover-style height register-handlers-fn dark? link-dist charge-strength charge-range]} (first (:rum/args state))
           style                                                                     (or style (default-style dark?))
           style                                                                     (or style (default-style dark?))
           hover-style                                                               (or hover-style (default-hover-style dark?))
           hover-style                                                               (or hover-style (default-hover-style dark?))
           graph                                                                     (Graph.)
           graph                                                                     (Graph.)
@@ -182,7 +200,7 @@
           links                                                                     (remove (fn [{:keys [source target]}] (or (nil? source) (nil? target))) links)
           links                                                                     (remove (fn [{:keys [source target]}] (or (nil? source) (nil? target))) links)
           nodes-js                                                                  (bean/->js nodes)
           nodes-js                                                                  (bean/->js nodes)
           links-js                                                                  (bean/->js links)
           links-js                                                                  (bean/->js links)
-          simulation                                                                (layout! nodes-js links-js)]
+          simulation                                                                (layout! nodes-js links-js link-dist charge-strength charge-range)]
       (doseq [node nodes-js]
       (doseq [node nodes-js]
         (try (.addNode graph (.-id node) node)
         (try (.addNode graph (.-id node) node)
           (catch :default e
           (catch :default e

+ 7 - 3
src/main/frontend/extensions/video/youtube.cljs

@@ -51,16 +51,20 @@
        (<! (load-youtube-api))
        (<! (load-youtube-api))
        (register-player state))
        (register-player state))
      state)}
      state)}
-  [state id {:keys [width height] :as _opts}]
+  [state id {:keys [width height start] :as _opts}]
   (let [width  (or width (min (- (util/get-width) 96)
   (let [width  (or width (min (- (util/get-width) 96)
                               560))
                               560))
-        height (or height (int (* width (/ 315 560))))]
+        height (or height (int (* width (/ 315 560))))
+        url (str "https://www.youtube.com/embed/" id "?enablejsapi=1")
+        url (if start
+              (str url "&start=" start)
+              url)]
     [:iframe
     [:iframe
      {:id                (str "youtube-player-" id)
      {:id                (str "youtube-player-" id)
       :allow-full-screen "allowfullscreen"
       :allow-full-screen "allowfullscreen"
       :allow             "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope"
       :allow             "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope"
       :frame-border      "0"
       :frame-border      "0"
-      :src               (str "https://www.youtube.com/embed/" id "?enablejsapi=1")
+      :src               url
       :height            height
       :height            height
       :width             width}]))
       :width             width}]))
 
 

+ 63 - 62
src/main/frontend/handler/events.cljs

@@ -182,16 +182,16 @@
           (repo-handler/broadcast-persist-db! graph))))
           (repo-handler/broadcast-persist-db! graph))))
      (repo-handler/restore-and-setup-repo! graph)
      (repo-handler/restore-and-setup-repo! graph)
      (graph-switch graph)
      (graph-switch graph)
-     state/set-state! :sync-graph/init? false)))
+     (state/set-state! :sync-graph/init? false))))
 
 
 (defmethod handle :graph/switch [[_ graph opts]]
 (defmethod handle :graph/switch [[_ graph opts]]
   (let [opts (if (false? (:persist? opts)) opts (assoc opts :persist? true))]
   (let [opts (if (false? (:persist? opts)) opts (assoc opts :persist? true))]
     (if (or (not (false? (get @outliner-file/*writes-finished? graph)))
     (if (or (not (false? (get @outliner-file/*writes-finished? graph)))
-           (:sync-graph/init? @state/state))
+            (:sync-graph/init? @state/state))
       (graph-switch-on-persisted graph opts)
       (graph-switch-on-persisted graph opts)
-     (notification/show!
-      "Please wait seconds until all changes are saved for the current graph."
-      :warning))))
+      (notification/show!
+       "Please wait seconds until all changes are saved for the current graph."
+       :warning))))
 
 
 (defmethod handle :graph/pull-down-remote-graph [[_ graph dir-name]]
 (defmethod handle :graph/pull-down-remote-graph [[_ graph dir-name]]
   (if (mobile-util/native-ios?)
   (if (mobile-util/native-ios?)
@@ -268,18 +268,18 @@
   (when
   (when
    (and (not (util/electron?))
    (and (not (util/electron?))
         (not (mobile-util/native-platform?)))
         (not (mobile-util/native-platform?)))
-   (fn [close-fn]
-     [:div
+    (fn [close-fn]
+      [:div
       ;; TODO: fn translation with args
       ;; TODO: fn translation with args
-      [:p
-       "Grant native filesystem permission for directory: "
-       [:b (config/get-local-dir repo)]]
-      (ui/button
-       (t :settings-permission/start-granting)
-       :class "ui__modal-enter"
-       :on-click (fn []
-                   (nfs/check-directory-permission! repo)
-                   (close-fn)))])))
+       [:p
+        "Grant native filesystem permission for directory: "
+        [:b (config/get-local-dir repo)]]
+       (ui/button
+        (t :settings-permission/start-granting)
+        :class "ui__modal-enter"
+        :on-click (fn []
+                    (nfs/check-directory-permission! repo)
+                    (close-fn)))])))
 
 
 (defmethod handle :modal/nfs-ask-permission []
 (defmethod handle :modal/nfs-ask-permission []
   (when-let [repo (get-local-repo)]
   (when-let [repo (get-local-repo)]
@@ -377,8 +377,9 @@
       (state/set-modal! #(diff/local-file repo path disk-content db-content)
       (state/set-modal! #(diff/local-file repo path disk-content db-content)
                         {:label "diff__cp"}))))
                         {:label "diff__cp"}))))
 
 
-(defmethod handle :modal/display-file-version [[_ path content hash]]
-  (state/set-modal! #(git-component/file-specific-version path hash content)))
+
+(defmethod handle :modal/display-file-version-selector  [[_ versions path  get-content]]
+  (state/set-modal! #(git-component/file-version-selector versions path get-content)))
 
 
 ;; Hook on a graph is ready to be shown to the user.
 ;; Hook on a graph is ready to be shown to the user.
 ;; It's different from :graph/restored, as :graph/restored is for window reloaded
 ;; It's different from :graph/restored, as :graph/restored is for window reloaded
@@ -431,8 +432,8 @@
 
 
 (defmethod handle :go/proxy-settings [[_ agent-opts]]
 (defmethod handle :go/proxy-settings [[_ agent-opts]]
   (state/set-sub-modal!
   (state/set-sub-modal!
-    (fn [_] (plugin/user-proxy-settings-panel agent-opts))
-    {:id :https-proxy-panel :center? true}))
+   (fn [_] (plugin/user-proxy-settings-panel agent-opts))
+   {:id :https-proxy-panel :center? true}))
 
 
 
 
 (defmethod handle :redirect-to-home [_]
 (defmethod handle :redirect-to-home [_]
@@ -575,14 +576,14 @@
           (if-not error-code
           (if-not error-code
             (plugin/set-updates-sub-content! (str title "...") 0)
             (plugin/set-updates-sub-content! (str title "...") 0)
             (notification/show!
             (notification/show!
-              (str "[Checked]<" title "> " error-code) :error)))))
+             (str "[Checked]<" title "> " error-code) :error)))))
 
 
     (if (and updated? downloading?)
     (if (and updated? downloading?)
       ;; try to start consume downloading item
       ;; try to start consume downloading item
       (if-let [next-coming (state/get-next-selected-coming-update)]
       (if-let [next-coming (state/get-next-selected-coming-update)]
         (plugin-handler/check-or-update-marketplace-plugin!
         (plugin-handler/check-or-update-marketplace-plugin!
-          (assoc next-coming :only-check false :error-code nil)
-          (fn [^js e] (js/console.error "[Download Err]" next-coming e)))
+         (assoc next-coming :only-check false :error-code nil)
+         (fn [^js e] (js/console.error "[Download Err]" next-coming e)))
         (plugin-handler/close-updates-downloading))
         (plugin-handler/close-updates-downloading))
 
 
       ;; try to start consume pending item
       ;; try to start consume pending item
@@ -590,11 +591,11 @@
         (do
         (do
           (println "Updates: take next pending - " (:id next-pending))
           (println "Updates: take next pending - " (:id next-pending))
           (js/setTimeout
           (js/setTimeout
-            #(plugin-handler/check-or-update-marketplace-plugin!
-               (assoc next-pending :only-check true :auto-check auto-checking? :error-code nil)
-               (fn [^js e]
-                 (notification/show! (.toString e) :error)
-                 (js/console.error "[Check Err]" next-pending e))) 500))
+           #(plugin-handler/check-or-update-marketplace-plugin!
+             (assoc next-pending :only-check true :auto-check auto-checking? :error-code nil)
+             (fn [^js e]
+               (notification/show! (.toString e) :error)
+               (js/console.error "[Check Err]" next-pending e))) 500))
 
 
         ;; try to open waiting updates list
         ;; try to open waiting updates list
         (do (when (and prev-pending? (not auto-checking?)
         (do (when (and prev-pending? (not auto-checking?)
@@ -619,7 +620,7 @@
         payload (js->clj event :keywordize-keys true)]
         payload (js->clj event :keywordize-keys true)]
     (fs-watcher/handle-changed! type payload)
     (fs-watcher/handle-changed! type payload)
     (when (file-sync-handler/enable-sync?)
     (when (file-sync-handler/enable-sync?)
-     (sync/file-watch-handler type payload))))
+      (sync/file-watch-handler type payload))))
 
 
 (defmethod handle :rebuild-slash-commands-list [[_]]
 (defmethod handle :rebuild-slash-commands-list [[_]]
   (page-handler/rebuild-slash-commands-list!))
   (page-handler/rebuild-slash-commands-list!))
@@ -640,7 +641,7 @@
       (t :yes)
       (t :yes)
       :autoFocus "on"
       :autoFocus "on"
       :class "ui__modal-enter"
       :class "ui__modal-enter"
-       :on-click (fn []
+      :on-click (fn []
                   (state/close-modal!)
                   (state/close-modal!)
                   (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))
                   (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))
 
 
@@ -660,7 +661,7 @@
                                           :GraphName graph-name
                                           :GraphName graph-name
                                           :remote? true)
                                           :remote? true)
                                    r))
                                    r))
-                            (state/get-repos)))))))
+                               (state/get-repos)))))))
 
 
 (defmethod handle :graph/re-index [[_]]
 (defmethod handle :graph/re-index [[_]]
   ;; Ensure the graph only has ONE window instance
   ;; Ensure the graph only has ONE window instance
@@ -702,12 +703,12 @@
        (when (not (nil? ui)) ui)
        (when (not (nil? ui)) ui)
        [:p (t :re-index-discard-unsaved-changes-warning)]
        [:p (t :re-index-discard-unsaved-changes-warning)]
        (ui/button
        (ui/button
-         (t :yes)
-         :autoFocus "on"
-         :class "ui__modal-enter"
-         :on-click (fn []
-                     (state/close-modal!)
-                     (state/pub-event! [:graph/re-index])))]])))
+        (t :yes)
+        :autoFocus "on"
+        :class "ui__modal-enter"
+        :on-click (fn []
+                    (state/close-modal!)
+                    (state/pub-event! [:graph/re-index])))]])))
 
 
 (defmethod handle :modal/remote-encryption-input-pw-dialog [[_ repo-url remote-graph-info type opts]]
 (defmethod handle :modal/remote-encryption-input-pw-dialog [[_ repo-url remote-graph-info type opts]]
   (state/set-modal!
   (state/set-modal!
@@ -893,17 +894,17 @@
          [:p
          [:p
           "Or, let me"
           "Or, let me"
           (ui/button "Fix"
           (ui/button "Fix"
-            :on-click (fn []
-                        (let [dir (config/get-repo-dir repo)]
-                          (p/let [content (fs/read-file dir file)]
-                            (let [new-content (string/replace content (str id) (str (random-uuid)))]
-                              (p/let [_ (fs/write-file! repo
-                                                        dir
-                                                        file
-                                                        new-content
-                                                        {})]
-                                (reset! resolved? true))))))
-            :class "inline mx-1")
+                     :on-click (fn []
+                                 (let [dir (config/get-repo-dir repo)]
+                                   (p/let [content (fs/read-file dir file)]
+                                     (let [new-content (string/replace content (str id) (str (random-uuid)))]
+                                       (p/let [_ (fs/write-file! repo
+                                                                 dir
+                                                                 file
+                                                                 new-content
+                                                                 {})]
+                                         (reset! resolved? true))))))
+                     :class "inline mx-1")
           "it."]])]]))
           "it."]])]]))
 
 
 (defmethod handle :file/parse-and-load-error [[_ repo parse-errors]]
 (defmethod handle :file/parse-and-load-error [[_ repo parse-errors]]
@@ -915,18 +916,18 @@
                         (for [[file error] parse-errors]
                         (for [[file error] parse-errors]
                           (let [data (ex-data error)]
                           (let [data (ex-data error)]
                             (cond
                             (cond
-                             (and (gp-config/whiteboard? file)
-                                  (= :transact/upsert (:error data))
-                                  (uuid? (last (:assertion data))))
-                             (rum/with-key (file-id-conflict-item repo file data) file)
-
-                             :else
-                             (do
-                               (state/pub-event! [:capture-error {:error error
-                                                                  :payload {:type :file/parse-and-load-error}}])
-                               [:li.my-1 {:key file}
-                                [:a {:on-click #(js/window.apis.openPath file)} file]
-                                [:p (.-message error)]]))))]
+                              (and (gp-config/whiteboard? file)
+                                   (= :transact/upsert (:error data))
+                                   (uuid? (last (:assertion data))))
+                              (rum/with-key (file-id-conflict-item repo file data) file)
+
+                              :else
+                              (do
+                                (state/pub-event! [:capture-error {:error error
+                                                                   :payload {:type :file/parse-and-load-error}}])
+                                [:li.my-1 {:key file}
+                                 [:a {:on-click #(js/window.apis.openPath file)} file]
+                                 [:p (.-message error)]]))))]
                        [:p "Don't forget to re-index your graph when all the conflicts are resolved."]]
                        [:p "Don't forget to re-index your graph when all the conflicts are resolved."]]
                       :status :error}]))
                       :status :error}]))
 
 
@@ -949,8 +950,8 @@
 (defmethod handle :editor/toggle-own-number-list [[_ blocks]]
 (defmethod handle :editor/toggle-own-number-list [[_ blocks]]
   (let [batch? (sequential? blocks)
   (let [batch? (sequential? blocks)
         blocks (cond->> blocks
         blocks (cond->> blocks
-                  batch?
-                  (map #(cond-> % (or (uuid? %) (string? %)) (db-model/get-block-by-uuid))))]
+                 batch?
+                 (map #(cond-> % (or (uuid? %) (string? %)) (db-model/get-block-by-uuid))))]
     (if (and batch? (> (count blocks) 1))
     (if (and batch? (> (count blocks) 1))
       (editor-handler/toggle-blocks-as-own-order-list! blocks)
       (editor-handler/toggle-blocks-as-own-order-list! blocks)
       (when-let [block (cond-> blocks batch? (first))]
       (when-let [block (cond-> blocks batch? (first))]

+ 9 - 30
src/main/frontend/handler/shell.cljs

@@ -1,14 +1,13 @@
 (ns frontend.handler.shell
 (ns frontend.handler.shell
   "Git related handler fns"
   "Git related handler fns"
-  (:require [electron.ipc :as ipc]
-            [clojure.string :as string]
-            [logseq.graph-parser.util :as gp-util]
-            [frontend.handler.notification :as notification]
-            [promesa.core :as p]
+  (:require [clojure.string :as string]
+            [electron.ipc :as ipc]
             [frontend.db :as db]
             [frontend.db :as db]
+            [frontend.handler.notification :as notification]
             [frontend.state :as state]
             [frontend.state :as state]
-            [frontend.config :as config]
-            [frontend.util :as util]))
+            [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
+            [promesa.core :as p]))
 
 
 (defn run-git-command!
 (defn run-git-command!
   [command]
   [command]
@@ -29,7 +28,7 @@
   (p/let [result (f command args)]
   (p/let [result (f command args)]
     (notification/show!
     (notification/show!
      (if (string/blank? result)
      (if (string/blank? result)
-       [:p [:code.mr-1 (str command " " args) ]
+       [:p [:code.mr-1 (str command " " args)]
         "was executed successfully!"]
         "was executed successfully!"]
        result)
        result)
      :success
      :success
@@ -59,15 +58,6 @@
         :else
         :else
         (run-cli-command! command args)))))
         (run-cli-command! command args)))))
 
 
-;; git show $REV:$FILE
-(defn- get-versioned-file-content
-  [hash path]
-  (when (and hash path)
-    (let [repo (state/get-current-repo)
-          local-dir (config/get-local-dir repo)
-          path (string/replace path (str local-dir "/") "")]
-      (p/let [content (run-git-command! ["show" (str hash ":" path)])]
-        (state/pub-event! [:modal/display-file-version path content hash])))))
 
 
 (defn get-file-latest-git-log
 (defn get-file-latest-git-log
   [page n]
   [page n]
@@ -77,19 +67,8 @@
         (p/let [result (run-git-command! ["log" (str "-" n) "--pretty=format:Commit: %C(auto)%h$$$%s$$$%ad" "-p" path])
         (p/let [result (run-git-command! ["log" (str "-" n) "--pretty=format:Commit: %C(auto)%h$$$%s$$$%ad" "-p" path])
                 lines (->> (string/split-lines result)
                 lines (->> (string/split-lines result)
                            (filter #(string/starts-with? % "Commit: ")))]
                            (filter #(string/starts-with? % "Commit: ")))]
-          (notification/show! [:div
-                               [:div.font-bold "File history - " path]
-                               (for [line lines]
-                                 (let [[hash title time] (string/split line "$$$")
-                                       hash (subs hash 8)]
-                                   [:div.my-4 {:key hash}
-                                    [:hr]
-                                    [:div.mb-2
-                                     [:a.font-medium.mr-1.inline
-                                      {:on-click (fn [] (get-versioned-file-content hash path))}
-                                      hash]
-                                     title]
-                                    [:div.opacity-50 time]]))] :success false))))))
+          (state/pub-event! [:modal/display-file-version-selector  lines path  (fn [hash path] (run-git-command! ["show" (str hash ":" path)]))]))))))
+
 
 
 (defn set-git-username-and-email
 (defn set-git-username-and-email
   [username email]
   [username email]

+ 1 - 0
src/main/frontend/schema/handler/common_config.cljc

@@ -63,6 +63,7 @@
     [:ref/default-open-blocks-level :int]
     [:ref/default-open-blocks-level :int]
     [:ref/linked-references-collapsed-threshold :int]
     [:ref/linked-references-collapsed-threshold :int]
     [:graph/settings [:map-of :keyword :boolean]]
     [:graph/settings [:map-of :keyword :boolean]]
+    [:graph/forcesettings [:map-of :keyword :int]]
     [:favorites [:vector :string]]
     [:favorites [:vector :string]]
     ;; There isn't a :float yet
     ;; There isn't a :float yet
     [:srs/learning-fraction float?]
     [:srs/learning-fraction float?]

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

@@ -644,6 +644,10 @@ Similar to re-frame subscriptions"
   []
   []
   (:graph/settings (sub-config)))
   (:graph/settings (sub-config)))
 
 
+(defn graph-forcesettings
+  []
+  (:graph/forcesettings (sub-config)))
+
 ;; Enable by default
 ;; Enable by default
 (defn show-brackets?
 (defn show-brackets?
   []
   []

+ 7 - 0
src/resources/templates/config.edn

@@ -291,6 +291,13 @@
  ;;  :excluded-pages? false  ; Default value: false
  ;;  :excluded-pages? false  ; Default value: false
  ;;  :journal?        false} ; Default value: false
  ;;  :journal?        false} ; Default value: false
 
 
+ ;; Graph view configuration.
+ ;; Example usage:
+ ;; :graph/forcesettings
+ ;; {:link-dist       180    ; Default value: 180
+ ;;  :charge-strength -600   ; Default value: -600
+ ;;  :charge-range    600}   ; Default value: 600
+
  ;; Favorites to list on the left sidebar
  ;; Favorites to list on the left sidebar
  :favorites []
  :favorites []