Merge branch 'feat/db' into fix/multiple-tabs

This commit is contained in:
Tienson Qin
2025-04-17 23:20:07 +08:00
27 changed files with 941 additions and 1042 deletions

View File

@@ -330,9 +330,7 @@
views
all-files
pages-datoms))]
(prn :debug "db-graph?" db-graph?)
(prn :debug "block/journal-day schema" (str (:block/journal-day (:schema db))))
{:schema (assoc schema :block/journal-day {:db/index true})
{:schema schema
:initial-data data}))
(defn restore-initial-data

View File

@@ -45,7 +45,8 @@
;; for pages
:block/alias {:db/valueType :db.type/ref
:db/cardinality :db.cardinality/many}
:db/cardinality :db.cardinality/many
:db/index true}
;; todo keywords, e.g. "TODO", "DOING", "DONE"
:block/marker {}

View File

@@ -53,6 +53,7 @@
(def tooltip-portal (util/lsui-wrap "TooltipPortal"))
(def tooltip-content (util/lsui-wrap "TooltipContent"))
(def tooltip-provider (util/lsui-wrap "TooltipProvider"))
(def tooltip-arrow (util/lsui-wrap "TooltipArrow"))
(def card (util/lsui-wrap "Card"))
(def card-header (util/lsui-wrap "CardHeader"))

View File

@@ -17,10 +17,9 @@
"del": "^6.0.0",
"glob": "9.0.0",
"gulp": "^4.0.2",
"gulp-replace": "^1.1.4",
"gulp-postcss": "^10.0.0",
"gulp-replace": "^1.1.4",
"ip": "1.1.9",
"semver": "7.5.2",
"karma": "^6.4.4",
"karma-chrome-launcher": "^3.2.0",
"karma-cljs-test": "^0.1.0",
@@ -33,6 +32,7 @@
"postcss-import-ext-glob": "2.0.1",
"postcss-nested": "6.0.0",
"purgecss": "4.0.2",
"semver": "7.5.2",
"shadow-cljs": "2.26.0",
"stylelint": "^13.8.0",
"stylelint-config-standard": "^20.0.0",
@@ -161,7 +161,6 @@
"react-intersection-observer": "^9.3.5",
"react-resize-context": "3.0.0",
"react-textarea-autosize": "8.3.3",
"react-tippy": "1.4.0",
"react-transition-group": "4.3.0",
"react-virtuoso": "4.12.5",
"remove-accents": "0.4.2",

View File

@@ -241,19 +241,16 @@
(if db-based?
(db-based-statuses)
(file-based-statuses))
(mapv (fn [m]
(let [command (if db-based?
[:div.flex.flex-row.items-center.gap-2 m [:div.text-xs.opacity-50 "Status"]]
m)
icon (if db-based?
(case m
(mapv (fn [command]
(let [icon (if db-based?
(case command
"Canceled" "Cancelled"
"Doing" "InProgress50"
m)
command)
"square-asterisk")]
[command (->marker m) (str "Set status to " m) icon]))))]
[command (->marker command) (str "Set status to " command) icon]))))]
(when (seq result)
(map (fn [v] (conj v "TASK")) result))))
(map (fn [v] (conj v "TASK STATUS")) result))))
(defn file-based-priorities
[]
@@ -273,17 +270,17 @@
(db-based-priorities)
(file-based-priorities))
(mapv (fn [item]
(let [command (if db-based?
[:div.flex.flex-row.items-center.gap-2 item [:div.text-xs.opacity-50 "Priority"]]
item)]
[command (->priority item) (str "Set priority to " item)
(let [command item]
[command
(->priority item)
(str "Set priority to " item)
(if db-based?
(str "priorityLvl" item)
(str "circle-letter-" (util/safe-lower-case item)))])))
(with-no-priority)
(vec))]
(when (seq result)
(map (fn [v] (conj v "PRIORITY")) result))))
(map (fn [v] (into v ["PRIORITY"])) result))))
;; Credits to roamresearch.com
@@ -297,7 +294,7 @@
[]
(mapv (fn [level]
(let [heading (str "Heading " level)]
[heading (->heading level) heading (str "h-" level)])) (range 1 7)))
[heading (->heading level) heading (str "h-" level) "Heading"])) (range 1 7)))
(defonce *matched-commands (atom nil))
(defonce *initial-commands (atom nil))

View File

@@ -1068,7 +1068,7 @@
(contains? config/video-formats asset-type))))
(declare block-positioned-properties)
(rum/defc page-reference < rum/reactive
(rum/defc page-reference < rum/reactive db-mixins/query
"Component for page reference"
[html-export? uuid-or-title* {:keys [nested-link? show-brackets? id] :as config} label]
(when uuid-or-title*
@@ -1077,7 +1077,8 @@
uuid-or-title*)
show-brackets? (if (some? show-brackets?) show-brackets? (state/show-brackets?))
contents-page? (= "contents" (string/lower-case (str id)))
block (db/get-page uuid-or-title)
block* (db/get-page uuid-or-title)
block (or (some-> (:db/id block*) db/sub-block) block*)
config' (assoc config
:label (mldoc/plain->text label)
:contents-page? contents-page?

View File

@@ -782,19 +782,20 @@
(rum/defc new-block-mode < rum/reactive
[]
(when (state/sub [:document/mode?])
(ui/tippy {:html [:div.p-2
[:p.mb-2 [:b "Document mode"]]
[:ul
[:li
[:div.inline-block.mr-1 (ui/render-keyboard-shortcut (shortcut-dh/gen-shortcut-seq :editor/new-line))]
[:p.inline-block "to create new block"]]
[:li
[:p.inline-block.mr-1 "Click `D` or type"]
[:div.inline-block.mr-1 (ui/render-keyboard-shortcut (shortcut-dh/gen-shortcut-seq :ui/toggle-document-mode))]
[:p.inline-block "to toggle document mode"]]]]}
[:a.block.px-1.text-sm.font-medium.bg-base-2.rounded-md.mx-2
{:on-click state/toggle-document-mode!}
"D"])))
(ui/tooltip
[:a.block.px-1.text-sm.font-medium.bg-base-2.rounded-md.mx-2
{:on-click state/toggle-document-mode!}
"D"]
[:div.p-2
[:p.mb-2 [:b "Document mode"]]
[:ul
[:li
[:div.inline-block.mr-1 (ui/render-keyboard-shortcut (shortcut-dh/gen-shortcut-seq :editor/new-line))]
[:p.inline-block "to create new block"]]
[:li
[:p.inline-block.mr-1 "Click `D` or type"]
[:div.inline-block.mr-1 (ui/render-keyboard-shortcut (shortcut-dh/gen-shortcut-seq :ui/toggle-document-mode))]
[:p.inline-block "to toggle document mode"]]]])))
(def help-menu-items
[{:title "Handbook" :icon "book-2" :on-click #(handbooks/toggle-handbooks)}

View File

@@ -45,7 +45,7 @@
(or
(= "Add new property" (first item))
(when (= (count item) 5)
(contains? #{"TASK" "PRIORITY"} (last item))))) commands)
(contains? #{"TASK STATUS" "PRIORITY"} (last item))))) commands)
commands))
(rum/defcs commands < rum/reactive
@@ -56,63 +56,59 @@
_ (when (state/get-editor-action)
(reset! *matched matched'))
page? (db/page? (db/entity (:db/id (state/get-edit-block))))
matched (filter-commands page? @*matched)]
matched (filter-commands page? @*matched)
filtered? (not= matched @commands/*initial-commands)]
(ui/auto-complete
matched
{:get-group-name
(fn [item]
(when (= (count item) 5) (last item)))
(cond->
{:item-render
(fn [item]
(let [command-name (first item)
command-doc (get item 2)
plugin-id (get-in item [1 1 1 :pid])
doc (when (state/show-command-doc?) command-doc)
options (some-> item (get 3))
icon-name (some-> (if (map? options) (:icon options) options) (name))
command-name (if icon-name
[:span.flex.items-center.gap-1
(shui/tabler-icon icon-name)
[:strong.font-normal command-name]]
command-name)]
(cond
(or plugin-id (vector? doc))
[:div.has-help
{:title plugin-id}
command-name
(when doc (ui/tooltip [:small (svg/help-circle)] doc))]
:item-render
(fn [item]
(let [command-name (first item)
command-doc (get item 2)
plugin-id (get-in item [1 1 1 :pid])
doc (when (state/show-command-doc?) command-doc)
options (some-> item (get 3))
icon-name (some-> (if (map? options) (:icon options) options) (name))
command-name (if icon-name
[:span.flex.items-center.gap-1
(shui/tabler-icon icon-name)
[:strong.font-normal command-name]]
command-name)]
(cond
(or plugin-id (vector? doc))
[:div.has-help
{:title plugin-id}
command-name
(when doc (ui/tippy
{:html doc
:interactive true
:fixed-position? true
:position "right"}
(string? doc)
[:div {:title doc}
command-name]
[:small (svg/help-circle)]))]
:else
[:div command-name])))
(string? doc)
[:div {:title doc}
command-name]
:else
[:div command-name])))
:on-chosen
(fn [chosen-item]
(let [command (first chosen-item)]
(reset! commands/*current-command command)
(let [command-steps (get (into {} matched) command)
restore-slash? (or
(contains? #{"Today" "Yesterday" "Tomorrow" "Current time"} command)
(and
(not (fn? command-steps))
(not (contains? (set (map first command-steps)) :editor/input))
(not (contains? #{"Date picker" "Template" "Deadline" "Scheduled" "Upload an image"} command))))]
(editor-handler/insert-command! id command-steps
format
{:restore? restore-slash?
:command command}))))
:class
"cp__commands-slash"})))
:on-chosen
(fn [chosen-item]
(let [command (first chosen-item)]
(reset! commands/*current-command command)
(let [command-steps (get (into {} matched) command)
restore-slash? (or
(contains? #{"Today" "Yesterday" "Tomorrow" "Current time"} command)
(and
(not (fn? command-steps))
(not (contains? (set (map first command-steps)) :editor/input))
(not (contains? #{"Date picker" "Template" "Deadline" "Scheduled" "Upload an image"} command))))]
(editor-handler/insert-command! id command-steps
format
{:restore? restore-slash?
:command command}))))
:class
"cp__commands-slash"}
(not filtered?)
(assoc :get-group-name
(fn [item]
(when (= (count item) 5) (last item))))))))
(defn- page-on-chosen-handler
[embed? input id q pos format]

View File

@@ -81,10 +81,9 @@
(defn priority-cp
[{:block/keys [pre-block? priority] :as block}]
(when (and (not pre-block?) priority)
(ui/tippy
{:interactive true
:html (set-priority block priority)}
(priority-text priority))))
(ui/tooltip
(priority-text priority)
(set-priority block priority))))
(defn clock-summary-cp
[block body]
@@ -96,22 +95,20 @@
(not= summary "0m")
(not (string/blank? summary)))
[:div {:style {:max-width 100}}
(ui/tippy {:html (fn []
(when-let [logbook (drawer/get-logbook body)]
(let [clocks (->> (last logbook)
(filter #(string/starts-with? % "CLOCK:"))
(remove string/blank?))]
[:div.p-4
[:div.font-bold.mb-2 "LOGBOOK:"]
[:ul
(for [clock (take 10 (reverse clocks))]
[:li clock])]])))
:interactive true
:in-editor? true
:delay [1000, 100]}
[:div.text-sm.time-spent.ml-1 {:style {:padding-top 3}}
[:a.fade-link
summary]])]))))
(ui/tooltip
[:div.text-sm.time-spent.ml-1 {:style {:padding-top 3}}
[:a.fade-link
summary]]
(when-let [logbook (drawer/get-logbook body)]
(let [clocks (->> (last logbook)
(filter #(string/starts-with? % "CLOCK:"))
(remove string/blank?))]
[:div.p-4
[:div.font-bold.mb-2 "LOGBOOK:"]
[:ul
(for [clock (take 10 (reverse clocks))]
[:li clock])]])))]))))
(rum/defc timestamp-editor
[ast *show-datapicker?]

View File

@@ -10,22 +10,17 @@
(rum/defc query-refresh-button
[query-time {:keys [on-pointer-down full-text-search?]}]
(ui/tippy
{:html [:div
[:p
(if full-text-search?
[:span "Full-text search results will not be refreshed automatically."]
[:span (str "This query takes " (int query-time) "ms to finish, it's a bit slow so that auto refresh is disabled.")])]
[:p
"Click the refresh button instead if you want to see the latest result."]]
:interactive true
:popperOptions {:modifiers {:preventOverflow
{:enabled true
:boundariesElement "viewport"}}}
:arrow true}
[:a.fade-link.flex
{:on-pointer-down on-pointer-down}
(ui/icon "refresh" {:style {:font-size 20}})]))
(ui/tooltip
[:a.fade-link.flex
{:on-pointer-down on-pointer-down}
(ui/icon "refresh" {:style {:font-size 20}})]
[:div
[:p
(if full-text-search?
[:span "Full-text search results will not be refreshed automatically."]
[:span (str "This query takes " (int query-time) "ms to finish, it's a bit slow so that auto refresh is disabled.")])]
[:p
"Click the refresh button instead if you want to see the latest result."]]))
;; Custom query header only used by file graphs
(rum/defc custom-query-header

View File

@@ -908,30 +908,35 @@
(reset! *excluded-pages? value)
(set-setting! :excluded-pages? value)))
true)]]
(when (config/db-based-graph? (state/get-current-repo))
[:div.flex.flex-col.mb-2
[:p "Created before"]
(when created-at-filter
[:div (.toDateString (js/Date. (+ created-at-filter (get-in graph [:all-pages :created-at-min]))))])
(ui/tippy {:html [:div.pr-3 (str (js/Date. (+ created-at-filter (get-in graph [:all-pages :created-at-min]))))]}
;; Slider keeps track off the range from min created-at to max created-at
;; because there were bugs with setting min and max directly
(ui/slider created-at-filter
{:min 0
:max (- (get-in graph [:all-pages :created-at-max])
(get-in graph [:all-pages :created-at-min]))
:on-change #(do
(reset! *created-at-filter (int %))
(set-setting! :created-at-filter (int %)))}))])
(ui/tooltip
;; Slider keeps track off the range from min created-at to max created-at
;; because there were bugs with setting min and max directly
(ui/slider created-at-filter
{:min 0
:max (- (get-in graph [:all-pages :created-at-max])
(get-in graph [:all-pages :created-at-min]))
:on-change #(do
(reset! *created-at-filter (int %))
(set-setting! :created-at-filter (int %)))})
[:div.px-1 (str (js/Date. (+ created-at-filter (get-in graph [:all-pages :created-at-min]))))])])
(when (seq focus-nodes)
[:div.flex.flex-col.mb-2
[:p {:title "N hops from selected nodes"}
"N hops from selected nodes"]
(ui/tippy {:html [:div.pr-3 n-hops]}
(ui/slider (or n-hops 10)
{:min 1
:max 10
:on-change #(reset! *n-hops (int %))}))])
(ui/tooltip
(ui/slider (or n-hops 10)
{:min 1
:max 10
:on-change #(reset! *n-hops (int %))})
[:div n-hops])])
[:a.opacity-70.opacity-100 {:on-click (fn []
(swap! *graph-reset? not)
@@ -977,39 +982,45 @@
[:div.flex.flex-col.mb-2
[:p {:title "Link Distance"}
"Link Distance"]
(ui/tippy {:html [:div.pr-3 link-dist]}
(ui/slider (/ link-dist 10)
{:min 1 ;; 10
:max 18 ;; 180
:on-change #(let [value (int %)]
(reset! *link-dist (* value 10))
(set-forcesetting! :link-dist (* value 10)))}))]
(ui/tooltip
(ui/slider (/ link-dist 10)
{:min 1 ;; 10
:max 18 ;; 180
:on-change #(let [value (int %)]
(reset! *link-dist (* value 10))
(set-forcesetting! :link-dist (* value 10)))})
[:div link-dist])]
[:div.flex.flex-col.mb-2
[:p {:title "Charge Strength"}
"Charge Strength"]
(ui/tippy {:html [:div.pr-3 charge-strength]}
(ui/slider (/ charge-strength 100)
{:min -10 ;;-1000
:max 10 ;;1000
:on-change #(let [value (int %)]
(reset! *charge-strength (* value 100))
(set-forcesetting! :charge-strength (* value 100)))}))]
(ui/tooltip
(ui/slider (/ charge-strength 100)
{:min -10 ;;-1000
:max 10 ;;1000
:on-change #(let [value (int %)]
(reset! *charge-strength (* value 100))
(set-forcesetting! :charge-strength (* value 100)))})
[:div charge-strength])]
[:div.flex.flex-col.mb-2
[:p {:title "Charge Range"}
"Charge Range"]
(ui/tippy {:html [:div.pr-3 charge-range]}
(ui/slider (/ charge-range 100)
{:min 5 ;;500
:max 40 ;;4000
:on-change #(let [value (int %)]
(reset! *charge-range (* value 100))
(set-forcesetting! :charge-range (* value 100)))}))]
(ui/tooltip
(ui/slider (/ charge-range 100)
{:min 5 ;;500
:max 40 ;;4000
:on-change #(let [value (int %)]
(reset! *charge-range (* value 100))
(set-forcesetting! :charge-range (* value 100)))})
[:div charge-range])]
[:a.opacity-70.opacity-100 {:on-click (fn []
(swap! *graph-forcereset? not)
(reset! *link-dist 70)
(reset! *charge-strength -600)
(reset! *charge-range 600))}
[:a
{:on-click (fn []
(swap! *graph-forcereset? not)
(reset! *link-dist 70)
(reset! *charge-strength -600)
(reset! *charge-range 600))}
"Reset Forces"]]]))
{})
(graph-filter-section

View File

@@ -585,14 +585,14 @@
(when (and develop-mode? (util/electron?) (not market?))
[:div
(ui/tippy {:html [:div (t :plugin/unpacked-tips)]
:arrow true}
(ui/button
(t :plugin/load-unpacked)
{:icon "upload"
:intent "link"
:class "load-unpacked"
:on-click plugin-handler/load-unpacked-plugin}))
(ui/tooltip
(ui/button
(t :plugin/load-unpacked)
{:icon "upload"
:intent "link"
:class "load-unpacked"
:on-click plugin-handler/load-unpacked-plugin})
[:div (t :plugin/unpacked-tips)])
(when (util/electron?)
(unpacked-plugin-loader selected-unpacked-pkg))])]
@@ -1075,9 +1075,7 @@
[:div.px-4
(when-not (string/blank? notes)
(ui/tippy
{:html [:p notes]}
[:span.opacity-30.hover:opacity-80 (ui/icon "info-circle")]))]])]
(ui/tooltip [:span.opacity-30.hover:opacity-80 (ui/icon "info-circle")] [:p notes]))]])]
;; all done
[:div.py-4 [:strong.text-4xl (str "\uD83C\uDF89 " (t :plugin/all-updated))]])

View File

@@ -145,11 +145,8 @@
(when (= "Enter" (.-key e))
(util/stop-propagation e)))} label)))))
(when show-type-change-hints?
(ui/tippy {:html "Changing the property type clears some property configurations."
:class "tippy-hover ml-2"
:interactive true
:disabled false}
(svg/info)))]))
(ui/tooltip (svg/info)
[:span "Changing the property type clears some property configurations."]))]))
(rum/defc property-select
[exclude-properties select-opts]

View File

@@ -601,19 +601,17 @@
(dropdown-editor-menuitem {:icon :letter-t
:title "Property type"
:desc (if disabled?'
(ui/tippy {:html [:div.w-96
"The type of this property is locked once you start using it. This is to make sure all your existing information stays correct if the property type is changed later. To unlock, all uses of a property must be deleted."]
:class "tippy-hover ml-2"
:interactive true
:disabled false}
(str property-type-label'))
(ui/tooltip
[:span (str property-type-label')]
[:div.w-96
"The type of this property is locked once you start using it. This is to make sure all your existing information stays correct if the property type is changed later. To unlock, all uses of a property must be deleted."])
(str property-type-label'))
:disabled? disabled?'
:submenu-content (fn [ops]
(property-type-sub-pane property ops))}))
(when (and (= property-type :node)
(not (contains? #{:logseq.property/parent} (:db/ident property))))
(not (contains? #{:logseq.property/parent} (:db/ident property))))
(dropdown-editor-menuitem {:icon :hash
:disabled? disabled?
:title "Specify node tags"

View File

@@ -381,13 +381,7 @@
:let [active? (= color color-accent)
none? (= color :none)]]
[:div.flex.items-center
(ui/tippy
{:html (case color
:none [:p {:style {:max-width "300px"}}
"Cancel accent color. This is currently in beta stage and mainly used for compatibility with custom themes."]
:logseq "Logseq classical color"
(str (name color) " color"))
:delay [1000, 100]}
(ui/tooltip
(shui/button
{: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"
:auto-focus (and _in-modal? active?)
@@ -402,7 +396,13 @@
{:class (if none? "h-0.5 w-full bg-red-700"
"w-2 h-2 rounded-full transition ease-in duration-100")
:style {:background-color (if-not none? (str "var(--rx-" (name color) "-07)") "")
:opacity (if (or none? active?) 1 0)}}]))])]]
:opacity (if (or none? active?) 1 0)}}])
(case color
:none [:p {:style {:max-width "300px"}}
"Cancel accent color. This is currently in beta stage and mainly used for compatibility with custom themes."]
:logseq "Logseq classical color"
(str (name color) " color")))])]]
[:div
(row-with-button-action
@@ -431,11 +431,8 @@
{:for "custom_date_format"}
(t :settings-page/custom-date-format)
(when-not (config/db-based-graph? (state/get-current-repo))
(ui/tippy {:html (t :settings-page/custom-date-format-warning)
:class "tippy-hover ml-2"
:interactive true
:disabled false}
(svg/info)))]
(ui/tooltip [:span.flex.px-2 (svg/info)]
[:span (t :settings-page/custom-date-format-warning)]))]
[:div.mt-1.sm:mt-0.sm:col-span-2
[:div.max-w-lg.rounded-md
[:select.form-select.is-small
@@ -484,11 +481,8 @@
(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))]
(ui/tooltip [:span.flex.px-2 (svg/info)]
(outdenting-hint) {:content-props {:side "right"}})]
logical-outdenting?
config-handler/toggle-logical-outdenting!))
@@ -501,22 +495,16 @@
(defn preferred-pasting-file [t preferred-pasting-file?]
(toggle "preferred_pasting_file"
[(t :settings-page/preferred-pasting-file)
(ui/tippy {:html (t :settings-page/preferred-pasting-file-hint)
:class "tippy-hover ml-2"
:interactive true
:disabled false}
(svg/info))]
(ui/tooltip [:span.flex.px-2 (svg/info)]
[:span.block.w-64 (t :settings-page/preferred-pasting-file-hint)])]
preferred-pasting-file?
config-handler/toggle-preferred-pasting-file!))
(defn auto-expand-row [t auto-expand-block-refs?]
(toggle "auto_expand_block_refs"
[(t :settings-page/auto-expand-block-refs)
(ui/tippy {:html (auto-expand-hint)
:class "tippy-hover ml-2"
:interactive true
:disabled false}
(svg/info))]
(ui/tooltip [:span.flex.px-2 (svg/info)]
(auto-expand-hint))]
auto-expand-block-refs?
config-handler/toggle-auto-expand-block-refs!))
@@ -866,26 +854,23 @@
(row-with-button-action
{:left-label (str (t :settings-page/sync-diff-merge) " (Experimental!)") ;; Not included in i18n to avoid outdating translations
:action (sync-diff-merge-enabled-switcher enabled?)
:desc (ui/tippy {:html [:div
[:div (t :settings-page/sync-diff-merge-desc)]
[:div (t :settings-page/sync-diff-merge-warn)]]
:class "tippy-hover ml-2"
:interactive true
:disabled false}
(svg/info))}))
:desc (ui/tooltip [:span.inline-flex.px-1 (svg/info)]
[:div
[:div (t :settings-page/sync-diff-merge-desc)]
[:div (t :settings-page/sync-diff-merge-warn)]])}))
(rum/defc rtc-enabled-switcher
[enabled?]
(ui/toggle enabled?
(fn []
(let [value (not enabled?)]
(state/set-rtc-enabled! value)))
true))
(fn []
(let [value (not enabled?)]
(state/set-rtc-enabled! value)))
true))
(defn rtc-switcher-row [enabled?]
(row-with-button-action
{:left-label "RTC"
:action (rtc-enabled-switcher enabled?)}))
{:left-label "RTC"
:action (rtc-enabled-switcher enabled?)}))
(rum/defc whiteboards-enabled-switcher
[enabled?]

View File

@@ -488,16 +488,17 @@
:on-click #(score-and-next-card 5 card card-index finished? phase review-records cb)})])
(when preview?
(ui/tippy {:html [:div.text-sm
(t :flashcards/modal-btn-reset-tip)]
:class "tippy-hover"
:interactive true}
(ui/button [:span (t :flashcards/modal-btn-reset)]
:id "card-reset"
:class (util/hiccup->class "opacity-60.hover:opacity-100.card-reset")
:on-click (fn [e]
(util/stop e)
(operation-reset! card)))))]
(ui/tooltip
(ui/button [:span (t :flashcards/modal-btn-reset)]
:id "card-reset"
:class (util/hiccup->class "opacity-60.hover:opacity-100.card-reset")
:on-click (fn [e]
(util/stop e)
(operation-reset! card)))
[:div.text-sm
(t :flashcards/modal-btn-reset-tip)]
{:trigger-props {:as-child false}}))]
[:div.my-3 (ui/button "Review cards" :small? true)])]))))
(rum/defc view-modal <
@@ -629,27 +630,20 @@
;; FIXME: CSS issue
(if @*preview-mode?
(ui/tippy {:html [:div.text-sm (t :flashcards/modal-current-total)]
:interactive true}
[:div.opacity-60.text-sm.mr-2
@*card-index
[:span "/"]
total])
(ui/tippy {:html [:div.text-sm (t :flashcards/modal-overdue-total)]
;; :class "tippy-hover"
:interactive true}
[:div.opacity-60.text-sm.mr-2
(max 0 (- filtered-total @*card-index))
[:span "/"]
total]))
(ui/tippy
{:html [:div.text-sm (t :flashcards/modal-toggle-preview-mode)]
:delay [1000, 100]
:class "tippy-hover"
:interactive true
:disabled false}
(ui/tooltip
[:div.opacity-60.text-sm.mr-2
@*card-index
[:span "/"]
total]
[:div.text-sm (t :flashcards/modal-current-total)])
(ui/tooltip
[:div.opacity-60.text-sm.mr-2
(max 0 (- filtered-total @*card-index))
[:span "/"]
total]
[:div.text-sm (t :flashcards/modal-overdue-total)]))
(ui/tooltip
(ui/button
(merge
{:icon "letter-a"
@@ -661,13 +655,11 @@
:button-props {:id "preview-all-cards"}
:small? true}
(when @*preview-mode?
{:icon-props {:style {:color "var(--ls-button-background)"}}}))))
{:icon-props {:style {:color "var(--ls-button-background)"}}})))
[:div.text-sm (t :flashcards/modal-toggle-preview-mode)]
{:trigger-props {:as-child false}})
(ui/tippy
{:html [:div.text-sm (t :flashcards/modal-toggle-random-mode)]
:delay [1000, 100]
:class "tippy-hover"
:interactive true}
(ui/tooltip
(ui/button
(merge
{:icon "arrows-shuffle"
@@ -677,7 +669,9 @@
(swap! *random-mode? not))
:small? true}
(when @*random-mode?
{:icon-props {:style {:color "var(--ls-button-background)"}}}))))]]
{:icon-props {:style {:color "var(--ls-button-background)"}}})))
[:div.text-sm (t :flashcards/modal-toggle-random-mode)]
{:trigger-props {:as-child false}})]]
[:div.px-1
(when (and (not modal?) (not @*preview-mode?))
{:on-click (fn []

View File

@@ -17,6 +17,7 @@
[frontend.error :as error]
[frontend.handler.command-palette :as command-palette]
[frontend.handler.events :as events]
[frontend.handler.events.ui]
[frontend.handler.file-based.events]
[frontend.handler.file-based.file :as file-handler]
[frontend.handler.global-config :as global-config-handler]

View File

@@ -818,7 +818,7 @@
concat-prev-block?
(let [children (:block/_parent (db/entity (:db/id block)))
db-based? (config/db-based-graph? repo)
prev-block-is-not-parent? (not= (:block/uuid (:block/parent block)) (:block/uuid prev-block))
prev-block-is-not-parent? (empty? (:block/_parent prev-block))
delete-prev-block? (and db-based?
prev-block-is-not-parent?
(empty? (:block/tags block))

View File

@@ -10,24 +10,7 @@
[clojure.core.async.interop :refer [p->c]]
[clojure.string :as string]
[frontend.commands :as commands]
[frontend.components.block :as block]
[frontend.components.cmdk.core :as cmdk]
[frontend.components.diff :as diff]
[frontend.components.encryption :as encryption]
[frontend.components.file-based.git :as git-component]
[frontend.components.file-sync :as file-sync]
[frontend.components.page :as component-page]
[frontend.components.plugins :as plugin]
[frontend.components.property.dialog :as property-dialog]
[frontend.components.repo :as repo]
[frontend.components.select :as select]
[frontend.components.selection :as selection]
[frontend.components.settings :as settings]
[frontend.components.shell :as shell]
[frontend.components.user.login :as login]
[frontend.components.whiteboard :as whiteboard]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.date :as date]
[frontend.db :as db]
[frontend.db.async :as db-async]
@@ -36,10 +19,7 @@
[frontend.db.persist :as db-persist]
[frontend.db.transact :as db-transact]
[frontend.extensions.fsrs :as fsrs]
[frontend.extensions.srs :as srs]
[frontend.fs :as fs]
[frontend.fs.capacitor-fs :as capacitor-fs]
[frontend.fs.nfs :as nfs]
[frontend.fs.sync :as sync]
[frontend.fs.watcher-handler :as fs-watcher]
[frontend.handler.assets :as assets-handler]
@@ -50,8 +30,6 @@
[frontend.handler.db-based.rtc-flows :as rtc-flows]
[frontend.handler.editor :as editor-handler]
[frontend.handler.export :as export]
[frontend.handler.file-based.file :as file-handler]
[frontend.handler.file-based.nfs :as nfs-handler]
[frontend.handler.file-sync :as file-sync-handler]
[frontend.handler.graph :as graph-handler]
[frontend.handler.notification :as notification]
@@ -63,12 +41,9 @@
[frontend.handler.search :as search-handler]
[frontend.handler.shell :as shell-handler]
[frontend.handler.ui :as ui-handler]
[frontend.handler.user :as user-handler]
[frontend.mobile.core :as mobile]
[frontend.mobile.graph-picker :as graph-picker]
[frontend.mobile.util :as mobile-util]
[frontend.modules.instrumentation.posthog :as posthog]
[frontend.modules.instrumentation.sentry :as sentry-event]
[frontend.modules.outliner.pipeline :as pipeline]
[frontend.modules.outliner.ui :as ui-outliner-tx]
[frontend.modules.shortcut.core :as st]
@@ -76,16 +51,11 @@
[frontend.quick-capture :as quick-capture]
[frontend.search :as search]
[frontend.state :as state]
[frontend.ui :as ui]
[frontend.util :as util]
[frontend.util.persist-var :as persist-var]
[goog.dom :as gdom]
[lambdaisland.glogi :as log]
[logseq.common.config :as common-config]
[logseq.common.util :as common-util]
[logseq.shui.ui :as shui]
[promesa.core :as p]
[rum.core :as rum]))
[promesa.core :as p]))
;; TODO: should we move all events here?
@@ -96,54 +66,10 @@
(async/<! (sync/<sync-stop))
(some-> (sync/<sync-start) async/<!)))
(defn- file-sync-stop! []
(defn file-sync-stop! []
(async/go (async/<! (p->c (persist-var/load-vars)))
(async/<! (sync/<sync-stop))))
(defn- enable-beta-features!
[]
(when-not (false? (state/enable-sync?)) ; user turns it off
(file-sync-handler/set-sync-enabled! true)))
(defmethod handle :user/fetch-info-and-graphs [[_]]
(state/set-state! [:ui/loading? :login] false)
(async/go
(let [result (async/<! (sync/<user-info sync/remoteapi))]
(cond
(instance? ExceptionInfo result)
nil
(map? result)
(do
(state/set-user-info! result)
(when-let [uid (user-handler/user-uuid)]
(sentry-event/set-user! uid))
(let [status (if (user-handler/alpha-or-beta-user?) :welcome :unavailable)]
(when (and (= status :welcome) (user-handler/logged-in?))
(enable-beta-features!)
(async/<! (p->c (rtc-handler/<get-remote-graphs)))
(async/<! (file-sync-handler/load-session-graphs))
(p/let [repos (repo-handler/refresh-repos!)]
(when-let [repo (state/get-current-repo)]
(when (some #(and (= (:url %) repo)
(vector? (:sync-meta %))
(util/uuid-string? (first (:sync-meta %)))
(util/uuid-string? (second (:sync-meta %)))) repos)
(sync/<sync-start)))))
(file-sync/maybe-onboarding-show status)))))))
(defmethod handle :user/logout [[_]]
(file-sync-handler/reset-session-graphs)
(sync/remove-all-pwd!)
(file-sync-handler/reset-user-state!)
(login/sign-out!))
(defmethod handle :user/login [[_ host-ui?]]
(if (or host-ui? (not util/electron?))
(js/window.open config/LOGIN-URL)
(if (mobile-util/native-platform?)
(route-handler/redirect! {:to :user-login})
(login/open-login-modal!))))
(defmethod handle :graph/added [[_ repo {:keys [empty-graph?]}]]
(search-handler/rebuild-indices!)
(plugin-handler/hook-plugin-app :graph-after-indexed {:repo repo :empty-graph? empty-graph?})
@@ -215,45 +141,6 @@
:warning))
(graph-switch-on-persisted graph opts))))
(defmethod handle :graph/pull-down-remote-graph [[_ graph dir-name]]
(if (mobile-util/native-ios?)
(when-let [graph-name (or dir-name (:GraphName graph))]
(let [graph-name (util/safe-sanitize-file-name graph-name)]
(if (string/blank? graph-name)
(notification/show! "Illegal graph folder name.")
;; Create graph directory under Logseq document folder (local)
(when-let [root (state/get-local-container-root-url)]
(let [graph-path (graph-picker/validate-graph-dirname root graph-name)]
(->
(p/let [exists? (fs/dir-exists? graph-path)]
(let [overwrite? (if exists?
(js/confirm (str "There's already a directory with the name \"" graph-name "\", do you want to overwrite it? Make sure to backup it first if you're not sure about it."))
true)]
(if overwrite?
(p/let [_ (fs/mkdir-if-not-exists graph-path)]
(nfs-handler/ls-dir-files-with-path!
graph-path
{:ok-handler (fn []
(file-sync-handler/init-remote-graph graph-path graph)
(js/setTimeout (fn [] (repo-handler/refresh-repos!)) 200))}))
(let [graph-name (-> (js/prompt "Please specify a new directory name to download the graph:")
str
string/trim)]
(when-not (string/blank? graph-name)
(state/pub-event! [:graph/pull-down-remote-graph graph graph-name]))))))
(p/catch (fn [^js e]
(notification/show! (str e) :error)
(js/console.error e)))))))))
(when (:GraphName graph)
(shui/dialog-open!
(file-sync/pick-dest-to-sync-panel graph)))))
(defmethod handle :graph/pick-page-histories [[_ graph-uuid page-name]]
(shui/dialog-open!
(file-sync/pick-page-histories-panel graph-uuid page-name)
{:id :page-histories :label "modal-page-histories"}))
(defmethod handle :graph/open-new-window [[_ev target-repo]]
(p/let [current-repo (state/get-current-repo)]
(ui-handler/open-new-window-or-tab! current-repo target-repo)))
@@ -261,67 +148,6 @@
(defmethod handle :graph/migrated [[_ _repo]]
(js/alert "Graph migrated."))
(defn get-local-repo
[]
(when-let [repo (state/get-current-repo)]
(when (config/local-file-based-graph? repo)
repo)))
(defn ask-permission
[repo]
(when
(and (not (util/electron?))
(not (mobile-util/native-platform?)))
(fn [{:keys [close]}]
[:div
;; TODO: fn translation with args
[:p
"Grant native filesystem permission for directory: "
[:b (config/get-local-dir repo)]]
(ui/button
(t :settings-permission/start-granting)
:class "ui__modal-enter"
:on-click (fn []
(nfs/check-directory-permission! repo)
(close)))])))
(defmethod handle :modal/nfs-ask-permission []
(when-let [repo (get-local-repo)]
(some-> (ask-permission repo)
(shui/dialog-open! {:align :top}))))
(defmethod handle :modal/show-cards [[_ cards-id]]
(let [db-based? (config/db-based-graph? (state/get-current-repo))]
(shui/dialog-open!
(if db-based? (fn [] (fsrs/cards-view cards-id)) srs/global-cards)
{:id :srs
:label "flashcards__cp"})))
(defmethod handle :modal/show-instruction [_]
(shui/dialog-open!
capacitor-fs/instruction
{:id :instruction
:label "instruction__cp"}))
(defmethod handle :modal/show-themes-modal [[_ classic?]]
(if classic?
(plugin/open-select-theme!)
(route-handler/go-to-search! :themes)))
(defmethod handle :ui/toggle-appearance [_]
(let [popup-id "appearance_settings"]
(if (gdom/getElement popup-id)
(shui/popup-hide! popup-id)
(shui/popup-show!
(js/document.querySelector ".toolbar-dots-btn")
(fn []
(settings/appearance))
{:id popup-id
:align :end}))))
(defmethod handle :modal/set-git-username-and-email [[_ _content]]
(shui/dialog-open! git-component/set-git-username-and-email))
(defmethod handle :page/create [[_ page-name opts]]
(if (= page-name (date/today))
(page-handler/create-today-journal!)
@@ -333,26 +159,10 @@
(defmethod handle :page/renamed [[_ repo data]]
(page-common-handler/after-page-renamed! repo data))
(defmethod handle :page/show-delete-dialog [[_ selected-rows ok-handler]]
(shui/dialog-open!
(component-page/batch-delete-dialog
selected-rows false
ok-handler)))
(defmethod handle :page/create-today-journal [[_ _repo]]
(p/let [_ (page-handler/create-today-journal!)]
(ui-handler/re-render-root!)))
(defmethod handle :file/not-matched-from-disk [[_ path disk-content db-content]]
(when-let [repo (state/get-current-repo)]
(shui/dialog-open!
#(diff/local-file repo path disk-content db-content)
{:label "diff__cp"})))
(defmethod handle :modal/display-file-version-selector [[_ versions path get-content]]
(shui/dialog-open!
#(git-component/file-version-selector versions path get-content)))
(defmethod handle :graph/sync-context []
(let [context {:dev? config/dev?
:node-test? util/node-test?
@@ -391,48 +201,6 @@
(export/auto-db-backup! repo {:backup-now? true})
(fs-watcher/load-graph-files! repo)))))
(defmethod handle :notification/show [[_ {:keys [content status clear?]}]]
(notification/show! content status clear?))
(defmethod handle :command/run [_]
(when (util/electron?)
(shui/dialog-open! shell/shell)))
(defmethod handle :go/search [_]
(shui/dialog-open!
cmdk/cmdk-modal
{:id :ls-dialog-cmdk
:align :top
:content-props {:class "ls-dialog-cmdk"}
:close-btn? false}))
(defmethod handle :go/plugins [_]
(plugin/open-plugins-modal!))
(defmethod handle :go/plugins-waiting-lists [_]
(plugin/open-waiting-updates-modal!))
(defmethod handle :go/plugins-from-file [[_ plugins]]
(plugin/open-plugins-from-file-modal! plugins))
(defmethod handle :go/install-plugin-from-github [[_]]
(shui/dialog-open!
(plugin/install-from-github-release-container)))
(defmethod handle :go/plugins-settings [[_ pid nav? title]]
(when pid
(state/set-state! :plugin/focused-settings pid)
(state/set-state! :plugin/navs-settings? (not (false? nav?)))
(plugin/open-focused-settings-modal! title)))
(defmethod handle :go/proxy-settings [[_ agent-opts]]
(shui/dialog-open!
(plugin/user-proxy-settings-container agent-opts)
{:id :https-proxy-panel :center? true :class "lg:max-w-2xl"}))
(defmethod handle :redirect-to-home [_]
(page-handler/create-today-journal!))
(defmethod handle :instrument [[_ {:keys [type payload] :as opts}]]
(when-not (empty? (dissoc opts :type :payload))
(js/console.error "instrument data-map should only contains [:type :payload]"))
@@ -557,57 +325,12 @@
(file-sync-restart!))))
(state/pub-event! [:graph/ready (state/get-current-repo)])))))
(defmethod handle :plugin/consume-updates [[_ id prev-pending? updated?]]
(let [downloading? (:plugin/updates-downloading? @state/state)
auto-checking? (plugin-handler/get-auto-checking?)]
(when-let [coming (and (not downloading?)
(get-in @state/state [:plugin/updates-coming id]))]
(let [error-code (:error-code coming)
error-code (if (= error-code (str :no-new-version)) nil error-code)
title (:title coming)]
(when (and prev-pending? (not auto-checking?))
(if-not error-code
(plugin/set-updates-sub-content! (str title "...") 0)
(notification/show!
(str "[Checked]<" title "> " error-code) :error)))))
(if (and updated? downloading?)
;; try to start consume downloading item
(if-let [next-coming (state/get-next-selected-coming-update)]
(plugin-handler/check-or-update-marketplace-plugin!
(assoc next-coming :only-check false :error-code nil)
(fn [^js e] (js/console.error "[Download Err]" next-coming e)))
(plugin-handler/close-updates-downloading))
;; try to start consume pending item
(if-let [next-pending (second (first (:plugin/updates-pending @state/state)))]
(do
(println "Updates: take next pending - " (:id next-pending))
(js/setTimeout
#(plugin-handler/check-or-update-marketplace-plugin!
(assoc next-pending :only-check true :auto-check auto-checking? :error-code nil)
(fn [^js e]
(notification/show! (.toString e) :error)
(js/console.error "[Check Err]" next-pending e))) 500))
;; try to open waiting updates list
(do (when (and prev-pending? (not auto-checking?)
(seq (state/all-available-coming-updates)))
(plugin/open-waiting-updates-modal!))
(plugin-handler/set-auto-checking! false))))))
(defmethod handle :plugin/hook-db-tx [[_ {:keys [blocks tx-data] :as payload}]]
(when-let [payload (and (seq blocks)
(merge payload {:tx-data (map #(into [] %) tx-data)}))]
(plugin-handler/hook-plugin-db :changed payload)
(plugin-handler/hook-plugin-block-changes payload)))
(defmethod handle :plugin/loader-perf-tip [[_ {:keys [^js o _s _e]}]]
(when-let [opts (.-options o)]
(notification/show!
(plugin/perf-tip-content (.-id o) (.-name opts) (.-url opts))
:warning false (.-id o))))
(defmethod handle :mobile-file-watcher/changed [[_ ^js event]]
(let [type (.-event event)
payload (js->clj event :keywordize-keys true)]
@@ -621,51 +344,6 @@
(defmethod handle :shortcut/refresh [[_]]
(st/refresh!))
(defn- refresh-cb []
(page-handler/create-today-journal!)
(file-sync-restart!))
(defmethod handle :graph/ask-for-re-fresh [_]
(shui/dialog-open!
[:div {:style {:max-width 700}}
[:p (t :sync-from-local-changes-detected)]
[:div.flex.justify-end
(ui/button
(t :yes)
:autoFocus "on"
:class "ui__modal-enter"
:on-click (fn []
(shui/dialog-close!)
(nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))
(defmethod handle :sync/create-remote-graph [[_ current-repo]]
(let [graph-name (js/decodeURI (util/node-path.basename current-repo))]
(async/go
(async/<! (sync/<sync-stop))
(state/set-state! [:ui/loading? :graph/create-remote?] true)
(when-let [GraphUUID (get (async/<! (file-sync-handler/create-graph graph-name)) 2)]
(async/<! (sync/<sync-start))
(state/set-state! [:ui/loading? :graph/create-remote?] false)
;; update existing repo
(state/set-repos! (map (fn [r]
(if (= (:url r) current-repo)
(assoc r
:GraphUUID GraphUUID
:GraphName graph-name
:remote? true)
r))
(state/get-repos)))))))
(defmethod handle :modal/remote-encryption-input-pw-dialog [[_ repo-url remote-graph-info type opts]]
(shui/dialog-open!
(encryption/input-password
repo-url nil (merge
(assoc remote-graph-info
:type (or type :create-pwd-remote)
:repo repo-url)
opts))
{:center? true :close-btn? false :close-backdrop? false}))
(defmethod handle :journal/insert-template [[_ page-name]]
(let [page-name (util/page-name-sanity-lc page-name)]
(when-let [page (db/get-page page-name)]
@@ -682,49 +360,6 @@
(when-let [id (:block/uuid block)]
(editor-handler/set-heading! id heading)))
(defmethod handle :file-sync-graph/restore-file [[_ graph page-entity content]]
(when (db/get-db graph)
(let [file (:block/file page-entity)]
(when-let [path (:file/path file)]
(when (and (not= content (:file/content file))
(:file/content file))
(sync/add-new-version-file graph path (:file/content file)))
(p/let [_ (file-handler/alter-file graph
path
content
{:re-render-root? true
:skip-compare? true})]
(state/close-modal!)
(route-handler/redirect! {:to :page
:path-params {:name (:block/name page-entity)}}))))))
(defmethod handle :whiteboard/onboarding [[_ opts]]
(shui/dialog-open!
(fn [{:keys [close]}] (whiteboard/onboarding-welcome close))
(merge {:close-btn? false
:center? true
:close-backdrop? false} opts)))
(defmethod handle :file-sync/onboarding-tip [[_ type opts]]
(let [type (keyword type)]
(when-not (config/db-based-graph? (state/get-current-repo))
(shui/dialog-open!
(file-sync/make-onboarding-panel type)
(merge {:close-btn? false
:center? true
:close-backdrop? (not= type :welcome)} opts)))))
(defmethod handle :file-sync/maybe-onboarding-show [[_ type]]
(file-sync/maybe-onboarding-show type))
(defmethod handle :file-sync/storage-exceed-limit [[_]]
(notification/show! "file sync storage exceed limit" :warning false)
(file-sync-stop!))
(defmethod handle :file-sync/graph-count-exceed-limit [[_]]
(notification/show! "file sync graph count exceed limit" :warning false)
(file-sync-stop!))
(defmethod handle :graph/restored [[_ graph]]
(when graph (assets-handler/ensure-assets-dir! graph))
(mobile/init!)
@@ -741,143 +376,12 @@
(route-handler/redirect! {:to :page
:path-params {:name link}}))
(defmethod handle :graph/dir-gone [[_ dir]]
(state/pub-event! [:notification/show
{:content (str "The directory " dir " has been renamed or deleted, the editor will be disabled for this graph, you can unlink the graph.")
:status :error
:clear? false}])
(state/update-state! :file/unlinked-dirs (fn [dirs] (conj dirs dir))))
(defmethod handle :graph/dir-back [[_ repo dir]]
(when (contains? (:file/unlinked-dirs @state/state) dir)
(notification/clear-all!)
(state/pub-event! [:notification/show
{:content (str "The directory " dir " has been back, you can edit your graph now.")
:status :success
:clear? true}])
(state/update-state! :file/unlinked-dirs (fn [dirs] (disj dirs dir)))
(when (= dir (config/get-repo-dir repo))
(fs/watch-dir! dir))))
(defmethod handle :ui/notify-skipped-downloading-files [[_ paths]]
(notification/show!
[:div
[:div.mb-4
[:div.font-semibold.mb-4.text-xl "It seems that some of your filenames are in the outdated format."]
[:p
"The files below that have reserved characters can't be saved on this device."]
[:div.overflow-y-auto.max-h-96
[:ol.my-2
(for [path paths]
[:li path])]]
[:div
[:p
"Check " [:a {:href "https://docs.logseq.com/#/page/logseq%20file%20and%20folder%20naming%20rules"
:target "_blank"}
"Logseq file and folder naming rules"]
" for more details."]]]]
:warning
false))
(defmethod handle :graph/setup-a-repo [[_ opts]]
(let [opts' (merge {:picked-root-fn #(state/close-modal!)
:native-icloud? (not (string/blank? (state/get-icloud-container-root-url)))
:logged? (user-handler/logged-in?)} opts)]
(if (mobile-util/native-ios?)
(shui/dialog-open!
#(graph-picker/graph-picker-cp opts')
{:label "graph-setup"})
(page-handler/ls-dir-files! st/refresh! opts'))))
(defmethod handle :graph/new-db-graph [[_ _opts]]
(shui/dialog-open!
repo/new-db-graph
{:id :new-db-graph
:title [:h2 "Create a new graph"]
:style {:max-width "500px"}}))
(defmethod handle :dialog-select/graph-open []
(select/dialog-select! :graph-open))
(defmethod handle :dialog-select/graph-remove []
(select/dialog-select! :graph-remove))
(defmethod handle :dialog-select/db-graph-replace []
(select/dialog-select! :db-graph-replace))
(defmethod handle :graph/save-db-to-disk [[_ _opts]]
(persist-db/export-current-graph! {:succ-notification? true}))
(defmethod handle :class/configure [[_ page]]
(shui/dialog-open!
#(block/block-container {} page)
{:label "page-configure"
:align :top}))
(defmethod handle :file/alter [[_ repo path content]]
(p/let [_ (file-handler/alter-file repo path content {:from-disk? true})]
(ui-handler/re-render-root!)))
(defmethod handle :ui/re-render-root [[_]]
(ui-handler/re-render-root!))
(rum/defcs file-id-conflict-item <
(rum/local false ::resolved?)
[state repo file data]
(let [resolved? (::resolved? state)
id (last (:assertion data))]
[:li {:key file}
[:div
[:a {:on-click #(js/window.apis.openPath file)} file]
(if @resolved?
[:div.flex.flex-row.items-center
(ui/icon "circle-check" {:style {:font-size 20}})
[:div.ml-1 "Resolved"]]
[:div
[:p
(str "It seems that another whiteboard file already has the ID \"" id
"\". You can fix it by changing the ID in this file with another UUID.")]
[:p
"Or, let me"
(ui/button "Fix"
:on-click (fn []
(let [dir (config/get-repo-dir repo)]
(p/let [content (fs/read-file dir file)]
(let [new-content (string/replace content (str id) (str (random-uuid)))]
(p/let [_ (fs/write-file! repo
dir
file
new-content
{})]
(reset! resolved? true))))))
:class "inline mx-1")
"it."]])]]))
(defmethod handle :file/parse-and-load-error [[_ repo parse-errors]]
(state/pub-event! [:notification/show
{:content
[:div
[:h2.title "Oops. These files failed to import to your graph:"]
[:ol.my-2
(for [[file error] parse-errors]
(let [data (ex-data error)]
(cond
(and (common-config/whiteboard? file)
(= :transact/upsert (:error data))
(uuid? (last (:assertion data))))
(rum/with-key (file-id-conflict-item repo file data) file)
:else
(do
(state/pub-event! [:capture-error {:error error
:payload {:type :file/parse-and-load-error}}])
[:li.my-1 {:key file}
[:a {:on-click #(js/window.apis.openPath file)} file]
[:p (.-message error)]]))))]
[:p "Don't forget to re-index your graph when all the conflicts are resolved."]]
:status :error}]))
(defmethod handle :run/cli-command [[_ command content]]
(when (and command (not (string/blank? content)))
(shell-handler/run-cli-command-wrapper! command content)))
@@ -930,70 +434,6 @@
(when-let [blocks (and block (db-model/get-block-immediate-children (state/get-current-repo) (:block/uuid block)))]
(editor-handler/toggle-blocks-as-own-order-list! blocks)))
(defn- editor-new-property [block target {:keys [selected-blocks] :as opts}]
(let [editing-block (state/get-edit-block)
pos (state/get-edit-pos)
edit-block-or-selected (cond
editing-block
[editing-block]
(seq selected-blocks)
selected-blocks
:else
(seq (keep #(db/entity [:block/uuid %]) (state/get-selection-block-ids))))
current-block (when-let [s (state/get-current-page)]
(when (util/uuid-string? s)
(db/entity [:block/uuid (uuid s)])))
blocks (or (when block [block])
edit-block-or-selected
(when current-block [current-block]))
opts' (cond-> opts
editing-block
(assoc :original-block editing-block
:edit-original-block
(fn [{:keys [editing-default-property?]}]
(when editing-block
(let [content (:block/title (db/entity (:db/id editing-block)))
esc? (= "Escape" (state/get-ui-last-key-code))
[content' pos] (cond
esc?
[nil pos]
(and (>= (count content) pos)
(>= pos 2)
(= (util/nth-safe content (dec pos))
(util/nth-safe content (- pos 2))
";"))
[(str (common-util/safe-subs content 0 (- pos 2))
(common-util/safe-subs content pos))
(- pos 2)]
:else
[nil pos])]
(when content'
(if editing-default-property?
(editor-handler/save-block! (state/get-current-repo) (:block/uuid editing-block) content')
(editor-handler/edit-block! editing-block (or pos :max)
(cond-> {}
content'
(assoc :custom-content content'))))))))))]
(when (seq blocks)
(let [target' (or target
(some-> (state/get-edit-input-id)
(gdom/getElement))
(first (state/get-selection-blocks)))]
(if target'
(shui/popup-show! target'
#(property-dialog/dialog blocks opts')
{:align "start"
:auto-focus? true})
(shui/dialog-open! #(property-dialog/dialog blocks opts')
{:id :property-dialog
:align "start"}))))))
(defmethod handle :editor/new-property [[_ {:keys [block target] :as opts}]]
(when-not config/publishing?
(p/do!
(editor-handler/save-current-block!)
(editor-new-property block target opts))))
(defmethod handle :editor/upsert-type-block [[_ {:keys [block type lang update-current-block?]}]]
(p/do!
(when-not update-current-block?
@@ -1026,25 +466,6 @@
(db/entity [:block/uuid (:block/uuid block)])))]
(js/setTimeout #(editor-handler/edit-block! block :max) 100)))))
(rum/defc multi-tabs-dialog
[]
(let [word (if (util/electron?) "window" "tab")]
[:div.flex.p-4.flex-col.gap-4.h-64
[:span.warning.text-lg
(util/format "Logseq doesn't support multiple %ss access to the same graph yet, please close this %s or switch to another graph."
word word)]
[:div.text-lg
[:p "Switch to another repo: "]
[:div.border.rounded.bg-gray-01.overflow-hidden.w-60
(repo/repos-dropdown {:on-click (fn [e]
(util/stop e)
(state/set-state! :error/multiple-tabs-access-opfs? false)
(shui/dialog-close!))})]]]))
(defmethod handle :show/multiple-tabs-error-dialog [_]
(state/set-state! :error/multiple-tabs-access-opfs? true)
(shui/dialog-open! multi-tabs-dialog))
(defmethod handle :rtc/sync-state [[_ state]]
(state/update-state! :rtc/state (fn [old] (merge old state))))
@@ -1075,24 +496,6 @@
(defmethod handle :editor/run-query-command [_]
(editor-handler/run-query-command!))
(defmethod handle :editor/show-action-bar []
(let [selection (state/get-selection-blocks)
first-visible-block (some #(when (util/el-visible-in-viewport? % true) %) selection)]
(when first-visible-block
(shui/popup-hide! :selection-action-bar)
(shui/popup-show!
first-visible-block
(fn []
(selection/action-bar))
{:id :selection-action-bar
:content-props {:side "top"
:class "!py-0 !px-0 !border-none"}
:auto-side? false
:align :start}))))
(defmethod handle :editor/hide-action-bar []
(shui/popup-hide! :selection-action-bar))
(defmethod handle :editor/load-blocks [[_ ids]]
(when (seq ids)
;; not using `<get-blocks` here becuase because we want to

View File

@@ -0,0 +1,402 @@
(ns frontend.handler.events.ui
"UI events"
(:require [clojure.core.async :as async]
[clojure.core.async.interop :refer [p->c]]
[frontend.components.block :as block]
[frontend.components.cmdk.core :as cmdk]
[frontend.components.file-sync :as file-sync]
[frontend.components.page :as component-page]
[frontend.components.plugins :as plugin]
[frontend.components.property.dialog :as property-dialog]
[frontend.components.repo :as repo]
[frontend.components.select :as select]
[frontend.components.selection :as selection]
[frontend.components.settings :as settings]
[frontend.components.shell :as shell]
[frontend.components.user.login :as login]
[frontend.components.whiteboard :as whiteboard]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.db :as db]
[frontend.extensions.fsrs :as fsrs]
[frontend.extensions.srs :as srs]
[frontend.fs.capacitor-fs :as capacitor-fs]
[frontend.fs.nfs :as nfs]
[frontend.fs.sync :as sync]
[frontend.handler.db-based.rtc :as rtc-handler]
[frontend.handler.editor :as editor-handler]
[frontend.handler.events :as events]
[frontend.handler.file-based.nfs :as nfs-handler]
[frontend.handler.file-sync :as file-sync-handler]
[frontend.handler.notification :as notification]
[frontend.handler.page :as page-handler]
[frontend.handler.plugin :as plugin-handler]
[frontend.handler.repo :as repo-handler]
[frontend.handler.route :as route-handler]
[frontend.handler.user :as user-handler]
[frontend.mobile.util :as mobile-util]
[frontend.modules.instrumentation.sentry :as sentry-event]
[frontend.state :as state]
[frontend.ui :as ui]
[frontend.util :as util]
[goog.dom :as gdom]
[logseq.common.util :as common-util]
[logseq.shui.ui :as shui]
[promesa.core :as p]
[rum.core :as rum]))
(defmethod events/handle :class/configure [[_ page]]
(shui/dialog-open!
#(block/block-container {} page)
{:label "page-configure"
:align :top}))
(defmethod events/handle :go/search [_]
(shui/dialog-open!
cmdk/cmdk-modal
{:id :ls-dialog-cmdk
:align :top
:content-props {:class "ls-dialog-cmdk"}
:close-btn? false}))
(defmethod events/handle :command/run [_]
(when (util/electron?)
(shui/dialog-open! shell/shell)))
(defmethod events/handle :notification/show [[_ {:keys [content status clear?]}]]
(notification/show! content status clear?))
(defmethod events/handle :command/run [_]
(when (util/electron?)
(shui/dialog-open! shell/shell)))
(defmethod events/handle :go/plugins [_]
(plugin/open-plugins-modal!))
(defmethod events/handle :go/plugins-waiting-lists [_]
(plugin/open-waiting-updates-modal!))
(defmethod events/handle :go/plugins-from-file [[_ plugins]]
(plugin/open-plugins-from-file-modal! plugins))
(defmethod events/handle :go/install-plugin-from-github [[_]]
(shui/dialog-open!
(plugin/install-from-github-release-container)))
(defmethod events/handle :go/plugins-settings [[_ pid nav? title]]
(when pid
(state/set-state! :plugin/focused-settings pid)
(state/set-state! :plugin/navs-settings? (not (false? nav?)))
(plugin/open-focused-settings-modal! title)))
(defmethod events/handle :go/proxy-settings [[_ agent-opts]]
(shui/dialog-open!
(plugin/user-proxy-settings-container agent-opts)
{:id :https-proxy-panel :center? true :class "lg:max-w-2xl"}))
(defmethod events/handle :redirect-to-home [_]
(page-handler/create-today-journal!))
(defmethod events/handle :page/show-delete-dialog [[_ selected-rows ok-handler]]
(shui/dialog-open!
(component-page/batch-delete-dialog
selected-rows false
ok-handler)))
(defn ask-permission
[repo]
(when
(and (not (util/electron?))
(not (mobile-util/native-platform?)))
(fn [{:keys [close]}]
[:div
;; TODO: fn translation with args
[:p
"Grant native filesystem permission for directory: "
[:b (config/get-local-dir repo)]]
(ui/button
(t :settings-permission/start-granting)
:class "ui__modal-enter"
:on-click (fn []
(nfs/check-directory-permission! repo)
(close)))])))
(defn get-local-repo
[]
(when-let [repo (state/get-current-repo)]
(when (config/local-file-based-graph? repo)
repo)))
(defmethod events/handle :modal/nfs-ask-permission []
(when-let [repo (get-local-repo)]
(some-> (ask-permission repo)
(shui/dialog-open! {:align :top}))))
(defmethod events/handle :modal/show-cards [[_ cards-id]]
(let [db-based? (config/db-based-graph? (state/get-current-repo))]
(shui/dialog-open!
(if db-based? (fn [] (fsrs/cards-view cards-id)) srs/global-cards)
{:id :srs
:label "flashcards__cp"})))
(defmethod events/handle :modal/show-instruction [_]
(shui/dialog-open!
capacitor-fs/instruction
{:id :instruction
:label "instruction__cp"}))
(defmethod events/handle :modal/show-themes-modal [[_ classic?]]
(if classic?
(plugin/open-select-theme!)
(route-handler/go-to-search! :themes)))
(defmethod events/handle :ui/toggle-appearance [_]
(let [popup-id "appearance_settings"]
(if (gdom/getElement popup-id)
(shui/popup-hide! popup-id)
(shui/popup-show!
(js/document.querySelector ".toolbar-dots-btn")
(fn []
(settings/appearance))
{:id popup-id
:align :end}))))
(defmethod events/handle :plugin/consume-updates [[_ id prev-pending? updated?]]
(let [downloading? (:plugin/updates-downloading? @state/state)
auto-checking? (plugin-handler/get-auto-checking?)]
(when-let [coming (and (not downloading?)
(get-in @state/state [:plugin/updates-coming id]))]
(let [error-code (:error-code coming)
error-code (if (= error-code (str :no-new-version)) nil error-code)
title (:title coming)]
(when (and prev-pending? (not auto-checking?))
(if-not error-code
(plugin/set-updates-sub-content! (str title "...") 0)
(notification/show!
(str "[Checked]<" title "> " error-code) :error)))))
(if (and updated? downloading?)
;; try to start consume downloading item
(if-let [next-coming (state/get-next-selected-coming-update)]
(plugin-handler/check-or-update-marketplace-plugin!
(assoc next-coming :only-check false :error-code nil)
(fn [^js e] (js/console.error "[Download Err]" next-coming e)))
(plugin-handler/close-updates-downloading))
;; try to start consume pending item
(if-let [next-pending (second (first (:plugin/updates-pending @state/state)))]
(do
(println "Updates: take next pending - " (:id next-pending))
(js/setTimeout
#(plugin-handler/check-or-update-marketplace-plugin!
(assoc next-pending :only-check true :auto-check auto-checking? :error-code nil)
(fn [^js e]
(notification/show! (.toString e) :error)
(js/console.error "[Check Err]" next-pending e))) 500))
;; try to open waiting updates list
(do (when (and prev-pending? (not auto-checking?)
(seq (state/all-available-coming-updates)))
(plugin/open-waiting-updates-modal!))
(plugin-handler/set-auto-checking! false))))))
(defmethod events/handle :plugin/loader-perf-tip [[_ {:keys [^js o _s _e]}]]
(when-let [opts (.-options o)]
(notification/show!
(plugin/perf-tip-content (.-id o) (.-name opts) (.-url opts))
:warning false (.-id o))))
(defn- refresh-cb []
(page-handler/create-today-journal!)
(events/file-sync-restart!))
(defmethod events/handle :graph/ask-for-re-fresh [_]
(shui/dialog-open!
[:div {:style {:max-width 700}}
[:p (t :sync-from-local-changes-detected)]
[:div.flex.justify-end
(ui/button
(t :yes)
:autoFocus "on"
:class "ui__modal-enter"
:on-click (fn []
(shui/dialog-close!)
(nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))
(defn- editor-new-property [block target {:keys [selected-blocks] :as opts}]
(let [editing-block (state/get-edit-block)
pos (state/get-edit-pos)
edit-block-or-selected (cond
editing-block
[editing-block]
(seq selected-blocks)
selected-blocks
:else
(seq (keep #(db/entity [:block/uuid %]) (state/get-selection-block-ids))))
current-block (when-let [s (state/get-current-page)]
(when (util/uuid-string? s)
(db/entity [:block/uuid (uuid s)])))
blocks (or (when block [block])
edit-block-or-selected
(when current-block [current-block]))
opts' (cond-> opts
editing-block
(assoc :original-block editing-block
:edit-original-block
(fn [{:keys [editing-default-property?]}]
(when editing-block
(let [content (:block/title (db/entity (:db/id editing-block)))
esc? (= "Escape" (state/get-ui-last-key-code))
[content' pos] (cond
esc?
[nil pos]
(and (>= (count content) pos)
(>= pos 2)
(= (util/nth-safe content (dec pos))
(util/nth-safe content (- pos 2))
";"))
[(str (common-util/safe-subs content 0 (- pos 2))
(common-util/safe-subs content pos))
(- pos 2)]
:else
[nil pos])]
(when content'
(if editing-default-property?
(editor-handler/save-block! (state/get-current-repo) (:block/uuid editing-block) content')
(editor-handler/edit-block! editing-block (or pos :max)
(cond-> {}
content'
(assoc :custom-content content'))))))))))]
(when (seq blocks)
(let [target' (or target
(some-> (state/get-edit-input-id)
(gdom/getElement))
(first (state/get-selection-blocks)))]
(if target'
(shui/popup-show! target'
#(property-dialog/dialog blocks opts')
{:align "start"
:auto-focus? true})
(shui/dialog-open! #(property-dialog/dialog blocks opts')
{:id :property-dialog
:align "start"}))))))
(defmethod events/handle :editor/new-property [[_ {:keys [block target] :as opts}]]
(when-not config/publishing?
(p/do!
(editor-handler/save-current-block!)
(editor-new-property block target opts))))
(defmethod events/handle :graph/new-db-graph [[_ _opts]]
(shui/dialog-open!
repo/new-db-graph
{:id :new-db-graph
:title [:h2 "Create a new graph"]
:style {:max-width "500px"}}))
(defmethod events/handle :dialog-select/graph-open []
(select/dialog-select! :graph-open))
(defmethod events/handle :dialog-select/graph-remove []
(select/dialog-select! :graph-remove))
(defmethod events/handle :dialog-select/db-graph-replace []
(select/dialog-select! :db-graph-replace))
(rum/defc multi-tabs-dialog
[]
(let [word (if (util/electron?) "window" "tab")]
[:div.flex.p-4.flex-col.gap-4.h-64
[:span.warning.text-lg
(util/format "Logseq doesn't support multiple %ss access to the same graph yet, please close this %s or switch to another graph."
word word)]
[:div.text-lg
[:p "Switch to another repo: "]
[:div.border.rounded.bg-gray-01.overflow-hidden.w-60
(repo/repos-dropdown {:on-click (fn [e]
(util/stop e)
(state/set-state! :error/multiple-tabs-access-opfs? false)
(shui/dialog-close!))})]]]))
(defmethod events/handle :show/multiple-tabs-error-dialog [_]
(state/set-state! :error/multiple-tabs-access-opfs? true)
(shui/dialog-open! multi-tabs-dialog))
(defmethod events/handle :editor/show-action-bar []
(let [selection (state/get-selection-blocks)
first-visible-block (some #(when (util/el-visible-in-viewport? % true) %) selection)]
(when first-visible-block
(shui/popup-hide! :selection-action-bar)
(shui/popup-show!
first-visible-block
(fn []
(selection/action-bar))
{:id :selection-action-bar
:content-props {:side "top"
:class "!py-0 !px-0 !border-none"}
:auto-side? false
:align :start}))))
(defmethod events/handle :editor/hide-action-bar []
(shui/popup-hide! :selection-action-bar))
(defmethod events/handle :user/logout [[_]]
(file-sync-handler/reset-session-graphs)
(sync/remove-all-pwd!)
(file-sync-handler/reset-user-state!)
(login/sign-out!))
(defmethod events/handle :user/login [[_ host-ui?]]
(if (or host-ui? (not util/electron?))
(js/window.open config/LOGIN-URL)
(if (mobile-util/native-platform?)
(route-handler/redirect! {:to :user-login})
(login/open-login-modal!))))
(defmethod events/handle :whiteboard/onboarding [[_ opts]]
(shui/dialog-open!
(fn [{:keys [close]}] (whiteboard/onboarding-welcome close))
(merge {:close-btn? false
:center? true
:close-backdrop? false} opts)))
(defn- enable-beta-features!
[]
(when-not (false? (state/enable-sync?)) ; user turns it off
(file-sync-handler/set-sync-enabled! true)))
;; TODO: separate rtc and file-based implementation
(defmethod events/handle :user/fetch-info-and-graphs [[_]]
(state/set-state! [:ui/loading? :login] false)
(async/go
(let [result (async/<! (sync/<user-info sync/remoteapi))]
(cond
(instance? ExceptionInfo result)
nil
(map? result)
(do
(state/set-user-info! result)
(when-let [uid (user-handler/user-uuid)]
(sentry-event/set-user! uid))
(let [status (if (user-handler/alpha-or-beta-user?) :welcome :unavailable)]
(when (and (= status :welcome) (user-handler/logged-in?))
(enable-beta-features!)
(async/<! (p->c (rtc-handler/<get-remote-graphs)))
(async/<! (file-sync-handler/load-session-graphs))
(p/let [repos (repo-handler/refresh-repos!)]
(when-let [repo (state/get-current-repo)]
(when (some #(and (= (:url %) repo)
(vector? (:sync-meta %))
(util/uuid-string? (first (:sync-meta %)))
(util/uuid-string? (second (:sync-meta %)))) repos)
(sync/<sync-start)))))
(file-sync/maybe-onboarding-show status)))))))
(defmethod events/handle :file-sync/onboarding-tip [[_ type opts]]
(let [type (keyword type)]
(when-not (config/db-based-graph? (state/get-current-repo))
(shui/dialog-open!
(file-sync/make-onboarding-panel type)
(merge {:close-btn? false
:center? true
:close-backdrop? (not= type :welcome)} opts)))))

View File

@@ -2,20 +2,37 @@
"Events that are only for file graphs"
(:require [clojure.core.async :as async]
[clojure.set :as set]
[clojure.string :as string]
[frontend.components.diff :as diff]
[frontend.components.encryption :as encryption]
[frontend.components.file-based.git :as git-component]
[frontend.components.file-sync :as file-sync]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.db :as db]
[frontend.handler.events :as events]
[frontend.handler.page :as page-handler]
[frontend.handler.repo :as repo-handler]
[frontend.handler.file-based.nfs :as nfs-handler]
[frontend.handler.common :as common-handler]
[frontend.handler.property :as property-handler]
[frontend.fs :as fs]
[frontend.fs.sync :as sync]
[frontend.handler.common :as common-handler]
[frontend.handler.events :as events]
[frontend.handler.file-based.file :as file-handler]
[frontend.handler.file-based.nfs :as nfs-handler]
[frontend.handler.file-sync :as file-sync-handler]
[frontend.handler.notification :as notification]
[frontend.handler.page :as page-handler]
[frontend.handler.property :as property-handler]
[frontend.handler.repo :as repo-handler]
[frontend.handler.route :as route-handler]
[frontend.handler.ui :as ui-handler]
[frontend.handler.user :as user-handler]
[frontend.mobile.graph-picker :as graph-picker]
[frontend.mobile.util :as mobile-util]
[frontend.modules.shortcut.core :as st]
[frontend.state :as state]
[frontend.ui :as ui]
[frontend.util :as util]
[frontend.config :as config]
[logseq.common.config :as common-config]
[logseq.shui.ui :as shui]
[promesa.core :as p]
[rum.core :as rum]))
(defmethod events/handle :graph/ask-for-re-index [[_ *multiple-windows? ui]]
@@ -23,32 +40,32 @@
;; ui - custom message to show on asking for re-index
(if (and (util/atom? *multiple-windows?) @*multiple-windows?)
(shui/dialog-open!
[:div
(when (not (nil? ui)) ui)
[:p (t :re-index-multiple-windows-warning)]])
[:div
(when (not (nil? ui)) ui)
[:p (t :re-index-multiple-windows-warning)]])
(shui/dialog-open!
[:div {:style {:max-width 700}}
(when (not (nil? ui)) ui)
[:p (t :re-index-discard-unsaved-changes-warning)]
[:div.flex.justify-end.pt-2
(ui/button
(t :yes)
:autoFocus "on"
:class "ui__modal-enter"
:on-click (fn []
(shui/dialog-close!)
(state/pub-event! [:graph/re-index])))]])))
[:div {:style {:max-width 700}}
(when (not (nil? ui)) ui)
[:p (t :re-index-discard-unsaved-changes-warning)]
[:div.flex.justify-end.pt-2
(ui/button
(t :yes)
:autoFocus "on"
:class "ui__modal-enter"
:on-click (fn []
(shui/dialog-close!)
(state/pub-event! [:graph/re-index])))]])))
(defmethod events/handle :graph/re-index [[_]]
;; Ensure the graph only has ONE window instance
(when (config/local-file-based-graph? (state/get-current-repo))
(async/go
(async/<! (sync/<sync-stop))
(repo-handler/re-index!
nfs-handler/rebuild-index!
#(do (page-handler/create-today-journal!)
(events/file-sync-restart!))))))
(async/<! (sync/<sync-stop))
(repo-handler/re-index!
nfs-handler/rebuild-index!
#(do (page-handler/create-today-journal!)
(events/file-sync-restart!))))))
(defn set-block-query-properties!
[block-id all-properties key add?]
@@ -118,5 +135,221 @@
(set all-properties))
shown-properties (set/intersection (set all-properties) shown-properties)]
(shui/dialog-open!
(query-properties-settings block shown-properties all-properties)
{})))
(query-properties-settings block shown-properties all-properties)
{})))
(defmethod events/handle :modal/set-git-username-and-email [[_ _content]]
(shui/dialog-open! git-component/set-git-username-and-email))
(defmethod events/handle :file/not-matched-from-disk [[_ path disk-content db-content]]
(when-let [repo (state/get-current-repo)]
(shui/dialog-open!
#(diff/local-file repo path disk-content db-content)
{:label "diff__cp"})))
(defmethod events/handle :modal/display-file-version-selector [[_ versions path get-content]]
(shui/dialog-open!
#(git-component/file-version-selector versions path get-content)))
(defmethod events/handle :modal/remote-encryption-input-pw-dialog [[_ repo-url remote-graph-info type opts]]
(shui/dialog-open!
(encryption/input-password
repo-url nil (merge
(assoc remote-graph-info
:type (or type :create-pwd-remote)
:repo repo-url)
opts))
{:center? true :close-btn? false :close-backdrop? false}))
(defmethod events/handle :graph/pull-down-remote-graph [[_ graph dir-name]]
(if (mobile-util/native-ios?)
(when-let [graph-name (or dir-name (:GraphName graph))]
(let [graph-name (util/safe-sanitize-file-name graph-name)]
(if (string/blank? graph-name)
(notification/show! "Illegal graph folder name.")
;; Create graph directory under Logseq document folder (local)
(when-let [root (state/get-local-container-root-url)]
(let [graph-path (graph-picker/validate-graph-dirname root graph-name)]
(->
(p/let [exists? (fs/dir-exists? graph-path)]
(let [overwrite? (if exists?
(js/confirm (str "There's already a directory with the name \"" graph-name "\", do you want to overwrite it? Make sure to backup it first if you're not sure about it."))
true)]
(if overwrite?
(p/let [_ (fs/mkdir-if-not-exists graph-path)]
(nfs-handler/ls-dir-files-with-path!
graph-path
{:ok-handler (fn []
(file-sync-handler/init-remote-graph graph-path graph)
(js/setTimeout (fn [] (repo-handler/refresh-repos!)) 200))}))
(let [graph-name (-> (js/prompt "Please specify a new directory name to download the graph:")
str
string/trim)]
(when-not (string/blank? graph-name)
(state/pub-event! [:graph/pull-down-remote-graph graph graph-name]))))))
(p/catch (fn [^js e]
(notification/show! (str e) :error)
(js/console.error e)))))))))
(when (:GraphName graph)
(shui/dialog-open!
(file-sync/pick-dest-to-sync-panel graph)))))
(defmethod events/handle :graph/pick-page-histories [[_ graph-uuid page-name]]
(shui/dialog-open!
(file-sync/pick-page-histories-panel graph-uuid page-name)
{:id :page-histories :label "modal-page-histories"}))
(defmethod events/handle :file-sync/maybe-onboarding-show [[_ type]]
(file-sync/maybe-onboarding-show type))
(defmethod events/handle :file-sync/storage-exceed-limit [[_]]
(notification/show! "file sync storage exceed limit" :warning false)
(events/file-sync-stop!))
(defmethod events/handle :file-sync/graph-count-exceed-limit [[_]]
(notification/show! "file sync graph count exceed limit" :warning false)
(events/file-sync-stop!))
(defmethod events/handle :graph/dir-gone [[_ dir]]
(state/pub-event! [:notification/show
{:content (str "The directory " dir " has been renamed or deleted, the editor will be disabled for this graph, you can unlink the graph.")
:status :error
:clear? false}])
(state/update-state! :file/unlinked-dirs (fn [dirs] (conj dirs dir))))
(defmethod events/handle :graph/dir-back [[_ repo dir]]
(when (contains? (:file/unlinked-dirs @state/state) dir)
(notification/clear-all!)
(state/pub-event! [:notification/show
{:content (str "The directory " dir " has been back, you can edit your graph now.")
:status :success
:clear? true}])
(state/update-state! :file/unlinked-dirs (fn [dirs] (disj dirs dir)))
(when (= dir (config/get-repo-dir repo))
(fs/watch-dir! dir))))
(defmethod events/handle :ui/notify-skipped-downloading-files [[_ paths]]
(notification/show!
[:div
[:div.mb-4
[:div.font-semibold.mb-4.text-xl "It seems that some of your filenames are in the outdated format."]
[:p
"The files below that have reserved characters can't be saved on this device."]
[:div.overflow-y-auto.max-h-96
[:ol.my-2
(for [path paths]
[:li path])]]
[:div
[:p
"Check " [:a {:href "https://docs.logseq.com/#/page/logseq%20file%20and%20folder%20naming%20rules"
:target "_blank"}
"Logseq file and folder naming rules"]
" for more details."]]]]
:warning
false))
(defmethod events/handle :graph/setup-a-repo [[_ opts]]
(let [opts' (merge {:picked-root-fn #(state/close-modal!)
:native-icloud? (not (string/blank? (state/get-icloud-container-root-url)))
:logged? (user-handler/logged-in?)} opts)]
(if (mobile-util/native-ios?)
(shui/dialog-open!
#(graph-picker/graph-picker-cp opts')
{:label "graph-setup"})
(page-handler/ls-dir-files! st/refresh! opts'))))
(defmethod events/handle :file/alter [[_ repo path content]]
(p/let [_ (file-handler/alter-file repo path content {:from-disk? true})]
(ui-handler/re-render-root!)))
(rum/defcs file-id-conflict-item <
(rum/local false ::resolved?)
[state repo file data]
(let [resolved? (::resolved? state)
id (last (:assertion data))]
[:li {:key file}
[:div
[:a {:on-click #(js/window.apis.openPath file)} file]
(if @resolved?
[:div.flex.flex-row.items-center
(ui/icon "circle-check" {:style {:font-size 20}})
[:div.ml-1 "Resolved"]]
[:div
[:p
(str "It seems that another whiteboard file already has the ID \"" id
"\". You can fix it by changing the ID in this file with another UUID.")]
[:p
"Or, let me"
(ui/button "Fix"
:on-click (fn []
(let [dir (config/get-repo-dir repo)]
(p/let [content (fs/read-file dir file)]
(let [new-content (string/replace content (str id) (str (random-uuid)))]
(p/let [_ (fs/write-file! repo
dir
file
new-content
{})]
(reset! resolved? true))))))
:class "inline mx-1")
"it."]])]]))
(defmethod events/handle :file/parse-and-load-error [[_ repo parse-errors]]
(state/pub-event! [:notification/show
{:content
[:div
[:h2.title "Oops. These files failed to import to your graph:"]
[:ol.my-2
(for [[file error] parse-errors]
(let [data (ex-data error)]
(cond
(and (common-config/whiteboard? file)
(= :transact/upsert (:error data))
(uuid? (last (:assertion data))))
(rum/with-key (file-id-conflict-item repo file data) file)
:else
(do
(state/pub-event! [:capture-error {:error error
:payload {:type :file/parse-and-load-error}}])
[:li.my-1 {:key file}
[:a {:on-click #(js/window.apis.openPath file)} file]
[:p (.-message error)]]))))]
[:p "Don't forget to re-index your graph when all the conflicts are resolved."]]
:status :error}]))
(defmethod events/handle :file-sync-graph/restore-file [[_ graph page-entity content]]
(when (db/get-db graph)
(let [file (:block/file page-entity)]
(when-let [path (:file/path file)]
(when (and (not= content (:file/content file))
(:file/content file))
(sync/add-new-version-file graph path (:file/content file)))
(p/let [_ (file-handler/alter-file graph
path
content
{:re-render-root? true
:skip-compare? true})]
(state/close-modal!)
(route-handler/redirect! {:to :page
:path-params {:name (:block/name page-entity)}}))))))
(defmethod events/handle :sync/create-remote-graph [[_ current-repo]]
(let [graph-name (js/decodeURI (util/node-path.basename current-repo))]
(async/go
(async/<! (sync/<sync-stop))
(state/set-state! [:ui/loading? :graph/create-remote?] true)
(when-let [GraphUUID (get (async/<! (file-sync-handler/create-graph graph-name)) 2)]
(async/<! (sync/<sync-start))
(state/set-state! [:ui/loading? :graph/create-remote?] false)
;; update existing repo
(state/set-repos! (map (fn [r]
(if (= (:url r) current-repo)
(assoc r
:GraphUUID GraphUUID
:GraphName graph-name
:remote? true)
r))
(state/get-repos)))))))

View File

@@ -1,79 +0,0 @@
.tippy-popper {
max-width: 800px;
}
.tippy-popper[x-placement^=top] [x-arrow],
.tippy-popper[x-placement^=top] [x-arrow].arrow-small,
.tippy-popper[x-placement^=top] [x-arrow].arrow-big {
border-top-color: var(--ls-tertiary-background-color);
}
.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-circle],
.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-circle],
.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-circle],
.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-circle],
.tippy-popper .tippy-tooltip.transparent-theme {
background-color: var(--ls-secondary-background-color);
}
.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-arrow],
.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-arrow].arrow-small,
.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-arrow].arrow-big {
border-top-color: var(--ls-secondary-background-color);
}
.tippy-popper[x-placement^=bottom] [x-arrow],
.tippy-popper[x-placement^=bottom] [x-arrow].arrow-small,
.tippy-popper[x-placement^=bottom] [x-arrow].arrow-big {
border-bottom-color: var(--ls-tertiary-background-color);
}
.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-arrow],
.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-arrow].arrow-small,
.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-arrow].arrow-big {
border-bottom-color: var(--ls-secondary-background-color);
}
.tippy-popper[x-placement^=left] [x-arrow],
.tippy-popper[x-placement^=left] [x-arrow].arrow-small,
.tippy-popper[x-placement^=left] [x-arrow].arrow-big {
border-left-color: var(--ls-tertiary-background-color);
}
.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-arrow],
.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-arrow].arrow-small,
.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-arrow].arrow-big {
border-left-color: var(--ls-secondary-background-color);
}
.tippy-popper[x-placement^=right] [x-arrow],
.tippy-popper[x-placement^=right] [x-arrow].arrow-small,
.tippy-popper[x-placement^=right] [x-arrow].arrow-big {
border-right-color: var(--ls-tertiary-background-color);
}
.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-arrow],
.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-arrow].arrow-small,
.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-arrow].arrow-big {
border-right-color: var(--ls-tertiary-background-color);
}
.tippy-tooltip {
@apply shadow border border-gray-07 dark:border-gray-05 px-2 py-1;
will-change: auto;
color: var(--ls-primary-text-color, hsl(var(--foreground)));
background-color: var(--lx-gray-03, var(--ls-tertiary-background-color, var(--rx-gray-03)));
}
.tippy-tooltip [x-circle] {
will-change: auto;
}
.tippy-popper .tippy-tooltip.customized-theme * {
text-align: left;
}
.tippy-popper .tippy-tooltip.monospace-theme {
font-family: 'Fira Code', Monaco, Menlo, Consolas, 'COURIER NEW', monospace;
}

View File

@@ -5,7 +5,6 @@
["emoji-mart" :as emoji-mart]
["react-intersection-observer" :as react-intersection-observer]
["react-textarea-autosize" :as TextareaAutosize]
["react-tippy" :as react-tippy]
["react-transition-group" :refer [CSSTransition TransitionGroup]]
["react-virtuoso" :refer [Virtuoso VirtuosoGrid]]
[cljs-bean.core :as bean]
@@ -40,6 +39,7 @@
[rum.core :as rum]))
(declare icon)
(declare tooltip)
(defonce transition-group (r/adapt-class TransitionGroup))
(defonce css-transition (r/adapt-class CSSTransition))
@@ -47,7 +47,6 @@
(defonce virtualized-list (r/adapt-class Virtuoso))
(defonce virtualized-grid (r/adapt-class VirtuosoGrid))
(def Tippy (r/adapt-class (gobj/get react-tippy "Tooltip")))
(def ReactTweetEmbed (r/adapt-class react-tweet-embed))
(def useInView (gobj/get react-intersection-observer "useInView"))
(defonce _emoji-init-data ((gobj/get emoji-mart "init") #js {:data emoji-data}))
@@ -791,45 +790,6 @@
:checked selected}]
label])]))
(rum/defcs tippy < rum/static
(rum/local false ::mounted?)
[state {:keys [fixed-position? open? html] :as opts} child]
(let [*mounted? (::mounted? state)
manual (not= open? nil)
open? (if manual open? @*mounted?)
disabled? (not (state/enable-tooltip?))]
(Tippy (->
(merge {:arrow true
:sticky true
:delay 600
:theme "customized"
:disabled disabled?
:unmountHTMLWhenHide true
:open (if disabled? false open?)
:trigger (if manual "manual" "mouseenter focus")
;; See https://github.com/tvkhoa/react-tippy/issues/13
:popperOptions {:modifiers {:flip {:enabled (not fixed-position?)}
:hide {:enabled false}
:preventOverflow {:enabled false}}}
:onShow #(when-not (or (state/editing?)
@(:ui/scrolling? @state/state))
(reset! *mounted? true))
:onHide #(reset! *mounted? false)}
opts)
(assoc :html (or
(when open?
(try
(when html
(if (fn? html)
(html)
[:div.px-2.py-1
html]))
(catch :default e
(log/error :exception e)
[:div])))
[:div {:key "tippy"} ""])))
(rum/fragment {:key "tippy-children"} child))))
(rum/defcs slider < rum/reactive
{:init (fn [state]
(assoc state ::value (atom (first (:rum/args state)))))}
@@ -911,18 +871,13 @@
(rum/defc with-shortcut < rum/reactive
< {:key-fn (fn [key pos] (str "shortcut-" key pos))}
[shortcut-key position content]
[shortcut-key _position content]
(let [shortcut-tooltip? (state/sub :ui/shortcut-tooltip?)
enabled-tooltip? (state/enable-tooltip?)]
(if (and enabled-tooltip? shortcut-tooltip?)
(tippy
{:html [:div.text-sm.font-medium (keyboard-shortcut-from-config shortcut-key)]
:interactive true
:position position
:theme "monospace"
:delay [1000, 100]
:arrow true}
content)
(tooltip content
[:div.text-sm.font-medium (keyboard-shortcut-from-config shortcut-key)]
{:trigger-props {:as-child true}})
content)))
(rum/defc progress-bar
@@ -1020,11 +975,14 @@
:small? true)]]))
(rum/defc tooltip
[trigger tooltip-content & {:keys [trigger-props]}]
[trigger tooltip-content & {:keys [portal? root-props trigger-props content-props]}]
(shui/tooltip-provider
(shui/tooltip
(shui/tooltip-trigger trigger-props trigger)
(shui/tooltip-content tooltip-content))))
(shui/tooltip root-props
(shui/tooltip-trigger (merge {:as-child true} trigger-props) trigger)
(if (not (false? portal?))
(shui/tooltip-portal
(shui/tooltip-content content-props tooltip-content))
(shui/tooltip-content content-props tooltip-content)))))
(rum/defc DelDateButton
[on-delete]

View File

@@ -0,0 +1,23 @@
(ns frontend.worker.db.fix
"fix db"
(:require [datascript.core :as d]
[logseq.db.sqlite.util :as sqlite-util]))
(defn check-and-fix-schema!
[repo conn]
(let [schema (sqlite-util/get-schema repo)
db-schema (:schema @conn)
diffs (->> (keep (fn [[k v]]
(let [schema-v (-> (get db-schema k)
(dissoc :db/ident))
schema-v' (cond-> schema-v
(= (:db/cardinality schema-v) :db.cardinality/one)
(dissoc :db/cardinality)
(and (:db/index schema-v)
(nil? (:db/index v)))
(dissoc :db/index))]
(when-not (or (= v schema-v') (= k :db/ident))
(assoc v :db/ident k))))
schema))]
(when (seq diffs)
(d/transact! conn diffs))))

View File

@@ -13,6 +13,7 @@
[frontend.common.graph-view :as graph-view]
[frontend.common.thread-api :as thread-api :refer [def-thread-api]]
[frontend.worker.db-listener :as db-listener]
[frontend.worker.db.fix :as db-fix]
[frontend.worker.db.migrate :as db-migrate]
[frontend.worker.db.validate :as worker-db-validate]
[frontend.worker.export :as worker-export]
@@ -332,6 +333,7 @@
(search/create-tables-and-triggers! search-db)
(let [schema (sqlite-util/get-schema repo)
conn (sqlite-common-db/get-storage-conn storage schema)
_ (db-fix/check-and-fix-schema! repo conn)
_ (when datoms
(let [data (map (fn [datom]
[:db/add (:e datom) (:a datom) (:v datom)]) datoms)]

View File

@@ -17,7 +17,6 @@
@import "codemirror/lib/codemirror.css";
@import "codemirror/theme/solarized.css";
@import "codemirror/addon/hint/show-hint.css";
@import "react-tippy/dist/tippy.css";
@import "pdfjs-dist/web/pdf_viewer.css";
@import "resources/css/tabler-extension.css";
@import "resources/css/codemirror.lsradix.css";

View File

@@ -6584,11 +6584,6 @@ plugin-error@^2.0.1:
dependencies:
ansi-colors "^1.0.1"
popper.js@^1.11.1:
version "1.16.1"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
@@ -7249,13 +7244,6 @@ react-textarea-autosize@8.3.3:
use-composed-ref "^1.0.0"
use-latest "^1.0.0"
react-tippy@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/react-tippy/-/react-tippy-1.4.0.tgz#e8a8b4085ec985e5c94fe128918b733b588a1465"
integrity sha512-r/hM5XK9Ztr2ZY7IWKuRmISTlUPS/R6ddz6PO2EuxCgW+4JBcGZRPU06XcVPRDCOIiio8ryBQFrXMhFMhsuaHA==
dependencies:
popper.js "^1.11.1"
react-transition-group@4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683"