فهرست منبع

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)]
         (if (and (coll? src)
                  (= (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
             (let [width (min (- (util/get-width) 96) 560)
                   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
   (:require [rum.core :as rum]
             [frontend.ui :as ui]
+            [promesa.core :as p]
             [frontend.util :as util]
             [clojure.string :as string]
             [frontend.handler.shell :as shell]
@@ -36,23 +37,42 @@
 
      [:div.mt-5.sm:mt-4.flex
       (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 *focus-nodes (atom []))
 (defonce *graph-reset? (atom false))
+(defonce *graph-forcereset? (atom false))
 (defonce *journal? (atom nil))
 (defonce *orphan-pages? (atom true))
 (defonce *builtin-pages? (atom nil))
 (defonce *excluded-pages? (atom true))
 (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
-  [graph settings n-hops]
+  [graph settings forcesettings n-hops]
   (let [{:keys [journal? orphan-pages? builtin-pages? excluded-pages?]
          :or {orphan-pages? true}} settings
+        {:keys [link-dist charge-strength charge-range]} forcesettings
         journal?' (rum/react *journal?)
         orphan-pages?' (rum/react *orphan-pages?)
         builtin-pages?' (rum/react *builtin-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?')
         orphan-pages? (if (nil? orphan-pages?') orphan-pages? orphan-pages?')
         builtin-pages? (if (nil? builtin-pages?') builtin-pages? builtin-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]
                        (let [new-settings (assoc settings key value)]
                          (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)
         focus-nodes (rum/react *focus-nodes)]
     [: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)}
                 "Click to search"])]))
          {: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
          [:span.font-medium "Export"]
          (fn [open?]
@@ -699,11 +771,15 @@
          (reset! last-node-position [node (.-x event) (.-y event)]))))
 
 (rum/defc global-graph-inner < rum/reactive
-  [graph settings theme]
+  [graph settings forcesettings theme]
   (let [[width height] (rum/react layout)
         dark? (= theme "dark")
         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?)
+        forcereset? (rum/react *graph-forcereset?)
         focus-nodes (when n-hops (rum/react *focus-nodes))
         graph (if (and (integer? n-hops)
                        (seq focus-nodes)
@@ -722,11 +798,15 @@
                       :width (- width 24)
                       :height (- height 48)
                       :dark? dark?
+                      :link-dist link-dist
+                      :charge-strength charge-strength
+                      :charge-range charge-range
                       :register-handlers-fn
                       (fn [graph]
                         (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
   [nodes filters]
@@ -741,21 +821,19 @@
      (mixins/listen state js/window "resize"
                     (fn [_e]
                       (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! *focus-nodes [])
                    (state/set-search-mode! :global)
                    state)}
   [state]
   (let [settings (state/graph-settings)
+        forcesettings (state/graph-forcesettings)
         theme (state/sub :ui/theme)
         graph (graph-handler/build-global-graph theme settings)
         search-graph-filters (state/sub :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
   [_page graph dark?]

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

@@ -75,6 +75,8 @@
           _ (state/sub :auth/id-token)
           file-sync-graph-uuid (and (user-handler/logged-in?)
                                     (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))]
       (when (and page (not block?))
         (->>

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

@@ -51,9 +51,9 @@
   {:did-update pixi/render!
    :should-update (fn [old-state new-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))
-                                       [:nodes :links :dark?])))
+                                       [:nodes :links :dark? :link-dist :charge-strength :charge-range])))
    :will-unmount (fn [state]
                    (reset! pixi/*graph-instance nil)
                    state)}

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

@@ -54,20 +54,35 @@
    :edge {:color "#A5B4FC"}})
 
 (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)
         simulation (forceSimulation nodes)]
-    (-> simulation
+    (-> simulation 
         (.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)
                     (.id (fn [d] (.-id d)))
-                    (.distance 180)
+                    (.distance link-dist)
                     (.links links)))
         (.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)
-                    (.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)
-                    (.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"
                 (-> (forceCollide)
                     (.radius (+ 8 18))
@@ -75,7 +90,10 @@
         (.force "x" (-> (forceX 0) (.strength 0.02)))
         (.force "y" (-> (forceY 0) (.strength 0.02)))
         (.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)
     simulation))
 
@@ -167,7 +185,7 @@
     (when @*graph-instance
       (clear-nodes! (:graph @*graph-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?))
           hover-style                                                               (or hover-style (default-hover-style dark?))
           graph                                                                     (Graph.)
@@ -182,7 +200,7 @@
           links                                                                     (remove (fn [{:keys [source target]}] (or (nil? source) (nil? target))) links)
           nodes-js                                                                  (bean/->js nodes)
           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]
         (try (.addNode graph (.-id node) node)
           (catch :default e

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

@@ -51,16 +51,20 @@
        (<! (load-youtube-api))
        (register-player 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)
                               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
      {:id                (str "youtube-player-" id)
       :allow-full-screen "allowfullscreen"
       :allow             "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope"
       :frame-border      "0"
-      :src               (str "https://www.youtube.com/embed/" id "?enablejsapi=1")
+      :src               url
       :height            height
       :width             width}]))
 

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

@@ -182,16 +182,16 @@
           (repo-handler/broadcast-persist-db! graph))))
      (repo-handler/restore-and-setup-repo! graph)
      (graph-switch graph)
-     state/set-state! :sync-graph/init? false)))
+     (state/set-state! :sync-graph/init? false))))
 
 (defmethod handle :graph/switch [[_ graph opts]]
   (let [opts (if (false? (:persist? opts)) opts (assoc opts :persist? true))]
     (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)
-     (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]]
   (if (mobile-util/native-ios?)
@@ -268,18 +268,18 @@
   (when
    (and (not (util/electron?))
         (not (mobile-util/native-platform?)))
-   (fn [close-fn]
-     [:div
+    (fn [close-fn]
+      [:div
       ;; 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 []
   (when-let [repo (get-local-repo)]
@@ -377,8 +377,9 @@
       (state/set-modal! #(diff/local-file repo path disk-content db-content)
                         {: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.
 ;; It's different from :graph/restored, as :graph/restored is for window reloaded
@@ -431,8 +432,8 @@
 
 (defmethod handle :go/proxy-settings [[_ agent-opts]]
   (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 [_]
@@ -575,14 +576,14 @@
           (if-not error-code
             (plugin/set-updates-sub-content! (str title "...") 0)
             (notification/show!
-              (str "[Checked]<" title "> " error-code) :error)))))
+             (str "[Checked]<" title "> " error-code) :error)))))
 
     (if (and updated? downloading?)
       ;; try to start consume downloading item
       (if-let [next-coming (state/get-next-selected-coming-update)]
         (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))
 
       ;; try to start consume pending item
@@ -590,11 +591,11 @@
         (do
           (println "Updates: take next pending - " (:id next-pending))
           (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
         (do (when (and prev-pending? (not auto-checking?)
@@ -619,7 +620,7 @@
         payload (js->clj event :keywordize-keys true)]
     (fs-watcher/handle-changed! type payload)
     (when (file-sync-handler/enable-sync?)
-     (sync/file-watch-handler type payload))))
+      (sync/file-watch-handler type payload))))
 
 (defmethod handle :rebuild-slash-commands-list [[_]]
   (page-handler/rebuild-slash-commands-list!))
@@ -640,7 +641,7 @@
       (t :yes)
       :autoFocus "on"
       :class "ui__modal-enter"
-       :on-click (fn []
+      :on-click (fn []
                   (state/close-modal!)
                   (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))
 
@@ -660,7 +661,7 @@
                                           :GraphName graph-name
                                           :remote? true)
                                    r))
-                            (state/get-repos)))))))
+                               (state/get-repos)))))))
 
 (defmethod handle :graph/re-index [[_]]
   ;; Ensure the graph only has ONE window instance
@@ -702,12 +703,12 @@
        (when (not (nil? ui)) ui)
        [:p (t :re-index-discard-unsaved-changes-warning)]
        (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]]
   (state/set-modal!
@@ -893,17 +894,17 @@
          [:p
           "Or, let me"
           (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."]])]]))
 
 (defmethod handle :file/parse-and-load-error [[_ repo parse-errors]]
@@ -915,18 +916,18 @@
                         (for [[file error] parse-errors]
                           (let [data (ex-data error)]
                             (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."]]
                       :status :error}]))
 
@@ -949,8 +950,8 @@
 (defmethod handle :editor/toggle-own-number-list [[_ blocks]]
   (let [batch? (sequential? 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))
       (editor-handler/toggle-blocks-as-own-order-list! blocks)
       (when-let [block (cond-> blocks batch? (first))]

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

@@ -1,14 +1,13 @@
 (ns frontend.handler.shell
   "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.handler.notification :as notification]
             [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!
   [command]
@@ -29,7 +28,7 @@
   (p/let [result (f command args)]
     (notification/show!
      (if (string/blank? result)
-       [:p [:code.mr-1 (str command " " args) ]
+       [:p [:code.mr-1 (str command " " args)]
         "was executed successfully!"]
        result)
      :success
@@ -59,15 +58,6 @@
         :else
         (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
   [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])
                 lines (->> (string/split-lines result)
                            (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
   [username email]

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

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

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

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

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

@@ -291,6 +291,13 @@
  ;;  :excluded-pages? 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 []