core.cljs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. (ns electron.core
  2. (:require [electron.handler :as handler]
  3. [electron.search :as search]
  4. [electron.updater :refer [init-updater]]
  5. [electron.utils :refer [*win mac? linux? logger get-win-from-sender restore-user-fetch-agent]]
  6. [clojure.string :as string]
  7. [promesa.core :as p]
  8. [cljs-bean.core :as bean]
  9. [electron.fs-watcher :as fs-watcher]
  10. ["fs-extra" :as fs]
  11. ["path" :as path]
  12. ["os" :as os]
  13. ["electron" :refer [BrowserWindow app protocol ipcMain dialog] :as electron]
  14. [clojure.core.async :as async]
  15. [electron.state :as state]
  16. [electron.git :as git]
  17. [electron.window :as win]
  18. [electron.exceptions :as exceptions]
  19. ["/electron/utils" :as utils]))
  20. (defonce LSP_SCHEME "logseq")
  21. (defonce LSP_PROTOCOL (str LSP_SCHEME "://"))
  22. (defonce PLUGIN_URL (str LSP_PROTOCOL "logseq.io/"))
  23. (defonce STATIC_URL (str LSP_PROTOCOL "logseq.com/"))
  24. (defonce PLUGINS_ROOT (.join path (.homedir os) ".logseq/plugins"))
  25. (defonce *setup-fn (volatile! nil))
  26. (defonce *teardown-fn (volatile! nil))
  27. (defonce *quit-dirty? (volatile! true))
  28. ;; Handle creating/removing shortcuts on Windows when installing/uninstalling.
  29. (when (js/require "electron-squirrel-startup") (.quit app))
  30. (defn setup-updater! [^js win]
  31. ;; manual/auto updater
  32. (when-not linux?
  33. (init-updater {:repo "logseq/logseq"
  34. :logger logger
  35. :win win})))
  36. (defn setup-interceptor! [win]
  37. (.registerFileProtocol
  38. protocol "assets"
  39. (fn [^js request callback]
  40. (let [url (.-url request)
  41. path (string/replace url "assets://" "")
  42. path (js/decodeURIComponent path)]
  43. (callback #js {:path path}))))
  44. (.registerFileProtocol
  45. protocol LSP_SCHEME
  46. (fn [^js request callback]
  47. (let [url (.-url request)
  48. url' ^js (js/URL. url)
  49. [_ ROOT] (if (string/starts-with? url PLUGIN_URL)
  50. [PLUGIN_URL PLUGINS_ROOT]
  51. [STATIC_URL js/__dirname])
  52. path' (.-pathname url')
  53. path' (js/decodeURIComponent path')
  54. path' (.join path ROOT path')]
  55. (callback #js {:path path'}))))
  56. (.registerHttpProtocol
  57. protocol LSP_SCHEME
  58. (fn [^js request callback]
  59. (prn "Request")
  60. (js/console.dir request)
  61. ;; placeholder
  62. ))
  63. #(do
  64. (.unregisterProtocol protocol LSP_SCHEME)
  65. (.unregisterProtocol protocol "assets")))
  66. (defn- handle-export-publish-assets [_event html custom-css-path repo-path asset-filenames output-path]
  67. (p/let [app-path (. app getAppPath)
  68. asset-filenames (js->clj asset-filenames)
  69. root-dir (or output-path (handler/open-dir-dialog))]
  70. (when root-dir
  71. (let [static-dir (path/join root-dir "static")
  72. assets-from-dir (path/join repo-path "assets")
  73. assets-to-dir (path/join root-dir "assets")
  74. index-html-path (path/join root-dir "index.html")]
  75. (p/let [_ (. fs ensureDir static-dir)
  76. _ (. fs ensureDir assets-to-dir)
  77. _ (p/all (concat
  78. [(. fs writeFile index-html-path html)
  79. (. fs copy (path/join app-path "404.html") (path/join root-dir "404.html"))]
  80. (map
  81. (fn [filename]
  82. (-> (. fs copy (path/join assets-from-dir filename) (path/join assets-to-dir filename))
  83. (p/catch
  84. (fn [e]
  85. (println (str "Failed to copy " (path/join assets-from-dir filename) " to " (path/join assets-to-dir filename)))
  86. (js/console.error e)))))
  87. asset-filenames)
  88. (map
  89. (fn [part]
  90. (. fs copy (path/join app-path part) (path/join static-dir part)))
  91. ["css" "fonts" "icons" "img" "js"])))
  92. custom-css (. fs readFile custom-css-path)
  93. _ (. fs writeFile (path/join static-dir "css" "custom.css") custom-css)
  94. js-files ["main.js" "code-editor.js" "excalidraw.js"]
  95. _ (p/all (map (fn [file]
  96. (. fs removeSync (path/join static-dir "js" file)))
  97. js-files))
  98. _ (p/all (map (fn [file]
  99. (. fs moveSync
  100. (path/join static-dir "js" "publishing" file)
  101. (path/join static-dir "js" file)))
  102. js-files))
  103. _ (. fs removeSync (path/join static-dir "js" "publishing"))
  104. ;; remove source map files
  105. ;; TODO: ugly, replace with ls-files and filter with ".map"
  106. _ (p/all (map (fn [file]
  107. (. fs removeSync (path/join static-dir "js" (str file ".map"))))
  108. ["main.js" "code-editor.js" "excalidraw.js" "age-encryption.js"]))]
  109. (. dialog showMessageBox (clj->js {:message (str "Export public pages and publish assets to " root-dir " successfully")})))))))
  110. (defn setup-app-manager!
  111. [^js win]
  112. (let [toggle-win-channel "toggle-max-or-min-active-win"
  113. call-app-channel "call-application"
  114. call-win-channel "call-main-win"
  115. export-publish-assets "export-publish-assets"
  116. quit-dirty-state "set-quit-dirty-state"
  117. clear-win-effects! (win/setup-window-listeners! win)]
  118. (doto ipcMain
  119. (.handle quit-dirty-state
  120. (fn [_ dirty?]
  121. (vreset! *quit-dirty? (boolean dirty?))))
  122. (.handle toggle-win-channel
  123. (fn [_ toggle-min?]
  124. (when-let [active-win (.getFocusedWindow BrowserWindow)]
  125. (if toggle-min?
  126. (if (.isMinimized active-win)
  127. (.restore active-win)
  128. (.minimize active-win))
  129. (if (.isMaximized active-win)
  130. (.unmaximize active-win)
  131. (.maximize active-win))))))
  132. (.handle export-publish-assets handle-export-publish-assets)
  133. (.handle call-app-channel
  134. (fn [_ type & args]
  135. (try
  136. (js-invoke app type args)
  137. (catch js/Error e
  138. (js/console.error e)))))
  139. (.handle call-win-channel
  140. (fn [^js e type & args]
  141. (let [win (get-win-from-sender e)]
  142. (try
  143. (js-invoke win type args)
  144. (catch js/Error e
  145. (js/console.error e)))))))
  146. #(do (clear-win-effects!)
  147. (.removeHandler ipcMain toggle-win-channel)
  148. (.removeHandler ipcMain export-publish-assets)
  149. (.removeHandler ipcMain quit-dirty-state)
  150. (.removeHandler ipcMain call-app-channel)
  151. (.removeHandler ipcMain call-win-channel))))
  152. (defn main
  153. []
  154. (if-not (.requestSingleInstanceLock app)
  155. (do
  156. (search/close!)
  157. (.quit app))
  158. (do
  159. (.registerSchemesAsPrivileged
  160. protocol (bean/->js [{:scheme LSP_SCHEME
  161. :privileges {:standard true
  162. :secure true
  163. :bypassCSP true
  164. :supportFetchAPI true}}]))
  165. (.on app "second-instance"
  166. (fn [_event _commandLine _workingDirectory]
  167. (when-let [win @*win]
  168. (when (.isMinimized ^object win)
  169. (.restore win))
  170. (.focus win))))
  171. (.on app "window-all-closed" (fn []
  172. (try
  173. (fs-watcher/close-watcher!)
  174. (search/close!)
  175. (catch js/Error e
  176. (js/console.error e)))
  177. (.quit app)))
  178. (.on app "ready"
  179. (fn []
  180. (let [t0 (setup-interceptor!)
  181. ^js win (win/create-main-window)
  182. _ (reset! *win win)]
  183. (.. logger (info (str "Logseq App(" (.getVersion app) ") Starting... ")))
  184. (restore-user-fetch-agent)
  185. (utils/disableXFrameOptions win)
  186. (search/ensure-search-dir!)
  187. (search/open-dbs!)
  188. (git/auto-commit-current-graph!)
  189. (vreset! *setup-fn
  190. (fn []
  191. (let [t1 (setup-updater! win)
  192. t2 (setup-app-manager! win)
  193. t3 (handler/set-ipc-handler! win)
  194. tt (exceptions/setup-exception-listeners!)]
  195. (vreset! *teardown-fn
  196. #(doseq [f [t0 t1 t2 t3 tt]]
  197. (and f (f)))))))
  198. ;; setup effects
  199. (@*setup-fn)
  200. ;; main window events
  201. ;; TODO merge with window/on-close-actions!
  202. ;; TODO elimilate the difference between main and non-main windows
  203. (.on win "close" (fn [e]
  204. (when @*quit-dirty? ;; when not updating
  205. (.preventDefault e)
  206. (let [web-contents (. win -webContents)]
  207. (.send web-contents "persistent-dbs"))
  208. (async/go
  209. (let [_ (async/<! state/persistent-dbs-chan)]
  210. (if (or @win/*quitting? (not mac?))
  211. ;; only cmd+q quitting will trigger actual closing on mac
  212. ;; otherwise, it's just hiding - don't do any actuall closing in that case
  213. ;; except saving transit
  214. (when-let [win @*win]
  215. (when-let [dir (state/get-window-graph-path win)]
  216. (handler/close-watcher-when-orphaned! win dir))
  217. (state/close-window! win)
  218. (win/destroy-window! win)
  219. (reset! *win nil))
  220. ;; Just hiding - don't do any actuall closing operation
  221. (do (.preventDefault ^js/Event e)
  222. (if (and mac? (.isFullScreen win))
  223. (do (.once win "leave-full-screen" #(.hide win))
  224. (.setFullScreen win false))
  225. (.hide win)))))))))
  226. (.on app "before-quit" (fn [_e]
  227. (reset! win/*quitting? true)))
  228. (.on app "activate" #(when @*win (.show win)))))))))
  229. (defn start []
  230. (js/console.log "Main - start")
  231. (when @*setup-fn (@*setup-fn)))
  232. (defn stop []
  233. (js/console.log "Main - stop")
  234. (when @*teardown-fn (@*teardown-fn)))