feat: git integration

pull/2708/head
Tienson Qin 2021-08-24 17:50:36 +08:00
parent 78de8ce5bc
commit 4c3bdb0bf3
16 changed files with 298 additions and 39 deletions

View File

@ -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 {{

View File

@ -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",

View File

@ -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)

View File

@ -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)))))

View File

@ -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)))

View File

@ -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))

View File

@ -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)))))

View File

@ -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!))

View File

@ -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))]])

View File

@ -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 "/")

View File

@ -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 {}))
{})

View File

@ -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)]

View File

@ -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))))))

View File

@ -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!}

View File

@ -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]

View File

@ -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
}