Fix clone

pull/645/head
Tienson Qin 2020-03-01 15:40:35 +08:00
parent 9265ba957d
commit fee8e9d407
18 changed files with 225 additions and 309 deletions

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -8,11 +8,11 @@
[clojure.string :as string]))
(rum/defc files-list
[files]
[current-repo files]
[:div
(if (seq files)
(let [files-set (set files)
prefix [(files-set "tasks.org") (files-set "links.org")]
prefix [(files-set "tasks.org")]
files (->> (remove (set prefix) files)
(concat prefix)
(remove nil?))]
@ -23,7 +23,7 @@
:key file
:style {:overflow "hidden"}
:on-click (fn []
(handler/load-file file)
(handler/load-file current-repo file)
(handler/toggle-drawer? false))}
(mui/list-item-text file)))))
"Loading...")])
@ -31,7 +31,7 @@
(rum/defc edit < rum/reactive
[]
(let [state (rum/react state/state)
{:keys [current-file contents]} state]
{:keys [current-repo current-file contents]} state]
(mui/container
{:id "root-container"
:style {:display "flex"
@ -62,5 +62,5 @@
(mui/button {:variant "contained"
:color "primary"
:on-click (fn []
(handler/alter-file current-file))}
(handler/alter-file current-repo current-file))}
"Submit")])))

View File

@ -29,7 +29,7 @@
(rum/defc home < rum/reactive
[]
(let [state (rum/react state/state)
{:keys [user tokens repos repo-url cloned? github-username github-token github-repo contents loadings current-file files width drawer? tasks links cloning?]} state
{:keys [user tokens repos repo-url cloned? github-token github-repo contents loadings current-repo current-file files width drawer? tasks cloning?]} state
loading? (get loadings current-file)
width (or width (util/get-width))
mobile? (and width (<= width 600))]
@ -56,7 +56,7 @@
:spacing 3}
(when-not mobile?
(mui/grid {:xs 2}
(file/files-list files)))
(file/files-list current-repo files)))
(if (and (not mobile?)
(not drawer?))
@ -82,5 +82,5 @@
:else
[:div "TBC"]
;; (settings/settings-form github-username github-token github-repo)
;; (settings/settings-form github-token github-repo)
))))

View File

@ -1,58 +0,0 @@
(ns frontend.components.link
(:require [rum.core :as rum]
[frontend.mui :as mui]
[frontend.util :as util]
[frontend.state :as state]
[frontend.handler :as handler]
[clojure.string :as string]))
(rum/defc links < rum/reactive
[]
(let [state (rum/react state/state)
links (reverse (get state :links))]
(mui/container
{:id "root-container"
:style {:display "flex"
:justify-content "center"
;; TODO: fewer spacing for mobile, 24px
:margin-top 64}}
(if (seq links)
(mui/list
(for [[idx link] (util/indexed links)]
(mui/list-item
{:key (str "link-" idx)}
(mui/list-item-text
[:a {:href link
:target "_blank"}
link]))))
[:div "Loading..."]))))
(rum/defcs dialog < (rum/local "" :link)
[state open?]
(let [link (get state :link)]
(mui/dialog
{:open open?
:on-close (fn []
(handler/toggle-link-dialog? false))}
(mui/dialog-title "Add new link")
(mui/dialog-content
(mui/text-field
{:auto-focus true
:auto-complete "off"
:margin "dense"
:id "link"
:label "Link"
:full-width true
:value @link
:on-change (fn [e] (reset! link (util/evalue e)))}))
(mui/dialog-actions
(mui/button {:on-click (fn []
(handler/toggle-link-dialog? false))
:color "primary"}
"Cancel")
(mui/button {:on-click (fn []
(when-not (string/blank? @link)
(handler/add-new-link @link
"New link")))
:color "primary"}
"Submit")))))

View File

@ -8,12 +8,13 @@
(defn repos
[repos]
[:div#repos
[:ul
(for [{:keys [url id]} repos]
[:li {:key id}
[:a {:href url}
(string/replace url "https://github.com/" "")]])]])
(when (seq repos)
[:div#repos
[:ul
(for [{:keys [url id]} (vals repos)]
[:li {:key id}
[:a {:href url}
(string/replace url "https://github.com/" "")]])]]))
(defn add-repo
[repo-url]

View File

@ -7,7 +7,7 @@
[clojure.string :as string]))
(defn settings-form
[github-username github-token github-repo]
[github-token github-repo]
[:form {:style {:min-width 300}}
(mui/grid
{:container true
@ -24,14 +24,14 @@
:color "primary"
:on-click (fn []
(when (and github-token github-repo)
(handler/clone github-username github-token github-repo)))}
(handler/clone github-token github-repo)))}
"Sync"))])
(rum/defc settings < rum/reactive
[]
;; Change username, repo and basic token
;; Change repo and basic token
(let [state (rum/react state/state)
{:keys [github-username github-token github-repo]} state]
{:keys [github-token github-repo]} state]
(mui/container
{:id "root-container"
:style {:display "flex"
@ -40,7 +40,7 @@
[:div
(settings-form github-username github-token github-repo)
(settings-form github-token github-repo)
(mui/divider {:style {:margin "24px 0"}})

View File

@ -1,9 +1,6 @@
(ns frontend.config)
(defonce dir "/gitnotes")
(defonce tasks-org "tasks.org")
(defonce links-org "links.org")
(defonce hidden-file ".hidden")
(defonce dev? ^boolean goog.DEBUG)
(def website

View File

@ -1,7 +1,6 @@
(ns frontend.core
(:require [rum.core :as rum]
[frontend.git :as git]
[frontend.fs :as fs]
[frontend.util :as util]
[frontend.state :as state]
[frontend.handler :as handler]
@ -19,13 +18,6 @@
;; so it is available even in :advanced release builds
(handler/get-me)
(handler/load-from-disk)
(when (:cloned? @state/state)
(handler/initial-db!)
(handler/periodically-pull)
(handler/periodically-push-tasks))
(handler/listen-to-resize)
;; (handler/request-notifications-if-not-asked)

View File

@ -5,11 +5,6 @@
(def conn (d/create-conn))
;; links
[:link/id
:link/label
:link/link]
;; TODO: added_at, started_at, schedule, deadline
(def qualified-map
{:file :heading/file
@ -111,7 +106,6 @@
;; transactions
(defn transact-headings!
[headings]
(prn "headings: " headings)
(let [headings (safe-headings headings)]
(d/transact! conn headings)))

View File

@ -1,19 +1,18 @@
(ns frontend.fs
(:require [frontend.config :refer [dir]]))
(ns frontend.fs)
(defn mkdir
[]
[dir]
(js/pfs.mkdir dir))
(defn readdir
[]
[dir]
(js/pfs.readdir dir))
(defn read-file
[path]
[dir path]
(js/pfs.readFile (str dir "/" path)
(clj->js {:encoding "utf8"})))
(defn write-file
[path content]
[dir path content]
(js/pfs.writeFile (str dir "/" path) content))

View File

@ -2,78 +2,77 @@
(:refer-clojure :exclude [clone])
(:require [promesa.core :as p]
[frontend.util :as util]
[frontend.config :refer [dir]]))
[clojure.string :as string]))
;; only support Github now
(defn auth
[token]
(prn {:token token})
{:onAuth (fn []
(clj->js
{:username token
:password "x-oauth-basic"}))})
{:username token
:password "x-oauth-basic"})
(defn with-auth
[token m]
(prn {:arguments (merge (auth token)
m)})
(clj->js
(merge (auth token)
m)))
(defn get-repo-dir
[repo-url]
(str "/" (last (string/split repo-url #"/"))))
(defn clone
[username token repo]
[repo-url token]
(js/git.clone (with-auth token
{:dir dir
:url repo
{:dir (get-repo-dir repo-url)
:url repo-url
:corsProxy "https://cors.isomorphic-git.org"
:singleBranch true
:depth 1})))
(defn list-files
[]
[repo-url]
(js/git.listFiles (clj->js
{:dir dir
{:dir (get-repo-dir repo-url)
:ref "HEAD"})))
(defn pull
[username token]
[repo-url token]
(js/git.pull (with-auth token
{:dir dir
{:dir (get-repo-dir repo-url)
:ref "master"
:singleBranch true})))
(defn add
[file]
[repo-url file]
(js/git.add (clj->js
{:dir dir
{:dir (get-repo-dir repo-url)
:filepath file})))
(defn commit
[message]
[repo-url message]
(js/git.commit (clj->js
{:dir dir
{:dir (get-repo-dir repo-url)
:author {:name "Orgnote"
:email "orgnote@hello.world"}
:message message})))
(defn push
[token]
[repo-url token]
(js/git.push (with-auth token
{:dir dir
{:dir (get-repo-dir repo-url)
:remote "origin"
:ref "master"
})))
(defn add-commit-push
[file message token push-ok-handler push-error-handler]
[repo-url file message token push-ok-handler push-error-handler]
(util/p-handle
(let [files (if (coll? file) file [file])]
(doseq [file files]
(add file)))
(add repo-url file)))
(fn [_]
(util/p-handle
(commit message)
(commit repo-url message)
(fn [_]
(push token)
(push repo-url token)
(push-ok-handler))
push-error-handler))))

View File

@ -15,9 +15,14 @@
[frontend.api :as api])
(:import [goog.events EventHandler]))
;; We only support Github token now
(defn get-token
[]
(:oauth_token (first (:tokens @state/state))))
(defn load-file
([path]
(util/p-handle (fs/read-file path)
([repo-url path]
(util/p-handle (fs/read-file (git/get-repo-dir repo-url) path)
(fn [content]
(let [state @state/state
state' (-> state
@ -25,8 +30,8 @@
(assoc-in [:loadings path] false)
(assoc :current-file path))]
(reset! state/state state')))))
([path state-handler]
(util/p-handle (fs/read-file path)
([repo-url path state-handler]
(util/p-handle (fs/read-file (git/get-repo-dir repo-url) path)
(fn [content]
(state-handler content)))))
@ -40,73 +45,66 @@
pattern)))) patterns))
(defn load-files
[]
(util/p-handle (git/list-files)
[repo-url]
(util/p-handle (git/list-files repo-url)
(fn [files]
(when (> (count files) 0)
(let [files (js->clj files)]
(if (contains? (set files) config/hidden-file)
(load-file config/hidden-file
(load-file repo-url config/hidden-file
(fn [patterns-content]
(let [patterns (string/split patterns-content #"\n")
files (remove (fn [path] (hidden? path patterns)) files)]
(swap! state/state
assoc :files files))))
(when patterns-content
(let [patterns (string/split patterns-content #"\n")
files (remove (fn [path] (hidden? path patterns)) files)]
(swap! state/state
assoc-in [:repos repo-url :files] files)))))
(swap! state/state
assoc :files files)))))))
assoc-in [:repos repo-url :files] files)))))))
(defn extract-links
[form]
(let [links (atom [])]
(clojure.walk/postwalk
(fn [x]
(when (and (vector? x)
(= "Link" (first x)))
(let [[_ {:keys [url label]}] x
[_ {:keys [protocol link]}] url
link (str protocol ":" link)]
(swap! links conj link)))
x)
form)
@links))
;; (defn extract-links
;; [form]
;; (let [links (atom [])]
;; (clojure.walk/postwalk
;; (fn [x]
;; (when (and (vector? x)
;; (= "Link" (first x)))
;; (let [[_ {:keys [url label]}] x
;; [_ {:keys [protocol link]}] url
;; link (str protocol ":" link)]
;; (swap! links conj link)))
;; x)
;; form)
;; @links))
(defn load-links
([]
(load-links config/links-org))
([path]
(util/p-handle (fs/read-file path)
(fn [content]
(when content
(let [blocks (org/parse-json content)
blocks (-> (.parse js/JSON blocks)
(js->clj :keywordize-keys true))]
(when (seq blocks)
(swap! state/state assoc :links (extract-links blocks)))))))))
(defn load-from-disk
(defn load-cloned?
[]
(let [cloned? (storage/get :cloned?)]
(swap! state/state assoc
:cloned? cloned?
:github-username (storage/get :github-username)
:github-token (storage/get :github-token)
:github-repo (storage/get :github-repo))
(when cloned?
(load-files)
(load-links))))
(storage/get :cloned?))
(defn set-cloned?
[repo-url value]
(let [cloned (or (load-cloned?) {})
new-cloned (assoc cloned repo-url value)]
(storage/set :cloned? new-cloned)
new-cloned))
;; TODO: remove this
(declare load-repo-to-db!)
(defn pull
[repo-url token]
(util/p-handle (git/pull repo-url token)
(fn [result]
;; TODO: diff
(-> (load-files repo-url)
(p/then
(fn []
(load-repo-to-db! repo-url)))))))
(defn periodically-pull
[]
(let [username (storage/get :github-username)
token (storage/get :github-token)
pull (fn []
(util/p-handle (git/pull username token)
(fn [_result]
;; TODO: diff
(load-files)))
(load-links))]
(pull)
(js/setInterval pull
[repo-url]
(when-let [token (get-token)]
(pull repo-url token)
(js/setInterval #(pull repo-url token)
(* 60 1000))))
(defn add-transaction
@ -121,19 +119,20 @@
[transactions]
(let [transactions (reverse transactions)]
(str
"Orgnote auto save tasks.\n\n"
"Gitnotes auto save tasks.\n\n"
(string/join "\n" transactions))))
(defn periodically-push-tasks
[]
(let [github-token (storage/get :github-token)
[repo-url]
(let [token (get-token)
push (fn []
(let [transactions (:tasks-transactions @state/state)]
(when (seq transactions)
(git/add-commit-push
repo-url
config/tasks-org
(transactions->commit-msg transactions)
github-token
token
(fn []
(prn "Commit tasks to Github.")
(clear-transactions!))
@ -143,19 +142,20 @@
(* 5 1000))))
(defn clone
[github-username github-token github-repo]
[token repo]
(util/p-handle
(do
(swap! state/state assoc
:cloning? true)
(git/clone github-username github-token github-repo))
(prn "Debug: cloning")
(swap! state/state assoc-in
[:repos repo :cloning?] true)
(git/clone repo token))
(fn []
(swap! state/state assoc
:cloned? true)
(storage/set :cloned? true)
(swap! state/state assoc
:cloning? false)
(periodically-pull))
(prn "Debug: cloned")
(swap! state/state assoc-in
[:repos repo :cloned?] true)
(set-cloned? repo true)
(swap! state/state assoc-in
[:repos repo :cloning?] false))
(fn [e]
(prn "Clone failed, reason: " e))))
@ -179,28 +179,6 @@
[]
(swap! state/state assoc :current-file nil))
(defn toggle-link-dialog?
[switch]
(swap! state/state assoc :add-link-dialog? switch))
(defn add-new-link
[link message]
(if-let [github-token (storage/get :github-token)]
(util/p-handle (fs/read-file config/links-org)
(fn [content]
(let [content' (str content "\n** " link)]
(util/p-handle
(fs/write-file config/links-org content')
(fn [_]
(git/add-commit-push config/links-org
message
github-token
(fn []
(toggle-link-dialog? false))
(fn []
(.log js/console "Failed to push the new link."))))))))
(.log js/console "Github token does not exists!")))
(defn new-notification
[text]
(js/Notification. "Gitnotes" #js {:body text
@ -257,70 +235,68 @@
(js/setTimeout hide-snackbar 3000))
(defn alter-file
[file]
(when-let [content (get-in @state/state [:contents file])]
(let [content' (get-in @state/state [:editing-files file])]
[repo-url file]
(when-let [content (get-in @state/state [:repos repo-url :contents file])]
(let [content' (get-in @state/state [:repos repo-url :editing-files file])]
(when-not (= (string/trim content)
(string/trim content'))
(let [github-token (:github-token @state/state)
path [:commit-message file]
(let [token (get-token)
path [:repos repo-url :commit-message file]
message (get-in @state/state path (str "Update " file))]
(util/p-handle
(fs/write-file file content')
(fs/write-file (git/get-repo-dir repo-url) file content')
(fn [_]
(git/add-commit-push file
(git/add-commit-push repo-url
file
message
github-token
token
(fn []
(swap! state/state util/dissoc-in path)
(swap! state/state assoc-in [:contents file] content')
(swap! state/state assoc-in [:repos repo-url :contents file] content')
(show-snackbar "File updated!")
(change-page :home))
(fn []
(prn "Failed to update file."))))))))))
(defn clear-storage
[]
(js/window.pfs._idb.wipe)
(storage/set :cloned? false)
(swap! state/state assoc
:cloned? false
:contents nil
:files nil)
(clone (:github-username @state/state)
(:github-token @state/state)
(:github-repo @state/state)))
[repo-url]
(let [token (get-token)]
(js/window.pfs._idb.wipe)
(storage/set :cloned? false)
(swap! state/state assoc
:cloned? false
:contents nil
:files nil)
(clone token repo-url)))
(defn check
[marker pos]
(let [file config/tasks-org
github-token (storage/get :github-token)]
(when-let [content (get-in @state/state [:contents file])]
[repo-url file marker pos]
(let [token (get-token)]
(when-let [content (get-in @state/state [:repos repo-url :contents file])]
(let [content' (str (subs content 0 pos)
(-> (subs content pos)
(string/replace-first marker "DONE")))]
;; TODO: optimize, only update the specific block
;; (build-tasks content' file)
(util/p-handle
(fs/write-file file content')
(fs/write-file (git/get-repo-dir repo-url) file content')
(fn [_]
(swap! state/state assoc-in [:contents file] content')
(swap! state/state assoc-in [:repos repo-url :contents file] content')
(add-transaction (util/format "`%s` marked as DONE." marker))))))))
(defn uncheck
[pos]
(let [file config/tasks-org
github-token (storage/get :github-token)]
(when-let [content (get-in @state/state [:contents file])]
[repo-url file pos]
(let [token (get-token)]
(when-let [content (get-in @state/state [:repos repo-url :contents file])]
(let [content' (str (subs content 0 pos)
(-> (subs content pos)
(string/replace-first "DONE" "TODO")))]
;; TODO: optimize, only update the specific block
;; (build-tasks content' file)
(util/p-handle
(fs/write-file file content')
(fs/write-file (git/get-repo-dir repo-url) file content')
(fn [_]
(swap! state/state assoc-in [:contents file] content')
(swap! state/state assoc-in [:repos repo-url :contents file] content')
(add-transaction "DONE rollbacks to TODO.")))))))
(defn extract-headings
@ -334,21 +310,21 @@
headings)))
(defn load-all-contents!
[ok-handler]
(let [files (:files @state/state)]
[repo-url ok-handler]
(let [files (get-in @state/state [:repos repo-url :files])]
(-> (p/all (for [file files]
(load-file file
(load-file repo-url file
(fn [content]
(swap! state/state
assoc-in [:contents file] content) ))))
assoc-in [:repos repo-url :contents file] content) ))))
(p/then
(fn [_]
(prn "Files are loaded!")
(ok-handler))))))
(defn extract-all-headings
[]
(let [contents (:contents @state/state)]
[repo-url]
(let [contents (get-in @state/state [:repos repo-url :contents])]
(vec
(mapcat
(fn [[file content] contents]
@ -357,31 +333,19 @@
(defonce headings-atom (atom nil))
(defn initial-db!
[]
(db/init)
(load-all-contents!
(fn []
(let [headings (extract-all-headings)]
(reset! headings-atom headings)
(db/transact-headings! headings)))))
(defn get-me
[]
(api/get-me (fn [body]
(let [{:keys [user tokens repos]} body]
(swap! state/state assoc
:user user
:tokens tokens
:repos repos)))
(fn [response]
(prn "Can't get user's information, error response: " response))))
(defn load-repo-to-db!
[repo-url]
(load-all-contents! repo-url
(fn []
(let [headings (extract-all-headings repo-url)]
(reset! headings-atom headings)
(db/transact-headings! headings)))))
(defn get-user-token-repos
[]
(let [user (:user @state/state)
token (:oauth_token (first (:tokens @state/state)))
repos (map :url (:repos @state/state))]
repos (map :url (vals (:repos @state/state)))]
[user token repos]))
(defn add-repo-and-clone
@ -392,15 +356,44 @@
(swap! state/state
update :repos conj repo)
;; clone
(clone (:name user) token url)))
(clone token url)))
(fn [response]
(prn "Can't add repo: " url))))
(defn sync
[]
(let [[user token repos] (get-user-token-repos)]
(let [[_user token repos] (get-user-token-repos)]
(doseq [repo repos]
(prn {:name (:name user)
:token token
:repo repo})
(clone (:name user) token repo))))
(pull token repo))))
(defn periodically-pull-and-push
[repo-url]
;; automatically pull
(periodically-pull repo-url)
;; automatically push
(periodically-push-tasks repo-url))
(defn get-me
[]
(api/get-me
(fn [body]
(let [{:keys [user tokens repos]} body]
(swap! state/state assoc
:user user
:tokens tokens
:repos (util/index-by repos :url))
(db/init)
(let [repos (map :url repos)
cloned (load-cloned?)
token (get-token)]
(when (seq repos)
(doseq [repo-url repos]
(if (get cloned repo-url)
(periodically-pull-and-push repo-url)
(-> (clone token repo-url)
(p/then
(fn []
(periodically-pull-and-push repo-url))))))))))
(fn [response]
(prn "Can't get user's information, error response: " response))))

View File

@ -2,16 +2,15 @@
(:require [frontend.mui :as mui]
[frontend.handler :as handler]
[frontend.state :as state]
[frontend.components.link :as link]
[frontend.components.file :as file]
[frontend.components.repo :as repo]
[rum.core :as rum]
[clojure.string :as string]))
(rum/defc frame < rum/reactive
[content width link-dialog?]
[content width]
(let [state (rum/react state/state)
{:keys [files drawer? snackbar? snackbar-message]} state
{:keys [files drawer? snackbar? snackbar-message current-repo]} state
mobile? (and width (<= width 600))]
(mui/theme-provider
{:theme (mui/custom-theme)}
@ -46,13 +45,7 @@
(mui/button {:color "inherit"
:on-click (fn []
(handler/change-page :settings))}
"Settings")
(mui/icon-button {:color "inherit"
:class "addButton"
:on-click (fn []
(handler/toggle-link-dialog? true))}
(mui/add-icon))))
"Settings")))
(repo/repos (:repos state))
@ -66,9 +59,7 @@
:on-close (fn []
(handler/toggle-drawer? false))}
[:div {:style {:width 240}}
(file/files-list files)]))
(link/dialog link-dialog?)
(file/files-list current-repo files)]))
(mui/snackbar {:open snackbar?
:auto-hide-duration 3000

View File

@ -9,4 +9,4 @@
(let [state (rum/react state/state)
current-page (get state :current-page :home)]
(when-let [view (get routes/routes current-page)]
(layout/frame (view) (:width state) (:add-link-dialog? state)))))
(layout/frame (view) (:width state)))))

View File

@ -1,12 +1,10 @@
(ns frontend.routes
(:require [frontend.components.home :as home]
[frontend.components.link :as link]
[frontend.components.settings :as settings]
[frontend.components.file :as file]
))
(def routes
{:home home/home
:links link/links
:settings settings/settings
:edit-file file/edit})

View File

@ -3,18 +3,23 @@
(def state (atom {:user nil
:tokens []
:repos []
:repos {}
;; nested in repos -> repo->url -> map
;; {
;; :cloning? false
;; :cloned? (storage/get :cloned?)
;; :files []
;; :contents {} ; file name -> string
;; :loadings {} ; file name -> bool
;; }
:repo-url ""
:current-page :home
:cloning? false
:cloned? (storage/get :cloned?)
:files []
:contents {} ; file name -> string
:current-repo nil
:current-file nil
:loadings {} ; file name -> bool
:width nil
:drawer? false
:tasks {}
:links []
:add-link-dialog? false
}))

View File

@ -86,3 +86,9 @@
(not-empty (into {} (remove (comp nil? second)) el))
el))
nm))
(defn index-by
[col k]
(->> (map (fn [entry] [(get entry k) entry])
col)
(into {})))

View File

@ -19,7 +19,6 @@
** File directory example
#+BEGIN_SRC shell
links.org # the app will extract links from this file
tasks.org # the app will extract todos from this file
other_notes.org
other_notes.markdown