settings.cljs 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328
  1. (ns frontend.components.settings
  2. (:require [clojure.string :as string]
  3. [electron.ipc :as ipc]
  4. [frontend.colors :as colors]
  5. [frontend.components.assets :as assets]
  6. [frontend.components.file-sync :as fs]
  7. [frontend.components.shortcut :as shortcut]
  8. [frontend.components.svg :as svg]
  9. [frontend.config :as config]
  10. [frontend.context.i18n :refer [t]]
  11. [frontend.date :as date]
  12. [frontend.db :as db]
  13. [frontend.dicts :as dicts]
  14. [frontend.handler.config :as config-handler]
  15. [frontend.handler.db-based.rtc :as rtc-handler]
  16. [frontend.handler.file-sync :as file-sync-handler]
  17. [frontend.handler.global-config :as global-config-handler]
  18. [frontend.handler.notification :as notification]
  19. [frontend.handler.plugin :as plugin-handler]
  20. [frontend.handler.property :as property-handler]
  21. [frontend.handler.route :as route-handler]
  22. [frontend.handler.ui :as ui-handler]
  23. [frontend.handler.user :as user-handler]
  24. [frontend.mobile.util :as mobile-util]
  25. [frontend.modules.instrumentation.core :as instrument]
  26. [frontend.modules.shortcut.data-helper :as shortcut-helper]
  27. [frontend.spec.storage :as storage-spec]
  28. [frontend.state :as state]
  29. [frontend.storage :as storage]
  30. [frontend.ui :as ui]
  31. [frontend.util :refer [classnames web-platform?] :as util]
  32. [frontend.version :as fv]
  33. [goog.object :as gobj]
  34. [goog.string :as gstring]
  35. [logseq.db :as ldb]
  36. [logseq.shui.ui :as shui]
  37. [promesa.core :as p]
  38. [reitit.frontend.easy :as rfe]
  39. [rum.core :as rum]))
  40. (defn toggle
  41. [label-for name state on-toggle & [detail-text]]
  42. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  43. [:label.block.text-sm.font-medium.leading-5.opacity-70
  44. {:for label-for}
  45. name]
  46. [:div.rounded-md.sm:max-w-tss.sm:col-span-2
  47. [:div.rounded-md {:style {:display "flex" :gap "1rem" :align-items "center"}}
  48. (ui/toggle state on-toggle true)
  49. detail-text]]])
  50. (rum/defcs app-updater < rum/reactive
  51. [state version]
  52. (let [update-pending? (state/sub :electron/updater-pending?)
  53. {:keys [type payload]} (state/sub :electron/updater)]
  54. [:span.cp__settings-app-updater
  55. [:div.ctls.flex.items-center
  56. [:div.mt-1.sm:mt-0.sm:col-span-2.flex.gap-4.items-center.flex-wrap
  57. [:div (cond
  58. (mobile-util/native-android?)
  59. (ui/button
  60. (t :settings-page/check-for-updates)
  61. :class "text-sm mr-1"
  62. :href "https://github.com/logseq/logseq/releases")
  63. (mobile-util/native-ios?)
  64. (ui/button
  65. (t :settings-page/check-for-updates)
  66. :class "text-sm mr-1"
  67. :href "https://apps.apple.com/app/logseq/id1601013908")
  68. (util/electron?)
  69. (ui/button
  70. (if update-pending? (t :settings-page/checking) (t :settings-page/check-for-updates))
  71. :class "text-sm mr-1"
  72. :disabled update-pending?
  73. :on-click #(js/window.apis.checkForUpdates false))
  74. :else
  75. nil)]
  76. [:div.text-sm.cursor
  77. {:title (str (t :settings-page/revision) config/revision)
  78. :on-click (fn []
  79. (notification/show! [:div "Current Revision: "
  80. [:a {:target "_blank"
  81. :href (str "https://github.com/logseq/logseq/commit/" config/revision)}
  82. config/revision]]
  83. :info
  84. false))}
  85. version]
  86. [:a.text-sm.fade-link.underline.inline
  87. {:target "_blank"
  88. :href "https://docs.logseq.com/#/page/changelog"}
  89. (t :settings-page/changelog)]]]
  90. (when-not (or update-pending?
  91. (string/blank? type))
  92. [:div.update-state.text-sm
  93. (case type
  94. "update-not-available"
  95. [:p (t :settings-page/app-updated)]
  96. "update-available"
  97. (let [{:keys [name url]} payload]
  98. [:p (str (t :settings-page/update-available))
  99. [:a.link
  100. {:on-click
  101. (fn [e]
  102. (js/window.apis.openExternal url)
  103. (util/stop e))}
  104. svg/external-link name " 🎉"]])
  105. "error"
  106. [:p (t :settings-page/update-error-1) [:br] (t :settings-page/update-error-2)
  107. [:a.link
  108. {:on-click
  109. (fn [e]
  110. (js/window.apis.openExternal "https://github.com/logseq/logseq/releases")
  111. (util/stop e))}
  112. svg/external-link " release channel"]])])]))
  113. (rum/defc outdenting-hint
  114. []
  115. [:div.ui__modal-panel
  116. {:style {:box-shadow "0 4px 20px 4px rgba(0, 20, 60, .1), 0 4px 80px -8px rgba(0, 20, 60, .2)"}}
  117. [:div {:style {:margin "12px" :max-width "500px"}}
  118. [:p.text-sm
  119. (t :settings-page/preferred-outdenting-tip)
  120. [:a.text-sm
  121. {:target "_blank" :href "https://discuss.logseq.com/t/whats-your-preferred-outdent-behavior-the-direct-one-or-the-logical-one/978"}
  122. (t :settings-page/preferred-outdenting-tip-more)]]
  123. [:img {:src "https://discuss.logseq.com/uploads/default/original/1X/e8ea82f63a5e01f6d21b5da827927f538f3277b9.gif"
  124. :width 500
  125. :height 500}]]])
  126. (rum/defc auto-expand-hint
  127. []
  128. [:div.ui__modal-panel
  129. {:style {:box-shadow "0 4px 20px 4px rgba(0, 20, 60, .1), 0 4px 80px -8px rgba(0, 20, 60, .2)"}}
  130. [:div {:style {:margin "12px" :max-width "500px"}}
  131. [:p.text-sm
  132. (t :settings-page/auto-expand-block-refs-tip)]
  133. [:img {:src "https://user-images.githubusercontent.com/28241963/225818326-118deda9-9d1e-477d-b0ce-771ca0bcd976.gif"
  134. :width 500
  135. :height 500}]]])
  136. (defn row-with-button-action
  137. [{:keys [left-label description action button-label href on-click desc -for stretch center?]
  138. :or {center? true}}]
  139. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4
  140. {:class (if center? "sm:items-center" "sm:items-start")}
  141. ;; left column
  142. [:div.flex.flex-col
  143. [:label.block.text-sm.font-medium.leading-5.opacity-70.pt-2
  144. {:for -for}
  145. left-label]
  146. (when description
  147. [:div.text-xs.text-gray-10 description])]
  148. ;; right column
  149. [:div.mt-1.sm:mt-0.sm:col-span-2.flex.items-center
  150. {:style {:display "flex" :gap "0.5rem" :align-items "center"}}
  151. [:div {:style (when stretch {:width "100%"})}
  152. (if action action (shui/button
  153. {:as-child (not (string/blank? href))
  154. :size :sm
  155. :on-click on-click}
  156. (if (string/blank? href) button-label
  157. (shui/link {:href href} button-label))))]
  158. (when-not (or (util/mobile?)
  159. (mobile-util/native-platform?))
  160. [:div.text-sm.flex desc])]])
  161. (defn edit-config-edn []
  162. (row-with-button-action
  163. {:left-label (t :settings-page/custom-configuration)
  164. :button-label (t :settings-page/edit-config-edn)
  165. :href (rfe/href :file {:path (config/get-repo-config-path)})
  166. :on-click ui-handler/toggle-settings-modal!
  167. :-for "config_edn"}))
  168. (defn edit-global-config-edn []
  169. (row-with-button-action
  170. {:left-label (t :settings-page/custom-global-configuration)
  171. :button-label (t :settings-page/edit-global-config-edn)
  172. :href (rfe/href :file {:path (global-config-handler/global-config-path)})
  173. :on-click ui-handler/toggle-settings-modal!
  174. :-for "global_config_edn"}))
  175. (defn edit-custom-css []
  176. (row-with-button-action
  177. {:left-label (t :settings-page/custom-theme)
  178. :button-label (t :settings-page/edit-custom-css)
  179. :href (rfe/href :file {:path (config/get-custom-css-path)})
  180. :on-click ui-handler/toggle-settings-modal!
  181. :-for "customize_css"}))
  182. (defn edit-export-css []
  183. (row-with-button-action
  184. {:left-label (t :settings-page/export-theme)
  185. :button-label (t :settings-page/edit-export-css)
  186. :href (rfe/href :file {:path (config/get-export-css-path)})
  187. :on-click ui-handler/toggle-settings-modal!
  188. :-for "export_css"}))
  189. (defn show-brackets-row [t show-brackets?]
  190. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  191. [:label.block.text-sm.font-medium.leading-5.opacity-70
  192. {:for "show_brackets"}
  193. (t :settings-page/show-brackets)]
  194. [:div
  195. [:div.rounded-md.sm:max-w-xs
  196. (ui/toggle show-brackets?
  197. config-handler/toggle-ui-show-brackets!
  198. true)]]
  199. (when (not (or (util/mobile?) (mobile-util/native-platform?)))
  200. [:div {:style {:text-align "right"}}
  201. (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-brackets))])])
  202. (defn toggle-wide-mode-row [t wide-mode?]
  203. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  204. [:label.block.text-sm.font-medium.leading-5.opacity-70
  205. {:for "wide_mode"}
  206. (t :settings-page/wide-mode)]
  207. [:div
  208. [:div.rounded-md.sm:max-w-xs
  209. (ui/toggle wide-mode?
  210. ui-handler/toggle-wide-mode!
  211. true)]]
  212. (when (not (or (util/mobile?) (mobile-util/native-platform?)))
  213. [:div {:style {:text-align "right"}}
  214. (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-wide-mode))])])
  215. (defn editor-font-family-row [t font]
  216. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  217. [:label.block.text-sm.font-medium.leading-5.opacity-70
  218. {:for "font_family"}
  219. (t :settings-page/editor-font)]
  220. [:div.rounded-md.sm:max-w-xs.flex.gap-2
  221. (for [t [:default :serif :mono]
  222. :let [t (name t)
  223. tt (string/capitalize t)
  224. active? (= font t)]]
  225. (shui/button
  226. {:variant :secondary
  227. :class (when active? "border-primary border-[2px]")
  228. :on-click #(state/set-editor-font! t)}
  229. [:span.flex.flex-col
  230. {:class (str "ls-font-" t)}
  231. [:strong "Ag"]
  232. [:small tt]]))]])
  233. (rum/defcs switch-spell-check-row < rum/reactive
  234. [state t]
  235. (let [enabled? (state/sub [:electron/user-cfgs :spell-check])]
  236. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  237. [:label.block.text-sm.font-medium.leading-5.opacity-70
  238. (t :settings-page/spell-checker)]
  239. [:div
  240. [:div.rounded-md.sm:max-w-xs
  241. (ui/toggle
  242. enabled?
  243. (fn []
  244. (state/set-state! [:electron/user-cfgs :spell-check] (not enabled?))
  245. (p/then (ipc/ipc :userAppCfgs :spell-check (not enabled?))
  246. #(when (js/confirm (t :relaunch-confirm-to-work))
  247. (js/logseq.api.relaunch))))
  248. true)]]]))
  249. (rum/defcs switch-git-auto-commit-row < rum/reactive
  250. [state t]
  251. (let [enabled? (state/get-git-auto-commit-enabled?)]
  252. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  253. [:label.block.text-sm.font-medium.leading-5.opacity-70
  254. (t :settings-page/git-switcher-label)]
  255. [:div
  256. [:div.rounded-md.sm:max-w-xs
  257. (ui/toggle
  258. enabled?
  259. (fn []
  260. (state/set-state! [:electron/user-cfgs :git/disable-auto-commit?] enabled?)
  261. (p/do!
  262. (ipc/ipc :userAppCfgs :git/disable-auto-commit? enabled?)
  263. (ipc/ipc :setGitAutoCommit)))
  264. true)]]]))
  265. (rum/defcs switch-git-commit-on-close-row < rum/reactive
  266. [state t]
  267. (let [enabled? (state/get-git-commit-on-close-enabled?)]
  268. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  269. [:label.block.text-sm.font-medium.leading-5.opacity-70
  270. (t :settings-page/git-commit-on-close)]
  271. [:div
  272. [:div.rounded-md.sm:max-w-xs
  273. (ui/toggle
  274. enabled?
  275. (fn []
  276. (state/set-state! [:electron/user-cfgs :git/commit-on-close?] (not enabled?))
  277. (ipc/ipc :userAppCfgs :git/commit-on-close? (not enabled?)))
  278. true)]]]))
  279. (rum/defcs git-auto-commit-seconds < rum/reactive
  280. [state t]
  281. (let [secs (or (state/sub [:electron/user-cfgs :git/auto-commit-seconds]) 60)]
  282. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  283. [:label.block.text-sm.font-medium.leading-5.opacity-70
  284. (t :settings-page/git-commit-delay)]
  285. [:div.mt-1.sm:mt-0.sm:col-span-2
  286. [:div.max-w-lg.rounded-md.sm:max-w-xs
  287. [:input#home-default-page.form-input.is-small.transition.duration-150.ease-in-out
  288. {:default-value secs
  289. :on-blur (fn [event]
  290. (let [value (-> (util/evalue event)
  291. util/safe-parse-int)]
  292. (if (and (number? value)
  293. (< 0 value (inc 86400)))
  294. (p/do!
  295. (state/set-state! [:electron/user-cfgs :git/auto-commit-seconds] value)
  296. (ipc/ipc :userAppCfgs :git/auto-commit-seconds value)
  297. (ipc/ipc :setGitAutoCommit))
  298. (when-let [elem (gobj/get event "target")]
  299. (notification/show!
  300. [:div "Invalid value! Must be a number between 1 and 86400"]
  301. :warning true)
  302. (gobj/set elem "value" secs)))))}]]]]))
  303. (rum/defc app-auto-update-row < rum/reactive [t]
  304. (let [enabled? (state/sub [:electron/user-cfgs :auto-update])
  305. enabled? (if (nil? enabled?) true enabled?)]
  306. (toggle "usage-diagnostics"
  307. (t :settings-page/auto-updater)
  308. enabled?
  309. #((state/set-state! [:electron/user-cfgs :auto-update] (not enabled?))
  310. (ipc/ipc :userAppCfgs :auto-update (not enabled?))))))
  311. (defn language-row [t preferred-language]
  312. (let [on-change (fn [e]
  313. (let [lang-code (util/evalue e)]
  314. (state/set-preferred-language! lang-code)
  315. (ui-handler/re-render-root!)))
  316. action [:select.form-select.is-small {:value preferred-language
  317. :on-change on-change}
  318. (for [language dicts/languages]
  319. (let [lang-code (name (:value language))
  320. lang-label (:label language)]
  321. [:option {:key lang-code :value lang-code} lang-label]))]]
  322. (row-with-button-action {:left-label (t :language)
  323. :-for "preferred_language"
  324. :action action})))
  325. (rum/defc theme-modes-row < rum/reactive
  326. [t]
  327. (let [theme (state/sub :ui/theme)
  328. dark? (= "dark" theme)
  329. system-theme? (state/sub :ui/system-theme?)
  330. switch-theme (if dark? "light" "dark")
  331. color-accent (state/sub :ui/radix-color)
  332. pick-theme [:ul.cp__theme-modes-options
  333. [:li {:on-click (partial state/use-theme-mode! "light")
  334. :class (classnames [{:active (and (not system-theme?) (not dark?))}])} [:i.mode-light {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-light)]]
  335. [:li {:on-click (partial state/use-theme-mode! "dark")
  336. :class (classnames [{:active (and (not system-theme?) dark?)}])} [:i.mode-dark {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-dark)]]
  337. [:li {:on-click (partial state/use-theme-mode! "system")
  338. :class (classnames [{:active system-theme?}])} [:i.mode-system {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-system)]]]]
  339. (row-with-button-action {:left-label (t :right-side-bar/switch-theme (string/capitalize switch-theme))
  340. :-for "toggle_theme"
  341. :action pick-theme
  342. :desc (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-theme))})))
  343. (rum/defc accent-color-row < rum/reactive
  344. [_in-modal?]
  345. (let [color-accent (state/sub :ui/radix-color)
  346. pick-theme [:div.cp__accent-colors-list-wrap
  347. {:class (if _in-modal? "as-modal-picker" "")}
  348. (for [color (concat [:none :logseq] colors/color-list)
  349. :let [active? (= color color-accent)
  350. none? (= color :none)]]
  351. [:div.flex.items-center {:style {:height 28}}
  352. (ui/tippy
  353. {:html (case color
  354. :none [:p {:style {:max-width "300px"}}
  355. "Cancel accent color. This is currently in beta stage and mainly used for compatibility with custom themes."]
  356. :logseq "Logseq classical color"
  357. (str (name color) " color"))
  358. :delay [1000, 100]}
  359. (shui/button
  360. {:class "w-5 h-5 px-1 rounded-full flex justify-center items-center transition ease-in duration-100 hover:cursor-pointer hover:opacity-100"
  361. :auto-focus (and _in-modal? active?)
  362. :style {:background-color (colors/variable color :09)
  363. :outline-color (colors/variable color (if active? :07 :06))
  364. :outline-width (if active? "4px" "1px")
  365. :outline-style :solid
  366. :opacity (if active? 1 0.5)}
  367. :variant :text
  368. :on-click (fn [_e] (state/set-color-accent! color))}
  369. [:strong
  370. {:class (if none? "h-0.5 w-full bg-red-700"
  371. "w-2 h-2 rounded-full transition ease-in duration-100")
  372. :style {:background-color (if-not none? (str "var(--rx-" (name color) "-07)") "")
  373. :opacity (if (or none? active?) 1 0)}}]))
  374. ])]]
  375. [:<>
  376. (row-with-button-action
  377. {:left-label (t :settings-page/accent-color)
  378. :description (t :settings-page/accent-color-alert)
  379. :-for "toggle_radix_theme"
  380. :desc (when-not _in-modal?
  381. [:span.pl-6 (ui/render-keyboard-shortcut
  382. (shortcut-helper/gen-shortcut-seq :ui/customize-appearance))])
  383. :stretch (boolean _in-modal?)
  384. :action pick-theme})]))
  385. (rum/defc modal-appearance-inner < rum/reactive
  386. []
  387. [:div.cp__settings-appearance-modal-inner
  388. [:h1.text-2xl.font-bold.pb-2.pt-1 (t :appearance)]
  389. (theme-modes-row t)
  390. (editor-font-family-row t (state/sub :ui/editor-font))
  391. (toggle-wide-mode-row t (state/sub :ui/wide-mode?))
  392. (show-brackets-row t (state/show-brackets?))
  393. (accent-color-row true)])
  394. (defn date-format-row [t preferred-date-format]
  395. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  396. [:label.block.text-sm.font-medium.leading-5.opacity-70
  397. {:for "custom_date_format"}
  398. (t :settings-page/custom-date-format)
  399. (when-not (config/db-based-graph? (state/get-current-repo))
  400. (ui/tippy {:html (t :settings-page/custom-date-format-warning)
  401. :class "tippy-hover ml-2"
  402. :interactive true
  403. :disabled false}
  404. (svg/info)))]
  405. [:div.mt-1.sm:mt-0.sm:col-span-2
  406. [:div.max-w-lg.rounded-md
  407. [:select.form-select.is-small
  408. {:value preferred-date-format
  409. :on-change (fn [e]
  410. (let [repo (state/get-current-repo)
  411. format (util/evalue e)
  412. db-based? (config/db-based-graph? repo)]
  413. (when-not (string/blank? format)
  414. (if db-based?
  415. (p/do!
  416. (property-handler/set-block-property! repo
  417. :logseq.class/Journal
  418. :logseq.property.journal/title-format
  419. format)
  420. (notification/show! "Please refresh the app for this change to take effect"))
  421. (do
  422. (config-handler/set-config! :journal/page-title-format format)
  423. (notification/show!
  424. [:div (t :settings-page/custom-date-format-notification)]
  425. :warning false)))
  426. (state/close-modal!)
  427. (shui/dialog-close-all!)
  428. (when-not db-based? (route-handler/redirect! {:to :graphs})))))}
  429. (for [format (sort (date/journal-title-formatters))]
  430. [:option {:key format} format])]]]])
  431. (defn workflow-row [t preferred-workflow]
  432. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  433. [:label.block.text-sm.font-medium.leading-5.opacity-70
  434. {:for "preferred_workflow"}
  435. (t :settings-page/preferred-workflow)]
  436. [:div.mt-1.sm:mt-0.sm:col-span-2
  437. [:div.max-w-lg.rounded-md
  438. [:select.form-select.is-small
  439. {:value (name preferred-workflow)
  440. :on-change (fn [e]
  441. (-> (util/evalue e)
  442. string/lower-case
  443. keyword
  444. (#(if (= % :now) :now :todo))
  445. user-handler/set-preferred-workflow!))}
  446. (for [workflow [:now :todo]]
  447. [:option {:key (name workflow) :value (name workflow)}
  448. (if (= workflow :now) "NOW/LATER" "TODO/DOING")])]]]])
  449. (defn outdenting-row [t logical-outdenting?]
  450. (toggle "preferred_outdenting"
  451. [(t :settings-page/preferred-outdenting)
  452. (ui/tippy {:html (outdenting-hint)
  453. :class "tippy-hover ml-2"
  454. :interactive true
  455. :disabled false}
  456. (svg/info))]
  457. logical-outdenting?
  458. config-handler/toggle-logical-outdenting!))
  459. (defn showing-full-blocks [t show-full-blocks?]
  460. (toggle "show_full_blocks"
  461. (t :settings-page/show-full-blocks)
  462. show-full-blocks?
  463. config-handler/toggle-show-full-blocks!))
  464. (defn preferred-pasting-file [t preferred-pasting-file?]
  465. (toggle "preferred_pasting_file"
  466. [(t :settings-page/preferred-pasting-file)
  467. (ui/tippy {:html (t :settings-page/preferred-pasting-file-hint)
  468. :class "tippy-hover ml-2"
  469. :interactive true
  470. :disabled false}
  471. (svg/info))]
  472. preferred-pasting-file?
  473. config-handler/toggle-preferred-pasting-file!))
  474. (defn auto-expand-row [t auto-expand-block-refs?]
  475. (toggle "auto_expand_block_refs"
  476. [(t :settings-page/auto-expand-block-refs)
  477. (ui/tippy {:html (auto-expand-hint)
  478. :class "tippy-hover ml-2"
  479. :interactive true
  480. :disabled false}
  481. (svg/info))]
  482. auto-expand-block-refs?
  483. config-handler/toggle-auto-expand-block-refs!))
  484. (defn tooltip-row [t enable-tooltip?]
  485. (toggle "enable_tooltip"
  486. (t :settings-page/enable-tooltip)
  487. enable-tooltip?
  488. (fn []
  489. (config-handler/toggle-ui-enable-tooltip!))))
  490. (defn shortcut-tooltip-row [t enable-shortcut-tooltip?]
  491. (toggle "enable_tooltip"
  492. (t :settings-page/enable-shortcut-tooltip)
  493. enable-shortcut-tooltip?
  494. (fn []
  495. (state/toggle-shortcut-tooltip!))))
  496. (defn timetracking-row [t enable-timetracking?]
  497. (toggle "enable_timetracking"
  498. (t :settings-page/enable-timetracking)
  499. enable-timetracking?
  500. #(let [value (not enable-timetracking?)]
  501. (config-handler/set-config! :feature/enable-timetracking? value))))
  502. (defn update-home-page
  503. [event]
  504. (let [value (util/evalue event)]
  505. (cond
  506. (string/blank? value)
  507. (let [home (get (state/get-config) :default-home {})
  508. new-home (dissoc home :page)]
  509. (p/do!
  510. (config-handler/set-config! :default-home new-home)
  511. (config-handler/set-config! :feature/enable-journals? true)
  512. (notification/show! "Journals enabled" :success)))
  513. ;; FIXME: home page should be db id instead of page name
  514. (ldb/get-page (db/get-db) value)
  515. (let [home (get (state/get-config) :default-home {})
  516. new-home (assoc home :page value)]
  517. (config-handler/set-config! :default-home new-home)
  518. (notification/show! "Home default page updated successfully!" :success))
  519. :else
  520. (notification/show! (str "The page \"" value "\" doesn't exist yet. Please create that page first, and then try again.") :warning))))
  521. (defn journal-row [enable-journals?]
  522. (toggle "enable_journals"
  523. (t :settings-page/enable-journals)
  524. enable-journals?
  525. (fn []
  526. (let [value (not enable-journals?)]
  527. (config-handler/set-config! :feature/enable-journals? value)))))
  528. (defn enable-all-pages-public-row [t enable-all-pages-public?]
  529. (toggle "all pages public"
  530. (t :settings-page/enable-all-pages-public)
  531. enable-all-pages-public?
  532. (fn []
  533. (let [value (not enable-all-pages-public?)]
  534. (config-handler/set-config! :publishing/all-pages-public? value)))))
  535. (defn zotero-settings-row []
  536. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  537. [:label.block.text-sm.font-medium.leading-5.opacity-70
  538. {:for "zotero_settings"}
  539. "Zotero"]
  540. [:div.mt-1.sm:mt-0.sm:col-span-2
  541. [:div
  542. (ui/button
  543. (t :settings)
  544. :class "text-sm"
  545. :style {:margin-top "0px"}
  546. :on-click
  547. (fn []
  548. (state/close-settings!)
  549. (route-handler/redirect! {:to :zotero-setting})))]]])
  550. (defn auto-push-row [_t current-repo enable-git-auto-push?]
  551. (when (and current-repo (string/starts-with? current-repo "https://"))
  552. (toggle "enable_git_auto_push"
  553. "Enable Git auto push"
  554. enable-git-auto-push?
  555. (fn []
  556. (let [value (not enable-git-auto-push?)]
  557. (config-handler/set-config! :git-auto-push value))))))
  558. (defn usage-diagnostics-row [t instrument-disabled?]
  559. (toggle "usage-diagnostics"
  560. (t :settings-page/disable-sentry)
  561. (not instrument-disabled?)
  562. (fn [] (instrument/disable-instrument
  563. (not instrument-disabled?)))
  564. [:span.text-sm.opacity-50 (t :settings-page/disable-sentry-desc)]))
  565. (defn clear-cache-row [t]
  566. (row-with-button-action {:left-label (t :settings-page/clear-cache)
  567. :button-label (t :settings-page/clear)
  568. :on-click #(state/pub-event! [:graph/clear-cache!])
  569. :-for "clear_cache"}))
  570. (defn version-row [t version]
  571. (row-with-button-action {:left-label (t :settings-page/current-version)
  572. :action (app-updater version)
  573. :-for "current-version"}))
  574. (defn developer-mode-row [t developer-mode?]
  575. (toggle "developer_mode"
  576. (t :settings-page/developer-mode)
  577. developer-mode?
  578. (fn []
  579. (let [mode (not developer-mode?)]
  580. (state/set-developer-mode! mode)))
  581. [:div.text-sm.opacity-50 (t :settings-page/developer-mode-desc)]))
  582. (rum/defc plugin-enabled-switcher
  583. [t]
  584. (let [value (state/lsp-enabled?-or-theme)
  585. [on? set-on?] (rum/use-state value)
  586. on-toggle #(let [v (not on?)]
  587. (set-on? v)
  588. (storage/set ::storage-spec/lsp-core-enabled v))]
  589. [:div.flex.items-center.gap-2
  590. (ui/toggle on? on-toggle true)
  591. (when (not= (boolean value) on?)
  592. (ui/button (t :plugin/restart)
  593. :on-click #(js/logseq.api.relaunch)
  594. :small? true :intent "logseq"))]))
  595. (rum/defc http-server-enabled-switcher
  596. [t]
  597. (let [[value _] (rum/use-state (boolean (storage/get ::storage-spec/http-server-enabled)))
  598. [on? set-on?] (rum/use-state value)
  599. on-toggle #(let [v (not on?)]
  600. (set-on? v)
  601. (storage/set ::storage-spec/http-server-enabled v))]
  602. [:div.flex.items-center.gap-2
  603. (ui/toggle on? on-toggle true)
  604. (when (not= (boolean value) on?)
  605. (ui/button (t :plugin/restart)
  606. :on-click #(js/logseq.api.relaunch)
  607. :small? true :intent "logseq"))]))
  608. (rum/defc flashcards-enabled-switcher
  609. [enable-flashcards?]
  610. (ui/toggle enable-flashcards?
  611. (fn []
  612. (let [value (not enable-flashcards?)]
  613. (config-handler/set-config! :feature/enable-flashcards? value)))
  614. true))
  615. (rum/defc user-proxy-settings
  616. [{:keys [type protocol host port] :as agent-opts}]
  617. (ui/button [:span.flex.items-center
  618. [:span.pr-1
  619. (case type
  620. "system" "System Default"
  621. "direct" "Direct"
  622. (and protocol host port (str protocol "://" host ":" port)))]
  623. (ui/icon "edit")]
  624. :class "text-sm"
  625. :on-click #(state/pub-event! [:go/proxy-settings agent-opts])))
  626. (defn plugin-system-switcher-row []
  627. (row-with-button-action
  628. {:left-label (t :settings-page/plugin-system)
  629. :action (plugin-enabled-switcher t)}))
  630. (defn http-server-switcher-row []
  631. (row-with-button-action
  632. {:left-label "HTTP APIs server"
  633. :action (http-server-enabled-switcher t)}))
  634. (defn flashcards-switcher-row [enable-flashcards?]
  635. (row-with-button-action
  636. {:left-label (t :settings-page/enable-flashcards)
  637. :action (flashcards-enabled-switcher enable-flashcards?)}))
  638. (defn https-user-agent-row [agent-opts]
  639. (row-with-button-action
  640. {:left-label (t :settings-page/network-proxy)
  641. :action (user-proxy-settings agent-opts)}))
  642. (rum/defcs auto-chmod-row < rum/reactive
  643. [state t]
  644. (let [enabled? (if (= nil (state/sub [:electron/user-cfgs :feature/enable-automatic-chmod?]))
  645. true
  646. (state/sub [:electron/user-cfgs :feature/enable-automatic-chmod?]))]
  647. (toggle
  648. "automatic-chmod"
  649. (t :settings-page/auto-chmod)
  650. enabled?
  651. #(do
  652. (state/set-state! [:electron/user-cfgs :feature/enable-automatic-chmod?] (not enabled?))
  653. (ipc/ipc :userAppCfgs :feature/enable-automatic-chmod? (not enabled?)))
  654. [:span.text-sm.opacity-50 (t :settings-page/auto-chmod-desc)])))
  655. (rum/defcs native-titlebar-row < rum/reactive
  656. [state t]
  657. (let [enabled? (state/sub [:electron/user-cfgs :window/native-titlebar?])]
  658. (toggle
  659. "native-titlebar"
  660. (t :settings-page/native-titlebar)
  661. enabled?
  662. #(when (js/confirm (t :relaunch-confirm-to-work))
  663. (state/set-state! [:electron/user-cfgs :window/native-titlebar?] (not enabled?))
  664. (ipc/ipc :userAppCfgs :window/native-titlebar? (not enabled?))
  665. (js/logseq.api.relaunch))
  666. [:span.text-sm.opacity-50 (t :settings-page/native-titlebar-desc)])))
  667. (rum/defcs settings-general < rum/reactive
  668. [_state current-repo]
  669. (let [preferred-language (state/sub [:preferred-language])
  670. show-radix-themes? true]
  671. [:div.panel-wrap.is-general
  672. (version-row t fv/version)
  673. (language-row t preferred-language)
  674. (theme-modes-row t)
  675. (when (and (util/electron?) (not util/mac?)) (native-titlebar-row t))
  676. (when show-radix-themes? (accent-color-row false))
  677. (when (config/global-config-enabled?) (edit-global-config-edn))
  678. (when current-repo (edit-config-edn))
  679. (when current-repo (edit-custom-css))
  680. (when current-repo (edit-export-css))]))
  681. (defn file-format-row [t preferred-format]
  682. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  683. [:label.block.text-sm.font-medium.leading-5.opacity-70
  684. {:for "preferred_format"}
  685. (t :settings-page/preferred-file-format)]
  686. [:div.mt-1.sm:mt-0.sm:col-span-2
  687. [:div.max-w-lg.rounded-md
  688. [:select.form-select.is-small
  689. {:value (name preferred-format)
  690. :on-change (fn [e]
  691. (let [format (-> (util/evalue e)
  692. (string/lower-case)
  693. keyword)]
  694. (user-handler/set-preferred-format! format)))}
  695. (for [format (map name [:org :markdown])]
  696. [:option {:key format :value format} (string/capitalize format)])]]]])
  697. (rum/defcs settings-editor < rum/reactive
  698. [_state current-repo]
  699. (let [preferred-format (state/get-preferred-format)
  700. preferred-date-format (state/get-date-formatter)
  701. preferred-workflow (state/get-preferred-workflow)
  702. enable-timetracking? (state/enable-timetracking?)
  703. enable-all-pages-public? (state/all-pages-public?)
  704. logical-outdenting? (state/logical-outdenting?)
  705. show-full-blocks? (state/show-full-blocks?)
  706. preferred-pasting-file? (state/preferred-pasting-file?)
  707. auto-expand-block-refs? (state/auto-expand-block-refs?)
  708. enable-tooltip? (state/enable-tooltip?)
  709. enable-shortcut-tooltip? (state/sub :ui/shortcut-tooltip?)
  710. show-brackets? (state/show-brackets?)
  711. wide-mode? (state/sub :ui/wide-mode?)
  712. editor-font (state/sub :ui/editor-font)
  713. enable-git-auto-push? (state/enable-git-auto-push? current-repo)
  714. db-graph? (config/db-based-graph? (state/get-current-repo))]
  715. [:div.panel-wrap.is-editor
  716. (when-not db-graph?
  717. (file-format-row t preferred-format))
  718. (date-format-row t preferred-date-format)
  719. (when-not db-graph?
  720. (workflow-row t preferred-workflow))
  721. (editor-font-family-row t editor-font)
  722. (show-brackets-row t show-brackets?)
  723. (toggle-wide-mode-row t wide-mode?)
  724. (when (util/electron?) (switch-spell-check-row t))
  725. (outdenting-row t logical-outdenting?)
  726. (showing-full-blocks t show-full-blocks?)
  727. (preferred-pasting-file t preferred-pasting-file?)
  728. (auto-expand-row t auto-expand-block-refs?)
  729. (when-not (or (util/mobile?) (mobile-util/native-platform?))
  730. (shortcut-tooltip-row t enable-shortcut-tooltip?))
  731. (when-not (or (util/mobile?) (mobile-util/native-platform?))
  732. (tooltip-row t enable-tooltip?))
  733. (timetracking-row t enable-timetracking?)
  734. (enable-all-pages-public-row t enable-all-pages-public?)
  735. (auto-push-row t current-repo enable-git-auto-push?)]))
  736. (rum/defc settings-git
  737. []
  738. [:div.panel-wrap
  739. [:div.text-sm.my-4
  740. (ui/admonition
  741. :tip
  742. [:p (t :settings-page/git-tip)])
  743. [:span.text-sm.opacity-50.my-4
  744. (t :settings-page/git-desc-1)]
  745. [:br] [:br]
  746. [:span.text-sm.opacity-50.my-4
  747. (t :settings-page/git-desc-2)]
  748. [:a {:href "https://git-scm.com/" :target "_blank"}
  749. "Git"]
  750. [:span.text-sm.opacity-50.my-4
  751. (t :settings-page/git-desc-3)]]
  752. [:br]
  753. (switch-git-auto-commit-row t)
  754. (switch-git-commit-on-close-row t)
  755. (git-auto-commit-seconds t)])
  756. (rum/defc settings-advanced < rum/reactive
  757. []
  758. (let [instrument-disabled? (state/sub :instrument/disabled?)
  759. developer-mode? (state/sub [:ui/developer-mode?])
  760. https-agent-opts (state/sub [:electron/user-cfgs :settings/agent])]
  761. [:div.panel-wrap.is-advanced
  762. (when (and (or util/mac? util/win32?) (util/electron?)) (app-auto-update-row t))
  763. (usage-diagnostics-row t instrument-disabled?)
  764. (when-not (mobile-util/native-platform?) (developer-mode-row t developer-mode?))
  765. (when (util/electron?) (https-user-agent-row https-agent-opts))
  766. (when (util/electron?) (auto-chmod-row t))
  767. (clear-cache-row t)
  768. (ui/admonition
  769. :warning
  770. [:p (t :settings-page/clear-cache-warning)])]))
  771. (rum/defc sync-enabled-switcher
  772. [enabled?]
  773. (ui/toggle enabled?
  774. (fn []
  775. (file-sync-handler/set-sync-enabled! (not enabled?)))
  776. true))
  777. (rum/defc sync-diff-merge-enabled-switcher
  778. [enabled?]
  779. (ui/toggle enabled?
  780. (fn []
  781. (file-sync-handler/set-sync-diff-merge-enabled! (not enabled?)))
  782. true))
  783. (defn sync-switcher-row [repo enabled?]
  784. (row-with-button-action
  785. (cond-> {:left-label (t :settings-page/sync)
  786. :action (sync-enabled-switcher enabled?)}
  787. (config/db-based-graph? repo)
  788. (merge {:action nil :desc "Not available yet for database graphs"}))))
  789. (defn sync-diff-merge-switcher-row [enabled?]
  790. (row-with-button-action
  791. {:left-label (str (t :settings-page/sync-diff-merge) " (Experimental!)") ;; Not included in i18n to avoid outdating translations
  792. :action (sync-diff-merge-enabled-switcher enabled?)
  793. :desc (ui/tippy {:html [:div
  794. [:div (t :settings-page/sync-diff-merge-desc)]
  795. [:div (t :settings-page/sync-diff-merge-warn)]]
  796. :class "tippy-hover ml-2"
  797. :interactive true
  798. :disabled false}
  799. (svg/info))}))
  800. (rum/defc rtc-enabled-switcher
  801. [enabled?]
  802. (ui/toggle enabled?
  803. (fn []
  804. (let [value (not enabled?)]
  805. (config-handler/set-config! :feature/enable-rtc? value)))
  806. true))
  807. (defn rtc-switcher-row [enabled?]
  808. (row-with-button-action
  809. {:left-label "RTC"
  810. :action (rtc-enabled-switcher enabled?)}))
  811. (rum/defc whiteboards-enabled-switcher
  812. [enabled?]
  813. (ui/toggle enabled?
  814. (fn []
  815. (let [value (not enabled?)]
  816. (config-handler/set-config! :feature/enable-whiteboards? value)))
  817. true))
  818. (defn whiteboards-switcher-row [enabled?]
  819. (row-with-button-action
  820. {:left-label (t :settings-page/enable-whiteboards)
  821. :action (whiteboards-enabled-switcher enabled?)}))
  822. (rum/defc settings-account-usage-description [pro-account? graph-usage]
  823. (let [count-usage (count graph-usage)
  824. count-limit (if pro-account? 10 1)
  825. count-percent (js/Math.round (/ count-usage count-limit 0.01))
  826. storage-usage (->> (map :used-gbs graph-usage)
  827. (reduce + 0))
  828. storage-usage-formatted (cond
  829. (zero? storage-usage) "0.0"
  830. (< storage-usage 0.01) "Less than 0.01"
  831. :else (gstring/format "%.2f" storage-usage))
  832. ;; TODO: check logic on this. What are the rules around storage limits?
  833. ;; do we, and should we be able to, give individual users more storage?
  834. ;; should that be on a per graph or per user basis?
  835. default-storage-limit (if pro-account? 10 0.05)
  836. storage-limit (->> (range 0 count-limit)
  837. (map #(get-in graph-usage [% :limit-gbs] default-storage-limit))
  838. (reduce + 0))
  839. storage-percent (/ storage-usage storage-limit 0.01)
  840. storage-percent-formatted (gstring/format "%.1f" storage-percent)]
  841. [:div.text-sm
  842. (when pro-account?
  843. [:<>
  844. (gstring/format "%s of %s synced graphs " count-usage count-limit)
  845. [:strong.text-white (gstring/format "(%s%%)" count-percent)]
  846. ", "])
  847. (gstring/format "%sGB of %sGB total storage " storage-usage-formatted storage-limit)
  848. [:strong.text-white (gstring/format "(%s%%)" storage-percent-formatted)]]))
  849. ; storage-usage-formatted "GB of " storage-limit "GB total storage"
  850. ; [:strong.text-white " (" storage-percent-formatted "%)"]]))
  851. (rum/defc settings-account-usage-graphs [_pro-account? graph-usage]
  852. (when (< 0 (count graph-usage))
  853. [:div.grid.gap-3 {:style {:grid-template-columns (str "repeat(" (count graph-usage) ", 1fr)")}}
  854. (for [{:keys [name used-percent]} graph-usage
  855. :let [color (if (<= 100 used-percent) "bg-red-500" "bg-blue-500")]]
  856. [:div.rounded-full.w-full.h-2 {:class "bg-black/50"
  857. :tooltip name}
  858. [:div.rounded-full.h-2 {:class color
  859. :style {:width (str used-percent "%")
  860. :min-width "0.5rem"
  861. :max-width "100%"}}]])]))
  862. (rum/defc ^:large-vars/cleanup-todo settings-account < rum/reactive
  863. []
  864. (let [current-graph-uuid (state/sub-current-file-sync-graph-uuid)
  865. graph-usage (state/get-remote-graph-usage)
  866. current-graph-is-remote? ((set (map :uuid graph-usage)) current-graph-uuid)
  867. logged-in? (user-handler/logged-in?)
  868. user-info (state/get-user-info)
  869. paid-user? (#{"active" "on_trial" "cancelled"} (:LemonStatus user-info))
  870. gift-user? (some #{"pro"} (:UserGroups user-info))
  871. pro-account? (or paid-user? gift-user?)
  872. expiration-date (some-> user-info :LemonEndsAt date/parse-iso)
  873. renewal-date (some-> user-info :LemonRenewsAt date/parse-iso)
  874. has-subscribed? (some? (:LemonStatus user-info))]
  875. [:div.panel-wrap.is-features.mb-8
  876. [:div.mt-1.sm:mt-0.sm:col-span-2
  877. (cond
  878. logged-in?
  879. [:div.grid.grid-cols-3.gap-8.pt-2
  880. [:div "Current plan"]
  881. [:div.col-span-2
  882. [:div {:class "w-full bg-gray-500/10 rounded-lg p-4 flex flex-col gap-4"}
  883. [:div.flex.gap-4.items-center
  884. (if pro-account?
  885. [:div.flex-1 "Pro"]
  886. [:div.flex-1 "Free"])
  887. (cond
  888. has-subscribed?
  889. (ui/button "Manage plan" {:class "p-1 h-8 justify-center"
  890. :disabled true
  891. :icon "upload"})
  892. ; :on-click user-handler/upgrade})
  893. (not pro-account?)
  894. (ui/button "Upgrade plan" {:class "p-1 h-8 justify-center"
  895. :icon "upload"
  896. :on-click user-handler/upgrade})
  897. :else nil)]
  898. (settings-account-usage-graphs pro-account? graph-usage)
  899. (settings-account-usage-description pro-account? graph-usage)
  900. (if current-graph-is-remote?
  901. (ui/button "Deactivate syncing" {:class "p-1 h-8 justify-center"
  902. :disabled true
  903. :background "gray"
  904. :icon "cloud-off"})
  905. (ui/button "Activate syncing" {:class "p-1 h-8 justify-center"
  906. :background "blue"
  907. :icon "cloud"
  908. :on-click #(fs/maybe-onboarding-show :sync-initiate)}))]]
  909. (when has-subscribed?
  910. [:<>
  911. [:div "Billing"]
  912. [:div.col-span-2.flex.flex-col.gap-4
  913. (cond
  914. ;; If there is no expiration date, print the renewal date
  915. (and renewal-date (nil? expiration-date))
  916. [:div
  917. [:strong.font-semibold "Next billing date: "
  918. (date/get-locale-string renewal-date)]]
  919. ;; If the expiration date is in the future, word it as such
  920. (< (js/Date.) expiration-date)
  921. [:div
  922. [:strong.font-semibold "Pro plan expires on: "
  923. (date/get-locale-string expiration-date)]]
  924. ;; Otherwise, ind
  925. :else
  926. [:div
  927. [:strong.font-semibold "Pro plan expired on: "
  928. (date/get-locale-string expiration-date)]])
  929. [:div (ui/button "Open invoices" {:class "w-full h-8 p-1 justify-center"
  930. :disabled true
  931. :background "gray"
  932. :icon "receipt"})]]])
  933. [:div "Profile"]
  934. [:div.col-span-2.grid.grid-cols-2.gap-4
  935. [:div.flex.flex-col.gap-2.box-border {:class "basis-1/2"}
  936. [:label.text-sm.font-semibold "First name"]
  937. [:input.rounded.border.px-2.py-1.box-border {:class "border-blue-500 bg-black/25 w-full"}]]
  938. [:div.flex.flex-col.gap-2 {:class "basis-1/2"}
  939. [:label.text-sm.font-semibold "Last name"]
  940. [:input.rounded.border.px-2.py-1.box-border {:class "border-blue-500 bg-black/25 w-full"}]]
  941. [:div.flex-1.flex.flex-col.gap-2.col-span-2
  942. [:label.text-sm.font-semibold "Username"]
  943. [:input.rounded.border.px-2.py-1.box-border {:class "border-blue-500 bg-black/25"
  944. :value (user-handler/email)}]]]
  945. [:div "Authentication"]
  946. [:div.col-span-2
  947. [:div.grid.grid-cols-2.gap-4
  948. [:div (ui/button (t :logout) {:class "p-1 h-8 justify-center w-full"
  949. :background "gray"
  950. :icon "logout"
  951. :on-click user-handler/logout})]
  952. [:div (ui/button "Reset password" {:class "p-1 h-8 justify-center w-full"
  953. :disabled true
  954. :background "gray"
  955. :icon "key"
  956. :on-click user-handler/logout})]
  957. [:div.col-span-2 (ui/button "Delete Account" {:class "p-1 h-8 justify-center w-full"
  958. :disabled true
  959. :background "red"})]]]]
  960. (not logged-in?)
  961. [:div.grid.grid-cols-3.gap-8.pt-2
  962. [:div "Authentication"]
  963. [:div.col-span-2.flex.flex-wrap.gap-4
  964. [:div.w-full.text-white "With a Logseq account, you can access cloud-based services like Logseq Sync and alpha/beta features."]
  965. [:div.flex-1 (ui/button "Sign up" {:class "h-8 w-full text-center justify-center"
  966. :on-click (fn []
  967. (state/close-settings!)
  968. (state/pub-event! [:user/login]))})]
  969. [:div.flex-1 (ui/button (t :login) {:icon "login"
  970. :class "h-8 w-full text-center justify-center"
  971. :background "gray"
  972. :on-click (fn []
  973. (state/close-settings!)
  974. (state/pub-event! [:user/login]))})]]
  975. [:div.col-span-3.flex.flex-col.gap-4 {:class "bg-black/20 p-4 rounded-lg"}
  976. [:div.flex.w-full.items-center
  977. [:div {:class "w-1/2 text-lg"}
  978. "Discover the power of "
  979. [:strong {:class "text-white/80"} "Logseq Sync"]]
  980. [:div {:class "w-1/2 bg-gradient-to-r from-white/10 to-transparent p-3 rounded-lg flex items-center gap-2 px-5 ml-5"}
  981. [:div.w-3.h-3.rounded-full.bg-green-500]
  982. "Synced"]]
  983. [:div.flex.w-full.gap-4
  984. [:div {:class "w-1/2 bg-black/50 rounded-lg p-4 pt-10 relative flex flex-col gap-4"}
  985. [:div.absolute.top-0.left-4.bg-gray-700.uppercase.px-2.py-1.rounded-b-lg.font-bold.text-xs "Free"]
  986. [:div
  987. [:strong.text-white.text-xl.font-normal "$0"]]
  988. [:div.text-white.font-bold {:class "h-[2.5rem] "} "Get started with basic syncing"]
  989. [:ul.text-xs.list-none.m-0.flex.flex-col.gap-0.5
  990. [:li "Unlimited unsynced graphs"]
  991. [:li "1 synced graph (up to 50MB, notes only)"]
  992. [:li "No asset syncing"]
  993. [:li "Access to core Logseq features"]]]
  994. [:div {:class "w-1/2 bg-black/50 rounded-lg p-4 pt-10 relative flex flex-col gap-4"}
  995. [:div.absolute.top-0.left-4.bg-blue-700.uppercase.px-2.py-1.rounded-b-lg.font-bold.text-xs "Pro"]
  996. [:div
  997. [:strong.text-white.text-xl.font-normal "$10"]
  998. [:span.text-xs.font-base {:class "ml-0.5"} "/ month"]]
  999. [:div.text-white.font-bold {:class "h-[2.5rem]"} "Unlock advanced syncing and more"]
  1000. [:ul.text-xs.list-none.m-0.flex.flex-col.gap-0.5
  1001. [:li "Unlimited unsynced graphs"]
  1002. [:li "10 synced graphs (up to 5GB each)"]
  1003. [:li "Sync assets up to 100MB per file"]
  1004. [:li "Early access to alpha/beta features"]
  1005. [:li "Upcoming cloud-based features, including Logseq Publish"]]]]]])]]))
  1006. (rum/defc settings-features < rum/reactive
  1007. []
  1008. (let [current-repo (state/get-current-repo)
  1009. enable-journals? (state/enable-journals? current-repo)
  1010. enable-flashcards? (state/enable-flashcards? current-repo)
  1011. enable-sync? (state/enable-sync?)
  1012. enable-sync-diff-merge? (state/enable-sync-diff-merge?)
  1013. db-based? (config/db-based-graph? current-repo)
  1014. enable-whiteboards? (state/enable-whiteboards? current-repo)
  1015. logged-in? (user-handler/logged-in?)]
  1016. [:div.panel-wrap.is-features.mb-8
  1017. (journal-row enable-journals?)
  1018. (when (not enable-journals?)
  1019. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  1020. [:label.block.text-sm.font-medium.leading-5.opacity-70
  1021. {:for "default page"}
  1022. (t :settings-page/home-default-page)]
  1023. [:div.mt-1.sm:mt-0.sm:col-span-2
  1024. [:div.max-w-lg.rounded-md.sm:max-w-xs
  1025. [:input#home-default-page.form-input.is-small.transition.duration-150.ease-in-out
  1026. {:default-value (state/sub-default-home-page)
  1027. :on-blur update-home-page
  1028. :on-key-press (fn [e]
  1029. (when (= "Enter" (util/ekey e))
  1030. (update-home-page e)))}]]]])
  1031. (when-not db-based? (whiteboards-switcher-row enable-whiteboards?))
  1032. (when (and (util/electron?) config/feature-plugin-system-on?)
  1033. (plugin-system-switcher-row))
  1034. (when (util/electron?)
  1035. (http-server-switcher-row))
  1036. (when-not db-based? (flashcards-switcher-row enable-flashcards?))
  1037. (when-not db-based? (zotero-settings-row))
  1038. (when (and config/dev? (config/db-based-graph? current-repo))
  1039. ;; FIXME: Wire this up again to RTC init calls
  1040. (rtc-switcher-row (state/enable-rtc? current-repo)))
  1041. (when-not web-platform?
  1042. [:div.mt-1.sm:mt-0.sm:col-span-2
  1043. [:hr]
  1044. (if logged-in?
  1045. [:div
  1046. (user-handler/email)
  1047. [:p (ui/button (t :logout) {:class "p-1"
  1048. :icon "logout"
  1049. :on-click user-handler/logout})]]
  1050. [:div
  1051. (ui/button (t :login) {:class "p-1"
  1052. :icon "login"
  1053. :on-click (fn []
  1054. (state/close-settings!)
  1055. (state/pub-event! [:user/login]))})
  1056. [:p.text-sm.opacity-50 (t :settings-page/login-prompt)]])])
  1057. (when-not web-platform?
  1058. [:<>
  1059. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  1060. [:label.flex.font-medium.leading-5.self-start.mt-1
  1061. (ui/icon (if logged-in? "lock-open" "lock") {:class "mr-1"}) (t :settings-page/beta-features)]]
  1062. [:div.flex.flex-col.gap-4
  1063. {:class (when-not user-handler/alpha-or-beta-user? "opacity-50 pointer-events-none cursor-not-allowed")}
  1064. (sync-switcher-row current-repo enable-sync?)
  1065. (when (and enable-sync? (not (config/db-based-graph? current-repo)))
  1066. (sync-diff-merge-switcher-row enable-sync-diff-merge?))
  1067. [:div.text-sm
  1068. (t :settings-page/sync-desc-1)
  1069. [:a.mx-1 {:href "https://blog.logseq.com/how-to-setup-and-use-logseq-sync/"
  1070. :target "_blank"}
  1071. (t :settings-page/sync-desc-2)]
  1072. (t :settings-page/sync-desc-3)]]])]))
  1073. ;; (when-not web-platform?
  1074. ;; [:<>
  1075. ;; [:hr]
  1076. ;; [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  1077. ;; [:label.flex.font-medium.leading-5.self-start.mt-1 (ui/icon (if logged-in? "lock-open" "lock") {:class "mr-1"}) (t :settings-page/alpha-features)]]
  1078. ;; [:div.flex.flex-col.gap-4
  1079. ;; {:class (when-not user-handler/alpha-user? "opacity-50 pointer-events-none cursor-not-allowed")}
  1080. ;; ;; features
  1081. ;; ]])
  1082. (def DEFAULT-ACTIVE-TAB-STATE (if config/ENABLE-SETTINGS-ACCOUNT-TAB [:account :account] [:general :general]))
  1083. (rum/defc settings-effect
  1084. < rum/static
  1085. [active]
  1086. (rum/use-effect!
  1087. (fn []
  1088. (let [active (and (sequential? active) (name (first active)))
  1089. ^js ds (.-dataset js/document.body)]
  1090. (if active
  1091. (set! (.-settingsTab ds) active)
  1092. (js-delete ds "settingsTab"))
  1093. #(js-delete ds "settingsTab")))
  1094. [active])
  1095. [:<>])
  1096. (rum/defcs settings-collaboration < rum/reactive
  1097. (rum/local "" ::invite-email)
  1098. {:will-mount (fn [state]
  1099. (rtc-handler/<rtc-get-users-info)
  1100. state)}
  1101. [state]
  1102. (let [*invite-email (::invite-email state)
  1103. current-repo (state/get-current-repo)
  1104. users (get (state/sub :rtc/users-info) current-repo)]
  1105. [:div.panel-wrap.is-collaboration.mb-8
  1106. [:div.flex.flex-col.gap-2.mt-4
  1107. [:h2.opacity-50.font-medium "Members:"]
  1108. [:div.users.flex.flex-col.gap-1
  1109. (for [{user-name :user/name
  1110. user-email :user/email
  1111. graph<->user-user-type :graph<->user/user-type} users]
  1112. [:div.flex.flex-row.items-center.gap-2 {:key (str "user-" user-name)}
  1113. [:div user-name]
  1114. (when user-email [:div.opacity-50.text-sm user-email])
  1115. (when graph<->user-user-type [:div.opacity-50.text-sm (name graph<->user-user-type)])])]
  1116. [:div.flex.flex-col.gap-4.mt-4
  1117. (shui/input
  1118. {:placeholder "Email address"
  1119. :on-change #(reset! *invite-email (util/evalue %))})
  1120. (shui/button
  1121. {:on-click (fn []
  1122. (let [user-email @*invite-email
  1123. graph-uuid (ldb/get-graph-rtc-uuid (db/get-db))]
  1124. (when-not (string/blank? user-email)
  1125. (when graph-uuid
  1126. (rtc-handler/<rtc-invite-email graph-uuid user-email)))))}
  1127. "Invite")]]]))
  1128. (rum/defcs ^:large-vars/cleanup-todo settings
  1129. < (rum/local DEFAULT-ACTIVE-TAB-STATE ::active)
  1130. {:will-mount
  1131. (fn [state]
  1132. (state/load-app-user-cfgs)
  1133. state)
  1134. :did-mount
  1135. (fn [state]
  1136. (let [active-tab (first (:rum/args state))
  1137. *active (::active state)]
  1138. (when (keyword? active-tab)
  1139. (reset! *active [active-tab nil])))
  1140. state)
  1141. :will-unmount
  1142. (fn [state]
  1143. (state/close-settings!)
  1144. state)}
  1145. rum/reactive
  1146. [state _active-tab]
  1147. (let [current-repo (state/sub :git/current-repo)
  1148. _installed-plugins (state/sub :plugin/installed-plugins)
  1149. plugins-of-settings (and config/lsp-enabled? (seq (plugin-handler/get-enabled-plugins-if-setting-schema)))
  1150. *active (::active state)
  1151. logged-in? (user-handler/logged-in?)]
  1152. [:div#settings.cp__settings-main
  1153. (settings-effect @*active)
  1154. [:div.cp__settings-inner
  1155. [:aside.md:w-64 {:style {:min-width "10rem"}}
  1156. [:header.cp__settings-header
  1157. [:h1.cp__settings-modal-title (t :settings)]]
  1158. [:ul.settings-menu
  1159. (for [[label id text icon]
  1160. [(when config/ENABLE-SETTINGS-ACCOUNT-TAB
  1161. [:account "account" (t :settings-page/tab-account) (ui/icon "user-circle")])
  1162. [:general "general" (t :settings-page/tab-general) (ui/icon "adjustments")]
  1163. [:editor "editor" (t :settings-page/tab-editor) (ui/icon "writing")]
  1164. [:keymap "keymap" (t :settings-page/tab-keymap) (ui/icon "keyboard")]
  1165. (when (util/electron?)
  1166. [:version-control "git" (t :settings-page/tab-version-control) (ui/icon "history")])
  1167. ;; (when (util/electron?)
  1168. ;; [:assets "assets" (t :settings-page/tab-assets) (ui/icon "box")])
  1169. [:advanced "advanced" (t :settings-page/tab-advanced) (ui/icon "bulb")]
  1170. [:features "features" (t :settings-page/tab-features) (ui/icon "app-feature")]
  1171. (when logged-in?
  1172. [:collaboration "collaboration" (t :settings-page/tab-collaboration) (ui/icon "users")])
  1173. (when plugins-of-settings
  1174. [:plugins-setting "plugins" (t :settings-of-plugins) (ui/icon "puzzle")])]]
  1175. (when label
  1176. [:li.settings-menu-item
  1177. {:key text
  1178. :data-id id
  1179. :class (util/classnames [{:active (= label (first @*active))}])
  1180. :on-click (fn []
  1181. (if (= label :plugins-setting)
  1182. (state/pub-event! [:go/plugins-settings (:id (first plugins-of-settings))])
  1183. (reset! *active [label (first @*active)])))}
  1184. [:a.flex.items-center.settings-menu-link icon [:strong text]]]))]]
  1185. [:article
  1186. [:header.cp__settings-header
  1187. [:h1.cp__settings-category-title (t (keyword (str "settings-page/tab-" (name (first @*active)))))]]
  1188. (case (first @*active)
  1189. :plugins-setting
  1190. (let [label (second @*active)]
  1191. (state/pub-event! [:go/plugins-settings (:id (first plugins-of-settings))])
  1192. (reset! *active [label label])
  1193. nil)
  1194. :account
  1195. (settings-account)
  1196. :general
  1197. (settings-general current-repo)
  1198. :editor
  1199. (settings-editor current-repo)
  1200. :keymap
  1201. (shortcut/shortcut-keymap-x)
  1202. :version-control
  1203. (settings-git)
  1204. :assets
  1205. (assets/settings-content)
  1206. :advanced
  1207. (settings-advanced)
  1208. :features
  1209. (settings-features)
  1210. :collaboration
  1211. (settings-collaboration)
  1212. nil)]]]))