| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507 |
- (ns frontend.components.settings
- (:require [clojure.string :as string]
- [frontend.components.svg :as svg]
- [frontend.config :as config]
- [frontend.context.i18n :as i18n]
- [frontend.date :as date]
- [frontend.dicts :as dicts]
- [frontend.handler :as handler]
- [frontend.handler.config :as config-handler]
- [frontend.handler.notification :as notification]
- [frontend.handler.page :as page-handler]
- [frontend.handler.route :as route-handler]
- [frontend.handler.ui :as ui-handler]
- [frontend.handler.user :as user-handler]
- [frontend.modules.instrumentation.core :as instrument]
- [frontend.state :as state]
- [frontend.ui :as ui]
- [frontend.util :refer [classnames] :as util]
- [frontend.version :refer [version]]
- [goog.object :as gobj]
- [reitit.frontend.easy :as rfe]
- [rum.core :as rum]))
- (rum/defcs set-email < (rum/local "" ::email)
- [state]
- (let [email (get state ::email)]
- [:div.p-8.flex.items-center.justify-center
- [:div.w-full.mx-auto
- [:div
- [:div
- [:h1.title.mb-1
- "Your email address:"]
- [:div.mt-2.mb-4.relative.rounded-md.max-w-xs
- [:input#.form-input.is-small
- {:autoFocus true
- :on-change (fn [e]
- (reset! email (util/evalue e)))}]]]]
- (ui/button
- "Submit"
- :on-click
- (fn []
- (user-handler/set-email! @email)))
- [:hr]
- [:span.pl-1.opacity-70 "Git commit requires the email address."]]]))
- (rum/defcs set-cors < (rum/local "" ::cors)
- [state]
- (let [cors (get state ::cors)]
- [:div.p-8.flex.items-center.justify-center
- [:div.w-full.mx-auto
- [:div
- [:div
- [:h1.title.mb-1
- "Your cors address:"]
- [:div.mt-2.mb-4.relative.rounded-md.max-w-xs
- [:input#.form-input.is-small
- {:autoFocus true
- :on-change (fn [e]
- (reset! cors (util/evalue e)))}]]]]
- (ui/button
- "Submit"
- :on-click
- (fn []
- (user-handler/set-cors! @cors)))
- [:hr]
- [:span.pl-1.opacity-70 "Git commit requires the cors address."]]]))
- (defn toggle
- [label-for name state on-toggle & [detail-text]]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for label-for}
- name]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.rounded-md
- {:style {:display "flex" :gap "1rem" :align-items "center"}}
- (ui/toggle state on-toggle true)
- detail-text]]])
- (rum/defcs app-updater < rum/reactive
- [state]
- (let [update-pending? (state/sub :electron/updater-pending?)
- {:keys [type payload]} (state/sub :electron/updater)]
- [:span.cp__settings-app-updater
- (ui/button
- (if update-pending? "Checking ..." "Check for updates")
- :class "text-sm p-1 mr-3"
- :disabled update-pending?
- :on-click #(js/window.apis.checkForUpdates false))
- (when-not (or update-pending?
- (string/blank? type))
- [:div.update-state
- (case type
- "update-not-available"
- [:p "😀 Your app is up-to-date!"]
- "update-available"
- (let [{:keys [name url]} payload]
- [:p (str "Found new release ")
- [:a.link
- {:on-click
- (fn [e]
- (js/window.apis.openExternal url)
- (util/stop e))}
- svg/external-link name " 🎉"]])
- "error"
- [:p "⚠️ Oops, Something Went Wrong!" [:br] " Please check out the "
- [:a.link
- {:on-click
- (fn [e]
- (js/window.apis.openExternal "https://github.com/logseq/logseq/releases")
- (util/stop e))}
- svg/external-link " release channel"]])])]))
- (rum/defc delete-account-confirm
- [close-fn]
- (rum/with-context [[t] i18n/*tongue-context*]
- [:div
- (ui/admonition
- :important
- [:p.text-gray-700 (t :user/delete-account-notice)])
- [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
- [:span.flex.w-full.rounded-md.sm:ml-3.sm:w-auto
- [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
- {:type "button"
- :on-click user-handler/delete-account!}
- (t :user/delete-account)]]
- [:span.mt-3.flex.w-full.rounded-md.sm:mt-0.sm:w-auto
- [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
- {:type "button"
- :on-click close-fn}
- "Cancel"]]]]))
- (rum/defc outdenting-hint
- []
- [:div.ui__modal-panel
- {:style {:box-shadow "0 4px 20px 4px rgba(0, 20, 60, .1), 0 4px 80px -8px rgba(0, 20, 60, .2)"}}
- [:div {:style {:margin "12px" :max-width "500px"}}
- [:p.text-sm
- "The left side shows outdenting with the default setting, and the right shows outdenting with logical outdenting enabled. "
- [:a.text-sm
- {:target "_blank" :href "https://discuss.logseq.com/t/whats-your-preferred-outdent-behavior-the-direct-one-or-the-logical-one/978"}
- "→ Learn more"]]
- [:img {:src "https://discuss.logseq.com/uploads/default/original/1X/e8ea82f63a5e01f6d21b5da827927f538f3277b9.gif"
- :width 500
- :height 500}]]])
- (defn edit-config-edn []
- (rum/with-context [[t] i18n/*tongue-context*]
- [:div.text-sm
- [:a {:href (rfe/href :file {:path (config/get-config-path)})
- :on-click #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))}
- (t :settings-page/edit-config-edn)]]))
- (defn show-brackets-row [t show-brackets?]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "show_brackets"}
- (t :settings-page/show-brackets)]
- [:div
- [:div.rounded-md.sm:max-w-xs
- (ui/toggle show-brackets?
- config-handler/toggle-ui-show-brackets!
- true)]]
- [:div {:style {:text-align "right"}}
- ;; TODO: Fetch this shortcut from config.cljs so there's one source of truth
- (ui/keyboard-shortcut [:meta :c :meta :b])]])
- (defn language-row [t preferred-language]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "preferred_language"}
- (t :language)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md
- [:select.form-select.is-small
- {:value preferred-language
- :on-change (fn [e]
- (let [lang-code (util/evalue e)]
- (state/set-preferred-language! lang-code)
- (ui-handler/re-render-root!)))}
- (for [language dicts/languages]
- (let [lang-code (name (:value language))
- lang-label (:label language)]
- [:option {:key lang-code :value lang-code} lang-label]))]]]])
- (defn theme-modes-row [t switch-theme system-theme? dark?]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "toggle_theme"}
- (t :right-side-bar/switch-theme (string/capitalize switch-theme))]
- [:div.flex.flex-row.mt-1.sm:mt-0.sm:col-span-1
- [:div.rounded-md.sm:max-w-xs
- [:ul.theme-modes-options
- [:li {:on-click (partial state/use-theme-mode! "light")
- :class (classnames [{:active (and (not system-theme?) (not dark?))}])} [:i.mode-light] [:strong "light"]]
- [:li {:on-click (partial state/use-theme-mode! "dark")
- :class (classnames [{:active (and (not system-theme?) dark?)}])} [:i.mode-dark] [:strong "dark"]]
- [:li {:on-click (partial state/use-theme-mode! "system")
- :class (classnames [{:active system-theme?}])} [:i.mode-system] [:strong "system"]]]]]
- [:div {:style {:text-align "right"}}
- (ui/keyboard-shortcut [:t :t])]])
- (defn file-format-row [t preferred-format]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "preferred_format"}
- (t :settings-page/preferred-file-format)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md
- [:select.form-select.is-small
- {:value (name preferred-format)
- :on-change (fn [e]
- (let [format (-> (util/evalue e)
- (string/lower-case)
- keyword)]
- (user-handler/set-preferred-format! format)))}
- (for [format (map name [:org :markdown])]
- [:option {:key format :value format} (string/capitalize format)])]]]])
- (defn date-format-row [t preferred-date-format]
- [: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-start
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "custom_date_format"}
- (t :settings-page/custom-date-format)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md
- [:select.form-select.is-small
- {:value preferred-date-format
- :on-change (fn [e]
- (let [format (util/evalue e)]
- (when-not (string/blank? format)
- (config-handler/set-config! :date-formatter format)
- (notification/show!
- [:div "You need to re-index your graph to make the change works"]
- :success)
- (state/close-modal!)
- (route-handler/redirect! {:to :repos}))))}
- (for [format (sort (date/journal-title-formatters))]
- [:option {:key format} format])]]]])
- (defn workflow-row [t preferred-workflow]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "preferred_workflow"}
- (t :settings-page/preferred-workflow)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md
- [:select.form-select.is-small
- {:value (name preferred-workflow)
- :on-change (fn [e]
- (-> (util/evalue e)
- string/lower-case
- keyword
- (#(if (= % :now) :now :todo))
- user-handler/set-preferred-workflow!))}
- (for [workflow [:now :todo]]
- [:option {:key (name workflow) :value (name workflow)}
- (if (= workflow :now) "NOW/LATER" "TODO/DOING")])]]]])
- (defn outdenting-row [t logical-outdenting?]
- (toggle "preferred_outdenting"
- [(t :settings-page/preferred-outdenting)
- (ui/tippy {:html (outdenting-hint)
- :class "tippy-hover ml-2"
- :interactive true
- :disabled false}
- (svg/info))]
- logical-outdenting?
- config-handler/toggle-logical-outdenting!))
- (defn tooltip-row [t enable-tooltip?]
- (toggle "enable_tooltip"
- (t :settings-page/enable-tooltip)
- enable-tooltip?
- (fn []
- (config-handler/toggle-ui-enable-tooltip!))))
- (defn timetracking-row [t enable-timetracking?]
- (toggle "enable_timetracking"
- (t :settings-page/enable-timetracking)
- enable-timetracking?
- (fn []
- (let [value (not enable-timetracking?)]
- (config-handler/set-config! :feature/enable-timetracking? value)))))
- (defn update-home-page
- [event]
- (let [value (util/evalue event)]
- (cond
- (string/blank? value)
- (let [home (get (state/get-config) :default-home {})
- new-home (dissoc home :page)]
- (config-handler/set-config! :default-home new-home)
- (notification/show! "Home default page updated successfully!" :success))
- (page-handler/page-exists? (string/lower-case value))
- (let [home (get (state/get-config) :default-home {})
- new-home (assoc home :page value)]
- (config-handler/set-config! :default-home new-home)
- (notification/show! "Home default page updated successfully!" :success))
- :else
- (notification/show! (str "The page \"" value "\" doesn't exist yet. Please create that page first, and then try again.") :warning))))
- (defn journal-row [t enable-journals?]
- [(toggle "enable_journals"
- (t :settings-page/enable-journals)
- enable-journals?
- (fn []
- (let [value (not enable-journals?)]
- (config-handler/set-config! :feature/enable-journals? value))))
- (when (not enable-journals?)
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "default page"}
- (t :settings-page/home-default-page)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md.sm:max-w-xs
- [:input#home-default-page.form-input.is-small.transition.duration-150.ease-in-out
- {:default-value (state/sub-default-home-page)
- :on-blur update-home-page
- :on-key-press (fn [e]
- (when (= "Enter" (util/ekey e))
- (update-home-page e)))}]]]])])
- (defn encryption-row [t enable-encryption?]
- (toggle "enable_encryption"
- (t :settings-page/enable-encryption)
- enable-encryption?
- (fn []
- (let [value (not enable-encryption?)]
- (config-handler/set-config! :feature/enable-encryption? value)))))
- (defn keyboard-shortcuts-row [t]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "customize_shortcuts"}
- (t :settings-page/customize-shortcuts)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div
- (ui/button
- (t :settings-page/shortcut-settings)
- :class "text-sm p-1"
- :style {:margin-top "0px"}
- :on-click
- (fn []
- (state/close-settings!)
- (route-handler/redirect! {:to :shortcut})))]]])
- (defn auto-push-row [t current-repo enable-git-auto-push?]
- (when (string/starts-with? current-repo "https://")
- (toggle "enable_git_auto_push"
- "Enable Git auto push"
- enable-git-auto-push?
- (fn []
- (let [value (not enable-git-auto-push?)]
- (config-handler/set-config! :git-auto-push value))))))
- (defn usage-diagnostics-row [t instrument-disabled?]
- (toggle "usage-diagnostics"
- (t :settings-page/disable-sentry)
- (not instrument-disabled?)
- (fn [] (instrument/disable-instrument
- (not instrument-disabled?)))
- [:span.text-sm.opacity-50 "Logseq will never collect your local graph database or sell your data."]))
- (defn clear-cache-row [t]
- [:div.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center.sm:pt-5
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "clear_cache"}
- (t :settings-page/clear-cache)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md.sm:max-w-xs
- (ui/button
- (t :settings-page/clear)
- :class "text-sm p-1"
- :on-click handler/clear-cache!)]]])
- (defn version-row [t version]
- [:div.it.app-updater.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- (t :settings-page/current-version)]
- [:div.wrap.sm:mt-0.sm:col-span-2
- (when (util/electron?) (app-updater))
- [:span.ver version]]])
- (defn developer-mode-row [t developer-mode?]
- (toggle "developer_mode"
- (t :settings-page/developer-mode)
- developer-mode?
- (fn []
- (let [mode (not developer-mode?)]
- (state/set-developer-mode! mode)
- (and mode (util/electron?)
- (if (js/confirm (t :developer-mode-alert))
- (js/logseq.api.relaunch)))))
- [:div.text-sm.opacity-50 (t :settings-page/developer-mode-desc)]))
- (rum/defcs settings < rum/reactive
- []
- (let [preferred-format (state/get-preferred-format)
- preferred-date-format (state/get-date-formatter)
- preferred-workflow (state/get-preferred-workflow)
- preferred-language (state/sub [:preferred-language])
- enable-timetracking? (state/enable-timetracking?)
- current-repo (state/get-current-repo)
- enable-journals? (state/enable-journals? current-repo)
- enable-encryption? (state/enable-encryption? current-repo)
- instrument-disabled? (state/sub :instrument/disabled?)
- logical-outdenting? (state/logical-outdenting?)
- enable-tooltip? (state/enable-tooltip?)
- enable-git-auto-push? (state/enable-git-auto-push? current-repo)
- enable-block-time? (state/enable-block-time?)
- show-brackets? (state/show-brackets?)
- github-token (state/sub [:me :access-token])
- cors-proxy (state/sub [:me :cors_proxy])
- logged? (state/logged?)
- developer-mode? (state/sub [:ui/developer-mode?])
- theme (state/sub :ui/theme)
- dark? (= "dark" theme)
- system-theme? (state/sub :ui/system-theme?)
- switch-theme (if dark? "white" "dark")]
- (rum/with-context [[t] i18n/*tongue-context*]
- [:div#settings.cp__settings-main
- [:div.panel-wrap
- [:h1.title (t :settings)]]
- (when current-repo
- [[:div.panel-wrap
- (edit-config-edn)]])
- [:hr]
- [:div.panel-wrap
- (theme-modes-row t switch-theme system-theme? dark?)
- (language-row t preferred-language)
- (file-format-row t preferred-format)
- (date-format-row t preferred-date-format)
- (workflow-row t preferred-workflow)
- (show-brackets-row t show-brackets?)
- (outdenting-row t logical-outdenting?)
- (tooltip-row t enable-tooltip?)
- (timetracking-row t enable-timetracking?)
- (journal-row t enable-journals?)
- (encryption-row t enable-encryption?)
- (keyboard-shortcuts-row t)
- (auto-push-row t current-repo enable-git-auto-push?)]
- [:hr] ;; Outside of panel wrap so that it is wider
- [:div.panel-wrap
- (clear-cache-row t)
- (version-row t version)
- (usage-diagnostics-row t instrument-disabled?)
- (developer-mode-row t developer-mode?)
- (when logged?
- [:div
- [:div.mt-6.sm:mt-5.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center.sm:pt-5
- [:label.block.text-sm.font-medium.leading-5.sm:mt-px..opacity-70
- {:for "cors"}
- (t :settings-page/custom-cors-proxy-server)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md.sm:max-w-xs
- [:input#pat.form-input.is-small.transition.duration-150.ease-in-out
- {:default-value cors-proxy
- :on-blur (fn [event]
- (when-let [server (util/evalue event)]
- (user-handler/set-cors! server)
- (notification/show! "Custom CORS proxy updated successfully!" :success)))
- :on-key-press (fn [event]
- (let [k (gobj/get event "key")]
- (if (= "Enter" k)
- (when-let [server (util/evalue event)]
- (user-handler/set-cors! server)
- (notification/show! "Custom CORS proxy updated successfully!" :success)))))}]]]]
- (ui/admonition
- :important
- [:p (t :settings-page/dont-use-other-peoples-proxy-servers)
- [:a {:href "https://github.com/isomorphic-git/cors-proxy"
- :target "_blank"}
- "https://github.com/isomorphic-git/cors-proxy"]])])
- (when logged?
- [:div
- [:hr]
- [:div.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center.sm:pt-5
- [:label.block.text-sm.font-medium.leading-5.opacity-70.text-red-600.dark:text-red-400
- {:for "delete account"}
- (t :user/delete-account)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md.sm:max-w-xs
- (ui/button (t :user/delete-your-account)
- :on-click (fn []
- (ui-handler/toggle-settings-modal!)
- (js/setTimeout #(state/set-modal! delete-account-confirm))))]]]])]])))
|