repo.cljs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. (ns frontend.components.repo
  2. (:require [clojure.string :as string]
  3. [frontend.common.async-util :as async-util]
  4. [frontend.components.rtc.indicator :as rtc-indicator]
  5. [frontend.config :as config]
  6. [frontend.context.i18n :refer [t]]
  7. [frontend.db :as db]
  8. [frontend.handler.db-based.rtc :as rtc-handler]
  9. [frontend.handler.db-based.rtc-flows :as rtc-flows]
  10. [frontend.handler.file-based.native-fs :as nfs-handler]
  11. [frontend.handler.file-sync :as file-sync]
  12. [frontend.handler.graph :as graph]
  13. [frontend.handler.notification :as notification]
  14. [frontend.handler.repo :as repo-handler]
  15. [frontend.handler.route :as route-handler]
  16. [frontend.handler.user :as user-handler]
  17. [frontend.mobile.util :as mobile-util]
  18. [frontend.state :as state]
  19. [frontend.ui :as ui]
  20. [frontend.util :as util]
  21. [frontend.util.fs :as fs-util]
  22. [frontend.util.text :as text-util]
  23. [goog.object :as gobj]
  24. [lambdaisland.glogi :as log]
  25. [logseq.shui.hooks :as hooks]
  26. [logseq.shui.ui :as shui]
  27. [medley.core :as medley]
  28. [promesa.core :as p]
  29. [rum.core :as rum]))
  30. (rum/defc normalized-graph-label
  31. [{:keys [url remote? GraphName GraphUUID] :as graph} on-click]
  32. (let [db-based? (config/db-based-graph? url)]
  33. (when graph
  34. [:span.flex.items-center
  35. (if (or (config/local-file-based-graph? url)
  36. db-based?)
  37. (let [local-dir (config/get-local-dir url)
  38. graph-name (text-util/get-graph-name-from-path url)]
  39. [:a.flex.items-center {:title local-dir
  40. :on-click #(on-click graph)}
  41. [:span graph-name (when (and GraphName (not db-based?)) [:strong.pl-1 "(" GraphName ")"])]
  42. (when remote? [:strong.px-1.flex.items-center (ui/icon "cloud")])])
  43. [:a.flex.items-center {:title GraphUUID
  44. :on-click #(on-click graph)}
  45. (db/get-repo-path (or url GraphName))
  46. (when remote? [:strong.pl-1.flex.items-center (ui/icon "cloud")])])])))
  47. (defn sort-repos-with-metadata-local
  48. [repos]
  49. (if-let [m (and (seq repos) (graph/get-metadata-local))]
  50. (->> repos
  51. (map (fn [r] (merge r (get m (:url r)))))
  52. (sort (fn [r1 r2]
  53. (compare (or (:last-seen-at r2) (:created-at r2))
  54. (or (:last-seen-at r1) (:created-at r1))))))
  55. repos))
  56. (defn- safe-locale-date
  57. [dst]
  58. (when (number? dst)
  59. (try
  60. (.toLocaleString (js/Date. dst))
  61. (catch js/Error _e nil))))
  62. (rum/defc ^:large-vars/cleanup-todo repos-inner
  63. "Graph list in `All graphs` page"
  64. [repos]
  65. (for [{:keys [root url remote? GraphUUID GraphSchemaVersion GraphName created-at last-seen-at] :as repo}
  66. (sort-repos-with-metadata-local repos)
  67. :let [db-based? (config/db-based-graph? url)
  68. graph-name (if db-based? (config/db-graph-name url) GraphName)]]
  69. [:div.flex.justify-between.mb-4.items-center.group {:key (or url GraphUUID)
  70. "data-testid" url}
  71. [:div
  72. [:span.flex.items-center.gap-1
  73. (normalized-graph-label repo
  74. (fn []
  75. (when-not (state/sub :rtc/downloading-graph-uuid)
  76. (cond
  77. root ; exists locally
  78. (state/pub-event! [:graph/switch url])
  79. (and db-based? remote?)
  80. (state/pub-event! [:rtc/download-remote-graph GraphName GraphUUID GraphSchemaVersion])
  81. :else
  82. (when-not (util/capacitor?)
  83. (state/pub-event! [:graph/pull-down-remote-graph repo]))))))]
  84. (when-let [time (some-> (or last-seen-at created-at) (safe-locale-date))]
  85. [:small.text-muted-foreground (str "Last opened at: " time)])]
  86. [:div.controls
  87. [:div.flex.flex-row.items-center
  88. (when (util/electron?)
  89. [:a.text-xs.items-center.text-gray-08.hover:underline.hidden.group-hover:flex
  90. {:on-click #(util/open-url (str "file://" root))}
  91. (shui/tabler-icon "folder-pin") [:span.pl-1 root]])
  92. (let [db-graph? (config/db-based-graph? url)
  93. manager? (and db-graph? (user-handler/manager? url))]
  94. (shui/dropdown-menu
  95. (shui/dropdown-menu-trigger
  96. {:asChild true}
  97. (shui/button
  98. {:variant "ghost"
  99. :class "graph-action-btn !px-1"
  100. :size :sm}
  101. (ui/icon "dots" {:size 15})))
  102. (shui/dropdown-menu-content
  103. {:align "end"}
  104. (when root
  105. (shui/dropdown-menu-item
  106. {:key "delete-locally"
  107. :class "delete-local-graph-menu-item"
  108. :on-click (fn []
  109. (let [prompt-str (if db-based?
  110. (str "Are you sure you want to permanently delete the graph \"" graph-name "\" from Logseq?")
  111. (str "Are you sure you want to unlink the graph \"" url "\" from local folder?"))]
  112. (-> (shui/dialog-confirm!
  113. [:p.font-medium.-my-4 prompt-str
  114. [:span.my-2.flex.font-normal.opacity-75
  115. (if db-based?
  116. [:small "⚠️ Notice that we can't recover this graph after being deleted. Make sure you have backups before deleting it."]
  117. [:small "⚠️ It won't remove your local files!"])]])
  118. (p/then (fn []
  119. (repo-handler/remove-repo! repo)
  120. (state/pub-event! [:graph/unlinked repo (state/get-current-repo)]))))))}
  121. "Delete local graph"))
  122. (when (and db-based? root
  123. (user-handler/logged-in?)
  124. (user-handler/rtc-group?)
  125. (not remote?)
  126. (= url (state/get-current-repo)))
  127. (shui/dropdown-menu-item
  128. {:key "logseq-sync"
  129. :class "use-logseq-sync-menu-item"
  130. :on-click (fn []
  131. (let [repo (state/get-current-repo)
  132. token (state/get-auth-id-token)
  133. remote-graph-name (config/db-graph-name (state/get-current-repo))]
  134. (when (and token remote-graph-name)
  135. (state/<invoke-db-worker :thread-api/rtc-async-upload-graph
  136. repo token remote-graph-name)
  137. (when (util/mobile?)
  138. (shui/popup-show! nil
  139. (fn []
  140. (rtc-indicator/uploading-logs))
  141. {:id :rtc-graph-upload-log}))
  142. (rtc-indicator/on-upload-finished-task
  143. (fn []
  144. (when (util/mobile?) (shui/popup-hide! :rtc-graph-upload-log))
  145. (p/do!
  146. (rtc-flows/trigger-rtc-start repo)
  147. (rtc-handler/<get-remote-graphs)))))))}
  148. "Use Logseq sync (Alpha testing)"))
  149. (when (and remote? (or (and db-based? manager?) (not db-based?)))
  150. (shui/dropdown-menu-item
  151. {:key "delete-remotely"
  152. :class "delete-remote-graph-menu-item"
  153. :on-click (fn []
  154. (let [prompt-str (str "Are you sure you want to permanently delete the graph \"" graph-name "\" from our server?")]
  155. (-> (shui/dialog-confirm!
  156. [:p.font-medium.-my-4 prompt-str
  157. [:span.my-2.flex.font-normal.opacity-75
  158. [:small "⚠️ Notice that we can't recover this graph after being deleted. Make sure you have backups before deleting it."]]])
  159. (p/then
  160. (fn []
  161. (when (or manager? (not db-graph?))
  162. (let [<delete-graph (if db-graph?
  163. rtc-handler/<rtc-delete-graph!
  164. (fn [graph-uuid _graph-schema-version]
  165. (async-util/c->p (file-sync/<delete-graph graph-uuid))))]
  166. (state/set-state! [:file-sync/remote-graphs :loading] true)
  167. (when (= (state/get-current-repo) repo)
  168. (state/<invoke-db-worker :thread-api/rtc-stop))
  169. (p/do! (<delete-graph GraphUUID GraphSchemaVersion)
  170. (state/delete-remote-graph! repo)
  171. (state/set-state! [:file-sync/remote-graphs :loading] false)
  172. (rtc-handler/<get-remote-graphs)))))))))}
  173. "Delete from server")))))]]]))
  174. (rum/defc repos-cp < rum/reactive
  175. {:will-mount (fn [state]
  176. (let [login? (:auth/id-token @state/state)]
  177. (when (and login? (user-handler/rtc-group?))
  178. (rtc-handler/<get-remote-graphs)))
  179. state)}
  180. []
  181. (let [login? (boolean (state/sub :auth/id-token))
  182. repos (state/sub [:me :repos])
  183. repos (util/distinct-by :url repos)
  184. remotes (concat
  185. (state/sub :rtc/graphs)
  186. (state/sub [:file-sync/remote-graphs :graphs]))
  187. remotes-loading? (state/sub [:file-sync/remote-graphs :loading])
  188. repos (if (and login? (seq remotes))
  189. (repo-handler/combine-local-&-remote-graphs repos remotes) repos)
  190. repos (cond->>
  191. (remove #(= (:url %) config/demo-repo) repos)
  192. (util/mobile?)
  193. (filter (fn [item]
  194. (config/db-based-graph? (:url item)))))
  195. {remote-graphs true local-graphs false} (group-by (comp boolean :remote?) repos)]
  196. [:div#graphs
  197. (when-not (util/capacitor?)
  198. [:h1.title (t :graph/all-graphs)])
  199. [:div.pl-1.content.mt-3
  200. [:div
  201. [:h2.text-lg.font-medium.my-4 (t :graph/local-graphs)]
  202. (when (seq local-graphs)
  203. (repos-inner local-graphs))
  204. (when-not (util/capacitor?)
  205. [:div.flex.flex-row.my-4
  206. (if util/web-platform?
  207. [:div.mr-8
  208. (ui/button
  209. "Create a new graph"
  210. :on-click #(state/pub-event! [:graph/new-db-graph]))]
  211. (when (or (nfs-handler/supported?)
  212. (mobile-util/native-platform?))
  213. [:div.mr-8
  214. (ui/button
  215. (t :open-a-directory)
  216. :on-click #(state/pub-event! [:graph/setup-a-repo]))]))])]
  217. (when (and (or (file-sync/enable-sync?)
  218. (user-handler/rtc-group?))
  219. login?)
  220. [:div
  221. [:hr]
  222. [:div.flex.align-items.justify-between
  223. [:h2.text-lg.font-medium.my-4 (t :graph/remote-graphs)]
  224. [:div
  225. (ui/button
  226. [:span.flex.items-center "Refresh"
  227. (when remotes-loading? [:small.pl-2 (ui/loading nil)])]
  228. :background "gray"
  229. :disabled remotes-loading?
  230. :on-click (fn []
  231. (when-not (util/capacitor?)
  232. (file-sync/load-session-graphs))
  233. (rtc-handler/<get-remote-graphs)))]]
  234. (repos-inner remote-graphs)])]]))
  235. (defn- repos-dropdown-links [repos current-repo downloading-graph-id & {:as opts}]
  236. (let [switch-repos (if-not (nil? current-repo)
  237. (remove (fn [repo] (= current-repo (:url repo))) repos) repos) ; exclude current repo
  238. repo-links (mapv
  239. (fn [{:keys [url remote? rtc-graph? GraphName GraphSchemaVersion GraphUUID] :as graph}]
  240. (let [local? (config/local-file-based-graph? url)
  241. db-only? (config/db-based-graph? url)
  242. repo-url (cond
  243. local? (db/get-repo-name url)
  244. db-only? url
  245. :else GraphName)
  246. short-repo-name (if (or local? db-only?)
  247. (text-util/get-graph-name-from-path repo-url)
  248. GraphName)
  249. downloading? (and downloading-graph-id (= GraphUUID downloading-graph-id))]
  250. (when short-repo-name
  251. {:title [:span.flex.items-center.title-wrap short-repo-name
  252. (when remote? [:span.pl-1.flex.items-center
  253. {:title (str "<" GraphName "> #" GraphUUID)}
  254. (ui/icon "cloud" {:size 18})
  255. (when downloading?
  256. [:span.opacity.text-sm.pl-1 "downloading"])])]
  257. :hover-detail repo-url ;; show full path on hover
  258. :options {:on-click
  259. (fn [e]
  260. (when-not downloading?
  261. (when-let [on-click (:on-click opts)]
  262. (on-click e))
  263. (if (and (gobj/get e "shiftKey")
  264. (not (and rtc-graph? remote?)))
  265. (state/pub-event! [:graph/open-new-window url])
  266. (cond
  267. ;; exists locally?
  268. (or (:root graph) (not rtc-graph?))
  269. (state/pub-event! [:graph/switch url])
  270. (and rtc-graph? remote?)
  271. (state/pub-event!
  272. [:rtc/download-remote-graph GraphName GraphUUID GraphSchemaVersion])
  273. :else
  274. (state/pub-event! [:graph/pull-down-remote-graph graph])))))}})))
  275. switch-repos)]
  276. (->> repo-links (remove nil?))))
  277. (defn- repos-footer [multiple-windows? db-based?]
  278. [:div.cp__repos-quick-actions
  279. {:on-click #(shui/popup-hide!)}
  280. (when (and (not db-based?)
  281. (not (config/demo-graph?)))
  282. [:<>
  283. (shui/button {:size :sm :variant :ghost
  284. :title (t :sync-from-local-files-detail)
  285. :on-click (fn []
  286. (state/pub-event! [:graph/ask-for-re-fresh]))}
  287. (shui/tabler-icon "file-report") [:span (t :sync-from-local-files)])
  288. (shui/button {:size :sm :variant :ghost
  289. :title (t :re-index-detail)
  290. :on-click (fn []
  291. (state/pub-event! [:graph/ask-for-re-index multiple-windows? nil]))}
  292. (shui/tabler-icon "folder-bolt") [:span (t :re-index)])])
  293. (when (util/electron?)
  294. (shui/button {:size :sm :variant :ghost
  295. :on-click (fn []
  296. (if (or (nfs-handler/supported?) (mobile-util/native-platform?))
  297. (state/pub-event! [:graph/setup-a-repo])
  298. (route-handler/redirect-to-all-graphs)))}
  299. (shui/tabler-icon "folder-plus")
  300. [:span (t :new-graph)]))
  301. (when-not config/publishing?
  302. (shui/button
  303. {:size :sm :variant :ghost
  304. :on-click #(state/pub-event! [:graph/new-db-graph])}
  305. (shui/tabler-icon "database-plus")
  306. [:span (if util/electron? "Create db graph" "Create new graph")]))
  307. (when-not config/publishing?
  308. (shui/button
  309. {:size :sm :variant :ghost
  310. :on-click (fn [] (route-handler/redirect! {:to :import}))}
  311. (shui/tabler-icon "database-import")
  312. [:span (t :import-notes)]))
  313. (when-not config/publishing?
  314. (shui/button {:size :sm :variant :ghost
  315. :on-click #(route-handler/redirect-to-all-graphs)}
  316. (shui/tabler-icon "layout-2") [:span (t :all-graphs)]))])
  317. (rum/defcs repos-dropdown-content < rum/reactive
  318. [_state & {:keys [contentid] :as opts}]
  319. (let [multiple-windows? false
  320. current-repo (state/sub :git/current-repo)
  321. login? (boolean (state/sub :auth/id-token))
  322. repos (state/sub [:me :repos])
  323. remotes (state/sub [:file-sync/remote-graphs :graphs])
  324. rtc-graphs (state/sub :rtc/graphs)
  325. downloading-graph-id (state/sub :rtc/downloading-graph-uuid)
  326. remotes-loading? (state/sub [:file-sync/remote-graphs :loading])
  327. db-based? (config/db-based-graph? current-repo)
  328. repos (sort-repos-with-metadata-local repos)
  329. repos (distinct
  330. (if (and (or (seq remotes) (seq rtc-graphs)) login?)
  331. (repo-handler/combine-local-&-remote-graphs repos (concat remotes rtc-graphs)) repos))
  332. items-fn #(repos-dropdown-links repos current-repo downloading-graph-id opts)
  333. header-fn #(when (> (count repos) 1) ; show switch to if there are multiple repos
  334. [:div.font-medium.md:text-sm.md:opacity-50.px-1.py-1.flex.flex-row.justify-between.items-center
  335. [:h4.pb-1 (t :left-side-bar/switch)]
  336. (when (and (file-sync/enable-sync?) login?)
  337. (if remotes-loading?
  338. (ui/loading "")
  339. (shui/button
  340. {:variant :ghost
  341. :size :sm
  342. :title "Refresh remote graphs"
  343. :class "!h-6 !px-1 relative right-[-4px]"
  344. :on-click (fn []
  345. (file-sync/load-session-graphs)
  346. (rtc-handler/<get-remote-graphs))}
  347. (ui/icon "refresh" {:size 15}))))])
  348. _remote? (and current-repo (:remote? (first (filter #(= current-repo (:url %)) repos))))
  349. _repo-name (when current-repo (db/get-repo-name current-repo))]
  350. [:div
  351. {:class (when (<= (count repos) 1) "no-repos")}
  352. (header-fn)
  353. [:div.cp__repos-list-wrap
  354. (for [{:keys [hr item hover-detail title options icon]} (items-fn)]
  355. (let [on-click' (:on-click options)
  356. href' (:href options)
  357. menu-item (if (util/mobile?) ui/menu-link shui/dropdown-menu-item)]
  358. (if hr
  359. (if (util/mobile?) [:hr.py-2] (shui/dropdown-menu-separator))
  360. (menu-item
  361. (assoc options
  362. :title hover-detail
  363. :on-click (fn [^js e]
  364. (when on-click'
  365. (when-not (false? (on-click' e))
  366. (shui/popup-hide! contentid)))))
  367. (or item
  368. (if href'
  369. [:a.flex.items-center.w-full
  370. {:href href' :on-click #(shui/popup-hide! contentid)
  371. :style {:color "inherit"}} title]
  372. [:span.flex.items-center.gap-1.w-full
  373. icon [:div title]]))))))]
  374. (repos-footer multiple-windows? db-based?)]))
  375. (rum/defcs graphs-selector < rum/reactive
  376. [_state]
  377. (let [current-repo (state/get-current-repo)
  378. user-repos (state/get-repos)
  379. current-repo' (some->> user-repos (medley/find-first #(= current-repo (:url %))))
  380. repo-name (when current-repo (db/get-repo-name current-repo))
  381. db-based? (config/db-based-graph? current-repo)
  382. remote? (:remote? current-repo')
  383. short-repo-name (if current-repo
  384. (db/get-short-repo-name repo-name)
  385. "Select a Graph")]
  386. [:div.cp__graphs-selector.flex.items-center.justify-between
  387. [:a.item.flex.items-center.gap-1.select-none
  388. {:title current-repo
  389. :on-click (fn [^js e]
  390. (shui/popup-show! (.closest (.-target e) "a")
  391. (fn [{:keys [id]}] (repos-dropdown-content {:contentid id}))
  392. {:as-dropdown? true
  393. :content-props {:class "repos-list"}
  394. :align :start}))}
  395. [:span.thumb (shui/tabler-icon (if remote? "cloud" (if db-based? "topology-star" "folder")) {:size 16})]
  396. [:strong short-repo-name]
  397. (shui/tabler-icon "selector" {:size 18})]]))
  398. (defn invalid-graph-name-warning
  399. []
  400. (notification/show!
  401. [:div
  402. [:p "Graph name can't contain following reserved characters:"]
  403. [:ul
  404. [:li "< (less than)"]
  405. [:li "> (greater than)"]
  406. [:li ": (colon)"]
  407. [:li "\" (double quote)"]
  408. [:li "/ (forward slash)"]
  409. [:li "\\ (backslash)"]
  410. [:li "| (vertical bar or pipe)"]
  411. [:li "? (question mark)"]
  412. [:li "* (asterisk)"]
  413. [:li "# (hash)"]
  414. ;; `+` is used to encode path that includes `:` or `/`
  415. [:li "+ (plus)"]]]
  416. :warning false))
  417. (defn invalid-graph-name?
  418. "Returns boolean indicating if DB graph name is invalid. Must be kept in sync with invalid-graph-name-warning"
  419. [graph-name]
  420. (or (fs-util/include-reserved-chars? graph-name)
  421. (string/includes? graph-name "+")
  422. (string/includes? graph-name "/")))
  423. (rum/defc new-db-graph
  424. []
  425. (let [[creating-db? set-creating-db?] (hooks/use-state false)
  426. [cloud? set-cloud?] (hooks/use-state false)
  427. [e2ee-rsa-key-ensured? set-e2ee-rsa-key-ensured?] (hooks/use-state nil)
  428. input-ref (hooks/create-ref)]
  429. (hooks/use-effect!
  430. (fn []
  431. (when-let [^js input (hooks/deref input-ref)]
  432. (js/setTimeout #(.focus input) 32)))
  433. [])
  434. (letfn [(new-db-f [graph-name]
  435. (when-not (or (string/blank? graph-name)
  436. creating-db?)
  437. (if (invalid-graph-name? graph-name)
  438. (invalid-graph-name-warning)
  439. (do
  440. (set-creating-db? true)
  441. (p/let [repo (repo-handler/new-db! graph-name)]
  442. (when cloud?
  443. (->
  444. (p/do
  445. (state/set-state! :rtc/uploading? true)
  446. (rtc-handler/<rtc-create-graph! repo)
  447. (rtc-flows/trigger-rtc-start repo)
  448. (rtc-handler/<get-remote-graphs))
  449. (p/catch (fn [error]
  450. (log/error :create-db-failed error)))
  451. (p/finally (fn []
  452. (state/set-state! :rtc/uploading? false)
  453. (set-creating-db? false)))))
  454. (shui/dialog-close!))))))
  455. (submit! [^js e click?]
  456. (when-let [value (and (or click? (= (gobj/get e "key") "Enter"))
  457. (util/trim-safe (.-value (rum/deref input-ref))))]
  458. (new-db-f value)))]
  459. [:div.new-graph.flex.flex-col.gap-4.p-1.pt-2
  460. (shui/input
  461. {:disabled creating-db?
  462. :ref input-ref
  463. :placeholder "your graph name"
  464. :on-key-down submit!
  465. :autoComplete "off"})
  466. (when (user-handler/rtc-group?)
  467. [:div.flex.flex-col
  468. [:div.flex.flex-row.items-center.gap-1
  469. (shui/checkbox
  470. {:id "rtc-sync"
  471. :value cloud?
  472. :on-checked-change
  473. (fn []
  474. (let [v (boolean (not cloud?))
  475. token (state/get-auth-id-token)
  476. user-uuid (user-handler/user-uuid)]
  477. (set-cloud? v)
  478. (when (and (true? v) (not e2ee-rsa-key-ensured?))
  479. (when (and token user-uuid)
  480. (-> (p/let [rsa-key-pair (state/<invoke-db-worker :thread-api/get-user-rsa-key-pair token user-uuid)]
  481. (set-e2ee-rsa-key-ensured? (some? rsa-key-pair)))
  482. (p/catch (fn [e]
  483. (log/error :get-user-rsa-key-pair e)
  484. e)))))))})
  485. [:label.opacity-70.text-sm
  486. {:for "rtc-sync"}
  487. "Use Logseq Sync?"]]
  488. (when (false? e2ee-rsa-key-ensured?)
  489. [:label.opacity-70.text-sm
  490. {:for "rtc-sync"}
  491. "Need to init E2EE settings first, Settings > Encryption"])])
  492. (shui/button
  493. {:disabled (and cloud? (not e2ee-rsa-key-ensured?))
  494. :on-click #(submit! % true)
  495. :on-key-down submit!}
  496. (if creating-db?
  497. (ui/loading "Creating graph")
  498. "Submit"))])))