mirror of https://github.com/logseq/logseq
feat: git integration
parent
78de8ce5bc
commit
4c3bdb0bf3
|
@ -115,6 +115,9 @@ dummy.getPageView = function() {};
|
|||
dummy.convertToPdfPoint = function() {};
|
||||
dummy.scrollPageIntoView = function() {};
|
||||
dummy.convertToViewportRectangle = function() {};
|
||||
dummy.init = function() {};
|
||||
dummy.commit = function() {};
|
||||
dummy.raw = function() {};
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"node-fetch": "^2.6.1",
|
||||
"open": "^7.3.1",
|
||||
"semver": "^7.3.5",
|
||||
"update-electron-app": "^2.0.1"
|
||||
"update-electron-app": "^2.0.1",
|
||||
"simple-git": "2.44.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^6.0.0-beta.57",
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
["electron" :refer [BrowserWindow app protocol ipcMain dialog Menu MenuItem] :as electron]
|
||||
["electron-window-state" :as windowStateKeeper]
|
||||
[clojure.core.async :as async]
|
||||
[electron.state :as state]))
|
||||
[electron.state :as state]
|
||||
[electron.git :as git]))
|
||||
|
||||
(def MAIN_WINDOW_ENTRY (if dev?
|
||||
"http://localhost:3001"
|
||||
|
@ -224,6 +225,8 @@
|
|||
|
||||
(search/open-dbs!)
|
||||
|
||||
(git/auto-commit-current-graph!)
|
||||
|
||||
(vreset! *setup-fn
|
||||
(fn []
|
||||
(let [t0 (setup-updater! win)
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
(ns electron.git
|
||||
(:require ["child_process" :as child-process]
|
||||
["simple-git" :as simple-git]
|
||||
[goog.object :as gobj]
|
||||
[electron.state :as state]
|
||||
[electron.utils :as utils]
|
||||
[promesa.core :as p]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(def spawn-sync (gobj/get child-process "spawnSync"))
|
||||
|
||||
(defonce gits
|
||||
(atom {}))
|
||||
|
||||
(defn installed?
|
||||
[]
|
||||
(let [command (spawn-sync "git"
|
||||
#js ["--version"]
|
||||
#js {:stdio "ignore"})]
|
||||
(if-let [error (gobj/get command "error")]
|
||||
(do
|
||||
(js/console.error error)
|
||||
false)
|
||||
true)))
|
||||
|
||||
(defn get-git
|
||||
[]
|
||||
(when (installed?)
|
||||
(when-let [path (:graph/current @state/state)]
|
||||
(if-let [result (get @gits path)]
|
||||
result
|
||||
(let [result (simple-git path)]
|
||||
(swap! gits assoc path result)
|
||||
result)))))
|
||||
|
||||
(defn init!
|
||||
[]
|
||||
(when-let [git ^js (get-git)]
|
||||
(.init git false)))
|
||||
|
||||
(defn add-all!
|
||||
([]
|
||||
(add-all! (get-git)))
|
||||
([^js git]
|
||||
(when git
|
||||
(.add git "./*" (fn [error] (js/console.error error))))))
|
||||
|
||||
(defn add-all-and-commit!
|
||||
([]
|
||||
(add-all-and-commit! "Auto saved by Logseq"))
|
||||
([message]
|
||||
(when-let [git ^js (get-git)]
|
||||
(p/let [_ (add-all! git)]
|
||||
(.commit git message)))))
|
||||
|
||||
(defonce quotes-regex #"\"[^\"]+\"")
|
||||
(defn wrapped-by-quotes?
|
||||
[v]
|
||||
(and (string? v) (>= (count v) 2) (= "\"" (first v) (last v))))
|
||||
|
||||
(defn unquote-string
|
||||
[v]
|
||||
(string/trim (subs v 1 (dec (count v)))))
|
||||
|
||||
(defn- split-args
|
||||
[s]
|
||||
(let [quotes (re-seq quotes-regex s)
|
||||
non-quotes (string/split s quotes-regex)
|
||||
col (if (seq quotes)
|
||||
(concat (interleave non-quotes quotes)
|
||||
(drop (count quotes) non-quotes))
|
||||
non-quotes)]
|
||||
(->> col
|
||||
(map (fn [s]
|
||||
(if (wrapped-by-quotes? s)
|
||||
[(unquote-string s)]
|
||||
(string/split s #"\s"))))
|
||||
(flatten)
|
||||
(remove string/blank?))))
|
||||
|
||||
(defn raw!
|
||||
[args & {:keys [ok-handler error-handler]}]
|
||||
(when-let [git ^js (get-git)]
|
||||
(let [args (if (string? args)
|
||||
(split-args args)
|
||||
args)
|
||||
ok-handler (if ok-handler
|
||||
ok-handler
|
||||
(fn [result]
|
||||
(utils/send-to-renderer "notification" {:type "success"
|
||||
:payload result})))
|
||||
error-handler (if error-handler
|
||||
error-handler
|
||||
(fn [error]
|
||||
(js/console.dir error)
|
||||
(utils/send-to-renderer "notification" {:type "error"
|
||||
:payload (.toString error)})))]
|
||||
(p/let [_ (when (= (first args) "commit")
|
||||
(add-all!))]
|
||||
(->
|
||||
(p/let [result (.raw git (clj->js args))]
|
||||
(when ok-handler
|
||||
(ok-handler result)))
|
||||
(p/catch error-handler))))))
|
||||
|
||||
(defn auto-commit-current-graph!
|
||||
[]
|
||||
(when (installed?)
|
||||
(state/clear-git-commit-interval!)
|
||||
(p/let [_ (add-all-and-commit!)]
|
||||
(let [seconds (state/get-git-commit-seconds)
|
||||
interval (js/setInterval add-all-and-commit! (* seconds 1000))]
|
||||
(state/set-git-commit-interval! interval)))))
|
|
@ -13,7 +13,8 @@
|
|||
[electron.utils :as utils]
|
||||
[electron.state :as state]
|
||||
[clojure.core.async :as async]
|
||||
[electron.search :as search]))
|
||||
[electron.search :as search]
|
||||
[electron.git :as git]))
|
||||
|
||||
(defmulti handle (fn [_window args] (keyword (first args))))
|
||||
|
||||
|
@ -198,6 +199,14 @@
|
|||
(defmethod handle :getDirname [_]
|
||||
js/__dirname)
|
||||
|
||||
(defmethod handle :setCurrentGraph [_ [_ path]]
|
||||
(let [path (when path (string/replace path "logseq_local_" ""))]
|
||||
(swap! state/state assoc :graph/current path)))
|
||||
|
||||
(defmethod handle :runGit [_ [_ args]]
|
||||
(when (seq args)
|
||||
(git/raw! args)))
|
||||
|
||||
(defmethod handle :default [args]
|
||||
(println "Error: no ipc handler for: " (bean/->js args)))
|
||||
|
||||
|
|
|
@ -2,3 +2,34 @@
|
|||
(:require [clojure.core.async :as async]))
|
||||
|
||||
(defonce persistent-dbs-chan (async/chan 1))
|
||||
|
||||
(defonce state
|
||||
(atom {:graph/current nil
|
||||
:git/auto-commit-seconds 60
|
||||
:git/auto-commit-interval nil}))
|
||||
|
||||
(defn set-state!
|
||||
[path value]
|
||||
(if (vector? path)
|
||||
(swap! state assoc-in path value)
|
||||
(swap! state assoc path value)))
|
||||
|
||||
(defn set-git-commit-interval!
|
||||
[v]
|
||||
(set-state! :git/auto-commit-interval v))
|
||||
|
||||
(defn clear-git-commit-interval!
|
||||
[]
|
||||
(when-let [interval (get @state :git/auto-commit-interval)]
|
||||
(js/clearInterval interval)))
|
||||
|
||||
(defn set-git-commit-seconds!
|
||||
[v]
|
||||
(let [v (if (and (integer? v) (< 0 v (inc (* 60 10)))) ; max 10 minutes
|
||||
v
|
||||
60)]
|
||||
(set-state! :git/auto-commit-seconds v)))
|
||||
|
||||
(defn get-git-commit-seconds
|
||||
[]
|
||||
(or (get @state :git/auto-commit-seconds) 60))
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
(:require [clojure.string :as string]
|
||||
["fs" :as fs]
|
||||
["path" :as path]
|
||||
[clojure.string :as string]))
|
||||
[clojure.string :as string]
|
||||
[cljs-bean.core :as bean]
|
||||
["electron" :refer [BrowserWindow]]))
|
||||
|
||||
(defonce mac? (= (.-platform js/process) "darwin"))
|
||||
(defonce win32? (= (.-platform js/process) "win32"))
|
||||
|
@ -45,3 +47,13 @@
|
|||
[path]
|
||||
(when (fs/existsSync path)
|
||||
(.toString (fs/readFileSync path))))
|
||||
|
||||
(defn get-focused-window
|
||||
[]
|
||||
(.getFocusedWindow BrowserWindow))
|
||||
|
||||
(defn send-to-renderer
|
||||
[kind payload]
|
||||
(when-let [window (get-focused-window)]
|
||||
(.. ^js window -webContents
|
||||
(send kind (bean/->js payload)))))
|
||||
|
|
|
@ -11,22 +11,6 @@
|
|||
[frontend.handler.metadata :as metadata-handler]
|
||||
[frontend.ui :as ui]))
|
||||
|
||||
(defn listen-to-open-dir!
|
||||
[]
|
||||
(js/window.apis.on "open-dir-confirmed"
|
||||
(fn []
|
||||
(state/set-loading-files! true)
|
||||
(when-not (state/home?)
|
||||
(route-handler/redirect-to-home!)))))
|
||||
|
||||
(defn run-dirs-watcher!
|
||||
[]
|
||||
;; TODO: move "file-watcher" to electron.ipc.channels
|
||||
(js/window.apis.on "file-watcher"
|
||||
(fn [data]
|
||||
(let [{:keys [type payload]} (bean/->clj data)]
|
||||
(watcher-handler/handle-changed! type payload)))))
|
||||
|
||||
(defn listen-persistent-dbs!
|
||||
[]
|
||||
;; TODO: move "file-watcher" to electron.ipc.channels
|
||||
|
@ -54,8 +38,28 @@
|
|||
100))
|
||||
(ipc/ipc "persistent-dbs-saved"))))))
|
||||
|
||||
(defn listen-to-electron!
|
||||
[]
|
||||
(js/window.apis.on "open-dir-confirmed"
|
||||
(fn []
|
||||
(state/set-loading-files! true)
|
||||
(when-not (state/home?)
|
||||
(route-handler/redirect-to-home!))))
|
||||
|
||||
;; TODO: move "file-watcher" to electron.ipc.channels
|
||||
(js/window.apis.on "file-watcher"
|
||||
(fn [data]
|
||||
(let [{:keys [type payload]} (bean/->clj data)]
|
||||
(watcher-handler/handle-changed! type payload))))
|
||||
|
||||
(js/window.apis.on "notification"
|
||||
(fn [data]
|
||||
(let [{:keys [type payload]} (bean/->clj data)
|
||||
type (keyword type)
|
||||
comp [:div (str payload)]]
|
||||
(notification/show! comp type false)))))
|
||||
|
||||
(defn listen!
|
||||
[]
|
||||
(listen-to-open-dir!)
|
||||
(run-dirs-watcher!)
|
||||
(listen-to-electron!)
|
||||
(listen-persistent-dbs!))
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
(ns frontend.components.shell
|
||||
(:require [rum.core :as rum]
|
||||
[frontend.ui :as ui]
|
||||
[frontend.util :as util]
|
||||
[frontend.handler.shell :as shell-handler]
|
||||
[clojure.string :as string]
|
||||
[frontend.mixins :as mixins]))
|
||||
|
||||
(defn- run-command
|
||||
[command]
|
||||
(when-not (string/blank? @command)
|
||||
(shell-handler/run-command! @command)))
|
||||
|
||||
(defonce command (atom ""))
|
||||
(rum/defcs shell < rum/reactive
|
||||
(mixins/event-mixin
|
||||
(fn [state]
|
||||
(mixins/on-enter state
|
||||
:on-enter (fn [state]
|
||||
(run-command command)))))
|
||||
[state]
|
||||
[:div.flex.flex-col
|
||||
[:div.w-full.mx-auto.sm:max-w-lg.sm:w-96
|
||||
[:div
|
||||
[:div
|
||||
[:h1.title
|
||||
"Input command"]
|
||||
[:div.mt-4.mb-4.relative.rounded-md.shadow-sm.max-w-xs
|
||||
[:input#run-command.form-input.block.w-full.sm:text-sm.sm:leading-5
|
||||
{:autoFocus true
|
||||
:placeholder "git ..."
|
||||
:on-change (fn [e]
|
||||
(reset! command (util/evalue e)))}]]]]
|
||||
(ui/button "Run" :on-click #(run-command command))]])
|
|
@ -269,16 +269,17 @@
|
|||
(defn convert-page-if-journal
|
||||
"Convert journal file name to user' custom date format"
|
||||
[original-page-name]
|
||||
(let [page-name (string/lower-case original-page-name)
|
||||
day (date/journal-title->int page-name)]
|
||||
(if day
|
||||
(let [original-page-name (date/int->journal-title day)]
|
||||
[original-page-name (string/lower-case original-page-name) day])
|
||||
[original-page-name page-name day])))
|
||||
(when original-page-name
|
||||
(let [page-name (string/lower-case original-page-name)
|
||||
day (date/journal-title->int page-name)]
|
||||
(if day
|
||||
(let [original-page-name (date/int->journal-title day)]
|
||||
[original-page-name (string/lower-case original-page-name) day])
|
||||
[original-page-name page-name day]))))
|
||||
|
||||
(defn page-name->map
|
||||
[original-page-name with-id?]
|
||||
(when original-page-name
|
||||
(when (and original-page-name (string? original-page-name))
|
||||
(let [original-page-name (util/remove-boundary-slashes original-page-name)
|
||||
[original-page-name page-name journal-day] (convert-page-if-journal original-page-name)
|
||||
namespace? (and (string/includes? original-page-name "/")
|
||||
|
|
|
@ -152,7 +152,7 @@
|
|||
properties-ast
|
||||
(map (fn [[k v]]
|
||||
(let [k (keyword (string/lower-case k))
|
||||
v (if (contains? #{:title :description :filters :roam_tags} k)
|
||||
v (if (contains? #{:title :description :filters :roam_tags :macro} k)
|
||||
v
|
||||
(text/split-page-refs-without-brackets v))]
|
||||
[k v]))))
|
||||
|
@ -162,10 +162,11 @@
|
|||
(->>
|
||||
(map
|
||||
(fn [[_ v]]
|
||||
(let [[k v] (util/split-first " " v)]
|
||||
(mapv
|
||||
string/trim
|
||||
[k v])))
|
||||
(do
|
||||
(let [[k v] (util/split-first " " v)]
|
||||
(mapv
|
||||
string/trim
|
||||
[k v]))))
|
||||
macro-properties)
|
||||
(into {}))
|
||||
{})
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
[frontend.handler.editor :as editor-handler]
|
||||
[frontend.handler.page :as page-handler]
|
||||
[frontend.components.encryption :as encryption]
|
||||
[frontend.components.shell :as shell]
|
||||
[frontend.fs.nfs :as nfs]
|
||||
[frontend.db.conn :as conn]
|
||||
[frontend.extensions.srs :as srs]
|
||||
|
@ -155,6 +156,10 @@
|
|||
false))))
|
||||
repos))
|
||||
|
||||
(defmethod handle :command/run [_]
|
||||
(when (util/electron?)
|
||||
(state/set-modal! shell/shell)))
|
||||
|
||||
(defn run!
|
||||
[]
|
||||
(let [chan (state/get-events-chan)]
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
(ns frontend.handler.shell
|
||||
(:require [electron.ipc :as ipc]
|
||||
[clojure.string :as string]
|
||||
[frontend.util :as util]
|
||||
[frontend.handler.notification :as notification]))
|
||||
|
||||
(defn run-git-command!
|
||||
[command]
|
||||
(ipc/ipc "runGit" command))
|
||||
|
||||
(defn run-pandoc-command!
|
||||
[command]
|
||||
(ipc/ipc "runPandoc" command))
|
||||
|
||||
(defn run-command!
|
||||
[command]
|
||||
(let [[command args] (util/split-first " " command)
|
||||
command (and command (string/lower-case command))]
|
||||
(when (and (not (string/blank? command)) (not (string/blank? args)))
|
||||
(let [args (string/trim args)]
|
||||
(case (keyword command)
|
||||
:git
|
||||
(run-git-command! args)
|
||||
|
||||
:pandoc
|
||||
(run-pandoc-command! args)
|
||||
|
||||
(notification/show!
|
||||
[:div (str command " is not supported yet!")]
|
||||
:error))))))
|
|
@ -322,7 +322,11 @@
|
|||
|
||||
:shortcut.handler/global-non-editing-only
|
||||
^{:before m/enable-when-not-editing-mode!}
|
||||
{:ui/toggle-document-mode
|
||||
{:command/run
|
||||
{:desc "Run git/pandoc/others commands"
|
||||
:binding "r"
|
||||
:fn #(state/pub-event! [:command/run])}
|
||||
:ui/toggle-document-mode
|
||||
{:desc "Toggle document mode"
|
||||
:binding "t d"
|
||||
:fn state/toggle-document-mode!}
|
||||
|
|
|
@ -17,8 +17,11 @@
|
|||
[cljs-time.format :as tf]))
|
||||
|
||||
(defonce ^:private state
|
||||
(atom
|
||||
(let [document-mode? (or (storage/get :document/mode?) false)]
|
||||
(let [document-mode? (or (storage/get :document/mode?) false)
|
||||
current-graph (let [graph (storage/get :git/current-repo)]
|
||||
(when graph (ipc/ipc "setCurrentGraph" graph))
|
||||
graph)]
|
||||
(atom
|
||||
{:route-match nil
|
||||
:today nil
|
||||
:system/events (async/chan 100)
|
||||
|
@ -38,7 +41,7 @@
|
|||
:network/online? true
|
||||
:indexeddb/support? true
|
||||
:me nil
|
||||
:git/current-repo (storage/get :git/current-repo)
|
||||
:git/current-repo current-graph
|
||||
:git/status {}
|
||||
:format/loading {}
|
||||
:draw? false
|
||||
|
@ -391,7 +394,8 @@
|
|||
(swap! state assoc :git/current-repo repo)
|
||||
(if repo
|
||||
(storage/set :git/current-repo repo)
|
||||
(storage/remove :git/current-repo)))
|
||||
(storage/remove :git/current-repo))
|
||||
(ipc/ipc "setCurrentGraph" repo))
|
||||
|
||||
(defn set-preferred-format!
|
||||
[format]
|
||||
|
|
|
@ -169,4 +169,8 @@
|
|||
;; hide specific properties for blocks
|
||||
;; E.g. #{:created-at :updated-at}
|
||||
;; :block-hidden-properties #{}
|
||||
|
||||
;; only for the desktop app
|
||||
:git/auto-commit-seconds 60
|
||||
:git/disable-auto-commit? false
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue