mirror of
https://github.com/logseq/logseq.git
synced 2026-06-01 19:01:22 +00:00
Merge branch 'master' into refactor/vite-migration
This commit is contained in:
@@ -81,6 +81,7 @@
|
||||
[logseq.graph-parser.mldoc :as gp-mldoc]
|
||||
[logseq.graph-parser.text :as text]
|
||||
[logseq.outliner.property :as outliner-property]
|
||||
[logseq.sdk.utils :as sdk-util]
|
||||
[logseq.shui.dialog.core :as shui-dialog]
|
||||
[logseq.shui.hooks :as hooks]
|
||||
[logseq.shui.ui :as shui]
|
||||
@@ -3252,6 +3253,134 @@
|
||||
[block* result]
|
||||
[nil result])))
|
||||
|
||||
(defn- build-block-renderer-children-props
|
||||
[block]
|
||||
(when-let [block-uuid (:block/uuid block)]
|
||||
(let [repo (state/get-current-repo)
|
||||
blocks (some->> (db/get-block-and-children repo block-uuid)
|
||||
(map (fn [child-block]
|
||||
(dissoc (db/pull (:db/id child-block)) :block.temp/load-status))))]
|
||||
(or (some-> blocks
|
||||
(tree/blocks->vec-tree block-uuid)
|
||||
first
|
||||
:block/children
|
||||
sdk-util/normalize-keyword-for-json)
|
||||
[]))))
|
||||
|
||||
(defn- build-block-renderer-match-context
|
||||
([block]
|
||||
(build-block-renderer-match-context block false))
|
||||
([block include-children?]
|
||||
(let [uuid-str (some-> (:block/uuid block) str)
|
||||
page-title (or (some-> (:block/page block) :block/title)
|
||||
(when (ldb/page? block) (:block/title block)))
|
||||
properties-map (if-let [db-id (:db/id block)]
|
||||
(->> (outliner-property/get-block-full-properties (db/get-db) db-id)
|
||||
(map :db/ident)
|
||||
(remove #(= % :logseq.property.class/properties))
|
||||
(map (fn [property-id] [property-id (get block property-id)]))
|
||||
(into {}))
|
||||
(->> (:block/properties block)
|
||||
(remove (fn [[property-id _]] (= property-id :logseq.property.class/properties)))
|
||||
(into {})))
|
||||
children (when include-children?
|
||||
(build-block-renderer-children-props block))
|
||||
props (cond-> {:blockId uuid-str
|
||||
:properties (into {} (map (fn [[k v]]
|
||||
[(subs (str k) 1)
|
||||
(plugin-handler/serialize-property-value-for-plugin v)])
|
||||
properties-map))}
|
||||
uuid-str (assoc :uuid uuid-str)
|
||||
page-title (assoc :page page-title)
|
||||
(:block/title block) (assoc :content (:block/title block))
|
||||
(get block :block/format :markdown) (assoc :format (name (get block :block/format :markdown)))
|
||||
include-children? (assoc :children children))]
|
||||
{:block-id uuid-str
|
||||
:uuid uuid-str
|
||||
:page page-title
|
||||
:content (:block/title block)
|
||||
:format (some-> (get block :block/format :markdown) name)
|
||||
:properties-map properties-map
|
||||
:props (clj->js props)})))
|
||||
|
||||
(defn- block-renderer-supported-view?
|
||||
[{:keys [sidebar?]} property? table?]
|
||||
(and (not sidebar?)
|
||||
(not property?)
|
||||
(not table?)))
|
||||
|
||||
(defn- block-renderer-display-mode
|
||||
[{:keys [matched-block-renderer use-plugin-renderer? editing? plugin-renderer-error?]}]
|
||||
(if (and matched-block-renderer use-plugin-renderer? (not editing?) (not plugin-renderer-error?))
|
||||
:plugin
|
||||
:outline))
|
||||
|
||||
(defn- show-block-renderer-plugin-toggle?
|
||||
[display-mode {:keys [matched-block-renderer editing?]}]
|
||||
(boolean (and matched-block-renderer (not editing?) (= :outline display-mode))))
|
||||
|
||||
(defn- show-block-renderer-outline-toggle?
|
||||
[display-mode]
|
||||
(= :plugin display-mode))
|
||||
|
||||
(rum/defc setup-plugin-renderer-effects!
|
||||
[editing? switch-to-plugin-renderer!]
|
||||
(let [*previous-editing? (hooks/use-ref editing?)]
|
||||
(hooks/use-effect!
|
||||
(fn []
|
||||
(let [previous-editing? (.-current *previous-editing?)]
|
||||
(when (and previous-editing? (not editing?))
|
||||
(switch-to-plugin-renderer!))
|
||||
(set! (.-current *previous-editing?) editing?))
|
||||
(fn []))
|
||||
[editing?])
|
||||
[:<>]))
|
||||
|
||||
(defn- block-renderer-hides-outline-children?
|
||||
[display-mode {:keys [matched-block-renderer]}]
|
||||
(boolean (and (= :plugin display-mode)
|
||||
(true? (:include-children matched-block-renderer)))))
|
||||
|
||||
(defn- block-renderer-outline-view
|
||||
[config block uuid title table? property? edit-input-id editing? refs-count *hide-block-refs? *show-query? page-icon block-id collapsed?]
|
||||
[:div.flex.flex-col.w-full
|
||||
[:div.block-main-content.flex.flex-row.gap-2
|
||||
(when page-icon
|
||||
page-icon)
|
||||
|
||||
[:div.flex.flex-col.w-full
|
||||
(let [parsed-block (merge block (block/parse-title-and-body uuid (get block :block/format :markdown) title))
|
||||
hide-block-refs-count? (or (and (:embed? config)
|
||||
(= (:block/uuid parsed-block) (:embed-id config)))
|
||||
table?)]
|
||||
(block-content-or-editor config
|
||||
parsed-block
|
||||
{:edit-input-id edit-input-id
|
||||
:block-id block-id
|
||||
:edit? editing?
|
||||
:refs-count refs-count
|
||||
:*hide-block-refs? *hide-block-refs?
|
||||
:hide-block-refs-count? hide-block-refs-count?
|
||||
:*show-query? *show-query?}))]]
|
||||
|
||||
(when (and (not collapsed?) (not (or table? property?)))
|
||||
(block-positioned-properties config block :block-below))
|
||||
|
||||
(when-not (or (:table? config) (:property? config))
|
||||
(block-reactions block))])
|
||||
|
||||
(rum/defcs block-renderer-error-boundary
|
||||
< {:init (fn [state]
|
||||
(assoc state ::on-error (some-> state :rum/args first :on-error)))
|
||||
:did-catch (fn [state error _info]
|
||||
(when-let [on-error (::on-error state)]
|
||||
(on-error error))
|
||||
(assoc state ::error error))}
|
||||
[{error ::error} {:keys [fallback-view]} view]
|
||||
(if (some? error)
|
||||
fallback-view
|
||||
view))
|
||||
|
||||
(rum/defcs ^:large-vars/cleanup-todo block-container-inner-aux < rum/reactive db-mixins/query
|
||||
{:init (fn [state]
|
||||
(let [*ref (atom nil)
|
||||
@@ -3270,18 +3399,31 @@
|
||||
::ref *ref
|
||||
::hide-block-refs? (atom default-hide?)
|
||||
::show-query? (atom false)
|
||||
::refs-count *refs-count)))}
|
||||
::refs-count *refs-count
|
||||
::plugin-renderer-error? (atom false)
|
||||
::use-plugin-renderer? (atom true))))}
|
||||
(mixins/event-mixin
|
||||
(fn [state]
|
||||
(let [*ref (::ref state)]
|
||||
;; React doesn't let us directly control passive via onTouchMove
|
||||
;; So here we listen `touchmove` on the block node
|
||||
;; React doesn't let us directly control passive via onTouchMove
|
||||
;; So here we listen `touchmove` on the block node
|
||||
(mixins/listen state @*ref "touchmove" block-handler/on-touch-move))))
|
||||
[state container-state repo config* block {:keys [navigating-block navigated? editing? selected?] :as opts}]
|
||||
(let [*ref (::ref state)
|
||||
*hide-block-refs? (get state ::hide-block-refs?)
|
||||
*show-query? (get state ::show-query?)
|
||||
show-query? (rum/react *show-query?)
|
||||
*plugin-renderer-error? (get state ::plugin-renderer-error?)
|
||||
*use-plugin-renderer? (get state ::use-plugin-renderer?)
|
||||
plugin-renderer-error? (rum/react *plugin-renderer-error?)
|
||||
use-plugin-renderer? (rum/react *use-plugin-renderer?)
|
||||
switch-to-plugin-renderer! (fn []
|
||||
(reset! *plugin-renderer-error? false)
|
||||
(reset! *use-plugin-renderer? true))
|
||||
switch-to-outline-view! (fn []
|
||||
(reset! *plugin-renderer-error? false)
|
||||
(reset! *use-plugin-renderer? false))
|
||||
set-plugin-renderer-error! #(reset! *plugin-renderer-error? %)
|
||||
*refs-count (get state ::refs-count)
|
||||
hide-block-refs? (rum/react *hide-block-refs?)
|
||||
refs-count (rum/react *refs-count)
|
||||
@@ -3303,10 +3445,13 @@
|
||||
*control-show? (get container-state ::control-show?)
|
||||
db-collapsed? (util/collapsed? block)
|
||||
collapsed? (cond
|
||||
(:ignore-block-collapsed? config)
|
||||
false
|
||||
|
||||
(or ref-or-custom-query?
|
||||
(:view? config)
|
||||
(root-block? config block)
|
||||
(and (or (ldb/class? block) (ldb/property? block)) (:page-title? config)))
|
||||
(:view? config)
|
||||
(root-block? config block)
|
||||
(and (or (ldb/class? block) (ldb/property? block)) (:page-title? config)))
|
||||
(state/sub-block-collapsed uuid container-id)
|
||||
|
||||
:else
|
||||
@@ -3328,82 +3473,125 @@
|
||||
page-icon (when (:page-title? config)
|
||||
(let [icon' (get block :logseq.property/icon)]
|
||||
(when-let [icon (and (ldb/page? block)
|
||||
(or icon'
|
||||
(some :logseq.property/icon (:block/tags block))
|
||||
(when (ldb/class? block)
|
||||
{:type :tabler-icon
|
||||
:id "hash"})
|
||||
(when (ldb/property? block)
|
||||
{:type :tabler-icon
|
||||
:id "letter-p"})))]
|
||||
(or icon'
|
||||
(some :logseq.property/icon (:block/tags block))
|
||||
(when (ldb/class? block)
|
||||
{:type :tabler-icon
|
||||
:id "hash"})
|
||||
(when (ldb/property? block)
|
||||
{:type :tabler-icon
|
||||
:id "letter-p"})))]
|
||||
[:div.ls-page-icon.flex.self-start
|
||||
(icon-component/icon-picker icon
|
||||
{:on-chosen (fn [_e icon]
|
||||
(if icon
|
||||
(db-property-handler/set-block-property!
|
||||
(:db/id block)
|
||||
:logseq.property/icon
|
||||
(select-keys icon [:id :type :color]))
|
||||
;; del
|
||||
(db-property-handler/remove-block-property!
|
||||
(:db/id block)
|
||||
:logseq.property/icon)))
|
||||
:del-btn? (boolean icon')
|
||||
:icon-props {:style {:width "1lh"
|
||||
:height "1lh"
|
||||
:font-size (cond
|
||||
(and (util/mobile?) (:page-title? config)) 24
|
||||
(:page-title? config) 38
|
||||
:else 18)}}})])))]
|
||||
{:on-chosen (fn [_e icon]
|
||||
(if icon
|
||||
(db-property-handler/set-block-property!
|
||||
(:db/id block)
|
||||
:logseq.property/icon
|
||||
(select-keys icon [:id :type :color]))
|
||||
;; del
|
||||
(db-property-handler/remove-block-property!
|
||||
(:db/id block)
|
||||
:logseq.property/icon)))
|
||||
:del-btn? (boolean icon')
|
||||
:icon-props {:style {:width "1lh"
|
||||
:height "1lh"
|
||||
:font-size (cond
|
||||
(and (util/mobile?) (:page-title? config)) 24
|
||||
(:page-title? config) 38
|
||||
:else 18)}}})])))
|
||||
;; --- block renderer (full-block plugin replacement) ---
|
||||
block-renderer-base-match-context
|
||||
(when (and config/lsp-enabled?
|
||||
(plugin-handler/any-block-renderers?)
|
||||
(block-renderer-supported-view? config property? table?))
|
||||
(build-block-renderer-match-context block false))
|
||||
matched-block-renderer
|
||||
(when (and (:props block-renderer-base-match-context) (not editing?))
|
||||
(plugin-handler/get-matched-block-renderer block-renderer-base-match-context))
|
||||
block-renderer-match-context
|
||||
(if (true? (:include-children matched-block-renderer))
|
||||
(build-block-renderer-match-context block true)
|
||||
block-renderer-base-match-context)
|
||||
block-renderer-props-js (:props block-renderer-match-context)
|
||||
renderer-display-mode
|
||||
(block-renderer-display-mode {:matched-block-renderer matched-block-renderer
|
||||
:use-plugin-renderer? use-plugin-renderer?
|
||||
:editing? editing?
|
||||
:plugin-renderer-error? plugin-renderer-error?})
|
||||
switch-to-plugin-renderer-title
|
||||
(t (if plugin-renderer-error?
|
||||
:block/retry-plugin-renderer
|
||||
:block/switch-to-plugin-renderer))
|
||||
switch-to-outline-view-title (t :block/switch-to-outline-view)
|
||||
outline-view-cp
|
||||
[:div.flex.flex-col.w-full
|
||||
(block-renderer-outline-view config block uuid title table? property? edit-input-id editing? refs-count *hide-block-refs? *show-query? page-icon block-id collapsed?)
|
||||
(when (show-block-renderer-plugin-toggle?
|
||||
renderer-display-mode
|
||||
{:matched-block-renderer matched-block-renderer
|
||||
:editing? editing?})
|
||||
(shui/button
|
||||
{:variant :ghost
|
||||
:size :icon
|
||||
:class "self-start h-5 w-5 opacity-20 hover:opacity-70"
|
||||
:title switch-to-plugin-renderer-title
|
||||
:aria-label switch-to-plugin-renderer-title
|
||||
:on-pointer-down util/stop
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(switch-to-plugin-renderer!))}
|
||||
(shui/tabler-icon "puzzle-piece" {:size 13})))]]
|
||||
|
||||
[:div.ls-block.swipe-item
|
||||
(cond->
|
||||
{:id (str "ls-block-"
|
||||
;; container-id "-"
|
||||
uuid)
|
||||
:blockid (str uuid)
|
||||
:containerid container-id
|
||||
:data-is-property (ldb/property? block)
|
||||
:ref #(when (nil? @*ref) (reset! *ref %))
|
||||
:data-collapsed (and collapsed? has-child?)
|
||||
:class (str (when selected? "selected")
|
||||
(when (ldb/recycled? block) " line-through opacity-70")
|
||||
(when order-list? " is-order-list")
|
||||
(when (string/blank? title) " is-blank")
|
||||
(when original-block " embed-block"))
|
||||
:haschild (str (boolean has-child?))
|
||||
:on-touch-start (fn [event uuid]
|
||||
(when-not (or @*dragging? (state/editing?))
|
||||
(block-handler/on-touch-start event uuid)))
|
||||
:on-touch-end (fn [event]
|
||||
(when-not @*dragging?
|
||||
(block-handler/on-touch-end event))
|
||||
(reset! *dragging? false))
|
||||
:on-touch-cancel (fn [e]
|
||||
(block-handler/on-touch-cancel e))}
|
||||
{:id (str "ls-block-"
|
||||
;; container-id "-"
|
||||
uuid)
|
||||
:blockid (str uuid)
|
||||
:containerid container-id
|
||||
:data-is-property (ldb/property? block)
|
||||
:ref #(when (nil? @*ref) (reset! *ref %))
|
||||
:data-collapsed (and collapsed? has-child?)
|
||||
:class (str (when selected? "selected")
|
||||
(when (ldb/recycled? block) " line-through opacity-70")
|
||||
(when order-list? " is-order-list")
|
||||
(when (string/blank? title) " is-blank")
|
||||
(when original-block " embed-block"))
|
||||
:haschild (str (boolean has-child?))
|
||||
:on-touch-start (fn [event uuid]
|
||||
(when-not (or @*dragging? (state/editing?))
|
||||
(block-handler/on-touch-start event uuid)))
|
||||
:on-touch-end (fn [event]
|
||||
(when-not @*dragging?
|
||||
(block-handler/on-touch-end event))
|
||||
(reset! *dragging? false))
|
||||
:on-touch-cancel (fn [e]
|
||||
(block-handler/on-touch-cancel e))}
|
||||
|
||||
(and (util/capacitor?) (not (ldb/page? block)))
|
||||
(assoc
|
||||
:draggable true
|
||||
:on-drag-start
|
||||
(fn [event]
|
||||
(when-not (state/editing?)
|
||||
(util/stop-propagation event)
|
||||
(let [target ^js (.-target event)
|
||||
blocks (or (seq (state/get-selection-blocks)) [target])
|
||||
multiple? (> (count blocks) 1)
|
||||
element (when multiple?
|
||||
(let [element (dom/create-element "div")]
|
||||
(-> element
|
||||
(dom/set-attr! "id" "dragging-ghost-element")
|
||||
(dom/set-text! (t :editor/moving-blocks-count (count blocks)))
|
||||
(dom/set-class! "p-2 rounded text-sm"))
|
||||
element))]
|
||||
(doseq [block blocks]
|
||||
(dom/add-class! block "dragging"))
|
||||
(on-drag-start event block block-id)
|
||||
(when element
|
||||
(dom/append! js/document.body element)
|
||||
(dnd/set-drag-image! event element (/ (.-offsetWidth target) 2) (/ (.-offsetHeight target) 2)))))))
|
||||
:draggable true
|
||||
:on-drag-start
|
||||
(fn [event]
|
||||
(when-not (state/editing?)
|
||||
(util/stop-propagation event)
|
||||
(let [target ^js (.-target event)
|
||||
blocks (or (seq (state/get-selection-blocks)) [target])
|
||||
multiple? (> (count blocks) 1)
|
||||
element (when multiple?
|
||||
(let [element (dom/create-element "div")]
|
||||
(-> element
|
||||
(dom/set-attr! "id" "dragging-ghost-element")
|
||||
(dom/set-text! (t :editor/moving-blocks-count (count blocks)))
|
||||
(dom/set-class! "p-2 rounded text-sm"))
|
||||
element))]
|
||||
(doseq [block blocks]
|
||||
(dom/add-class! block "dragging"))
|
||||
(on-drag-start event block block-id)
|
||||
(when element
|
||||
(dom/append! js/document.body element)
|
||||
(dnd/set-drag-image! event element (/ (.-offsetWidth target) 2) (/ (.-offsetHeight target) 2)))))))
|
||||
|
||||
(:property-default-value? config)
|
||||
(assoc :data-is-property-default-value (:property-default-value? config))
|
||||
@@ -3453,48 +3641,49 @@
|
||||
|
||||
(when (and (not property?) (not (:table-block-title? config)))
|
||||
(let [edit? (or editing?
|
||||
(= uuid (:block/uuid (state/get-edit-block))))]
|
||||
(= uuid (:block/uuid (state/get-edit-block))))]
|
||||
(block-control (assoc config :hide-bullet? (:page-title? config))
|
||||
block
|
||||
(merge opts
|
||||
{:uuid uuid
|
||||
:block-id block-id
|
||||
:collapsed? collapsed?
|
||||
:*control-show? *control-show?
|
||||
:edit? edit?}))))
|
||||
block
|
||||
(merge opts
|
||||
{:uuid uuid
|
||||
:block-id block-id
|
||||
:collapsed? collapsed?
|
||||
:*control-show? *control-show?
|
||||
:edit? edit?}))))
|
||||
|
||||
[:div.flex.flex-col.w-full
|
||||
[:div.block-main-content.flex.flex-row.gap-2
|
||||
(when page-icon
|
||||
page-icon)
|
||||
(if (= :plugin renderer-display-mode)
|
||||
;; --- Plugin renderer: full-block replacement ---
|
||||
[:div.block-renderer-container.flex.flex-col.w-full
|
||||
(when (show-block-renderer-outline-toggle? renderer-display-mode)
|
||||
[:div.block-renderer-action-bar
|
||||
(shui/button
|
||||
{:variant :outline
|
||||
:class "block-renderer-action-btn h-6 w-6"
|
||||
:title switch-to-outline-view-title
|
||||
:aria-label switch-to-outline-view-title
|
||||
:on-pointer-down util/stop
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(switch-to-outline-view!))}
|
||||
(shui/tabler-icon "list" {:size 13}))])
|
||||
[:div.ls-block-plugin-renderer
|
||||
(rum/with-key
|
||||
(block-renderer-error-boundary
|
||||
{:on-error (fn [_error]
|
||||
(set-plugin-renderer-error! true))
|
||||
:fallback-view outline-view-cp}
|
||||
(when-some [renderer (:render matched-block-renderer)]
|
||||
(js/React.createElement renderer block-renderer-props-js)))
|
||||
(str "block-renderer-" (:key matched-block-renderer) "-" uuid))]]
|
||||
|
||||
;; Not embed self
|
||||
[:div.flex.flex-col.w-full
|
||||
(let [block (merge block (block/parse-title-and-body uuid (get block :block/format :markdown) title))
|
||||
hide-block-refs-count? (or (and (:embed? config)
|
||||
(= (:block/uuid block) (:embed-id config)))
|
||||
table?)]
|
||||
(block-content-or-editor config
|
||||
block
|
||||
{:edit-input-id edit-input-id
|
||||
:block-id block-id
|
||||
:edit? editing?
|
||||
:refs-count refs-count
|
||||
:*hide-block-refs? *hide-block-refs?
|
||||
:hide-block-refs-count? hide-block-refs-count?
|
||||
:*show-query? *show-query?}))]]
|
||||
|
||||
(when (and (not collapsed?) (not (or table? property?)))
|
||||
(block-positioned-properties config block :block-below))
|
||||
|
||||
(when-not (or (:table? config) (:property? config))
|
||||
(block-reactions block))]])
|
||||
;; --- Original outline ---
|
||||
outline-view-cp)])
|
||||
|
||||
(when (and (not (:library? config))
|
||||
(or (:tag-dialog? config)
|
||||
(and
|
||||
(not collapsed?)
|
||||
(not (or table? property?)))))
|
||||
(or (:tag-dialog? config)
|
||||
(and
|
||||
(not collapsed?)
|
||||
(not (or table? property?)))))
|
||||
[:div (when-not (:page-title? config) {:style {:padding-left (if (util/mobile?) 12 45)}})
|
||||
(db-properties-cp config block {:in-block-container? true})])
|
||||
|
||||
@@ -3510,15 +3699,15 @@
|
||||
(block-container config query)])]))
|
||||
|
||||
(when (and (not (or (:table? config) (:property? config)))
|
||||
(not hide-block-refs?)
|
||||
(> refs-count 0)
|
||||
(not (:page-title? config)))
|
||||
(not hide-block-refs?)
|
||||
(> refs-count 0)
|
||||
(not (:page-title? config)))
|
||||
(when-let [refs-cp (state/get-component :block/linked-references)]
|
||||
[:div.px-4.py-2.border.rounded.my-2.shadow-xs {:style {:margin-left 42}}
|
||||
(refs-cp block {})]))
|
||||
|
||||
(when (and (not collapsed?) (not (or table? property?))
|
||||
(ldb/class-instance? (entity-plus/entity-memoized (db/get-db) :logseq.class/Query) block))
|
||||
(ldb/class-instance? (entity-plus/entity-memoized (db/get-db) :logseq.class/Query) block))
|
||||
(let [query-block (:logseq.property/query (db/entity (:db/id block)))
|
||||
query-block (if query-block (db/sub-block (:db/id query-block)) query-block)
|
||||
query (:block/title query-block)
|
||||
@@ -3527,20 +3716,28 @@
|
||||
(when query-block
|
||||
[:div {:style {:padding-left 42}}
|
||||
(query/custom-query (wrap-query-components (assoc config
|
||||
:dsl-query? (not advanced-query?)
|
||||
:cards? (ldb/class-instance? (entity-plus/entity-memoized
|
||||
(db/get-db)
|
||||
:logseq.class/Cards) block)))
|
||||
(if advanced-query? result {:builder nil
|
||||
:query (query-builder-component/sanitize-q query)}))])))
|
||||
:dsl-query? (not advanced-query?)
|
||||
:cards? (ldb/class-instance? (entity-plus/entity-memoized
|
||||
(db/get-db)
|
||||
:logseq.class/Cards) block)))
|
||||
(if advanced-query? result {:builder nil
|
||||
:query (query-builder-component/sanitize-q query)}))])))
|
||||
|
||||
(when-not (or (:hide-children? config) table? property?)
|
||||
(when-not (or (:hide-children? config)
|
||||
table?
|
||||
property?
|
||||
(block-renderer-hides-outline-children?
|
||||
renderer-display-mode
|
||||
{:matched-block-renderer matched-block-renderer}))
|
||||
(let [config' (-> (update config :level inc)
|
||||
(dissoc :original-block :data))]
|
||||
(dissoc :original-block :data))]
|
||||
(block-children config' block children collapsed?)))
|
||||
|
||||
(when-not (or table? property?)
|
||||
(dnd-separator-wrapper block block-id false))]))
|
||||
(dnd-separator-wrapper block block-id false))
|
||||
|
||||
(when config/lsp-enabled?
|
||||
(setup-plugin-renderer-effects! editing? switch-to-plugin-renderer!))]))
|
||||
|
||||
(rum/defc block-container-inner
|
||||
[container-state repo config* block opts]
|
||||
@@ -3644,21 +3841,25 @@
|
||||
(rum/defc block-container
|
||||
[config block* & {:as opts}]
|
||||
(let [[block set-block!] (hooks/use-state block*)
|
||||
id (or (:db/id block*) (:block/uuid block*))]
|
||||
(when-not (or (:page-title? config)
|
||||
(:view? config))
|
||||
(hooks/use-effect!
|
||||
(fn []
|
||||
id (or (:db/id block*) (:block/uuid block*))
|
||||
temporary-collapsed-state (state/get-block-collapsed (:block/uuid block)
|
||||
(:container-id config))
|
||||
ignore-block-collapsed? (:ignore-block-collapsed? config)
|
||||
load-children? (editor-handler/load-children? block
|
||||
temporary-collapsed-state
|
||||
ignore-block-collapsed?)]
|
||||
(hooks/use-effect!
|
||||
(fn []
|
||||
(when-not (or (:page-title? config) (:view? config))
|
||||
(p/let [block (db-async/<get-block (state/get-current-repo)
|
||||
id
|
||||
{:children? (not
|
||||
(if-some [result (state/get-block-collapsed (:block/uuid block)
|
||||
(:container-id config))]
|
||||
result
|
||||
(:block/collapsed? block)))
|
||||
{:children? load-children?
|
||||
:include-collapsed-children? (and load-children?
|
||||
ignore-block-collapsed?)
|
||||
:skip-refresh? false})]
|
||||
(set-block! block)))
|
||||
[]))
|
||||
(set-block! block)))
|
||||
nil)
|
||||
[id load-children? ignore-block-collapsed? temporary-collapsed-state])
|
||||
(when (or (:view? config) (:block/title block))
|
||||
(loaded-block-container config block opts))))
|
||||
|
||||
@@ -4295,77 +4496,77 @@
|
||||
[:div.only-page-blocks items]))]))
|
||||
|
||||
;; headers to hiccup
|
||||
(defn ->hiccup
|
||||
[blocks config option]
|
||||
[:div.content
|
||||
(cond-> option
|
||||
(:document/mode? config) (assoc :class "doc-mode"))
|
||||
(cond
|
||||
(and (:custom-query? config) (:group-by-page? config))
|
||||
[:div.flex.flex-col
|
||||
(let [blocks (sort-by (comp :block/journal-day first) > blocks)]
|
||||
(for [[page blocks] blocks]
|
||||
(let [alias? (:block/alias? page)
|
||||
page (db/entity (:db/id page))
|
||||
blocks (tree/non-consecutive-blocks->vec-tree blocks)
|
||||
parent-blocks (group-by :block/parent blocks)]
|
||||
[:div.custom-query-page-result {:key (str "page-" (:db/id page))}
|
||||
(ui/foldable
|
||||
[:div
|
||||
(page-cp config page)
|
||||
(when alias? [:span.text-sm.font-medium.opacity-50 (str " " (t :property.built-in/alias))])]
|
||||
(fn []
|
||||
(let [{top-level-blocks true others false} (group-by
|
||||
(fn [b] (= (:db/id page) (:db/id (first b))))
|
||||
parent-blocks)
|
||||
sorted-parent-blocks (concat top-level-blocks others)]
|
||||
(for [[parent blocks] sorted-parent-blocks]
|
||||
(let [top-level? (= (:db/id parent) (:db/id page))]
|
||||
(rum/with-key
|
||||
(breadcrumb-with-container blocks (assoc config :top-level? top-level?))
|
||||
(:db/id parent))))))
|
||||
{:debug-id page})])))]
|
||||
(defn ->hiccup
|
||||
[blocks config option]
|
||||
[:div.content
|
||||
(cond-> option
|
||||
(:document/mode? config) (assoc :class "doc-mode"))
|
||||
(cond
|
||||
(and (:custom-query? config) (:group-by-page? config))
|
||||
[:div.flex.flex-col
|
||||
(let [blocks (sort-by (comp :block/journal-day first) > blocks)]
|
||||
(for [[page blocks] blocks]
|
||||
(let [alias? (:block/alias? page)
|
||||
page (db/entity (:db/id page))
|
||||
blocks (tree/non-consecutive-blocks->vec-tree blocks)
|
||||
parent-blocks (group-by :block/parent blocks)]
|
||||
[:div.custom-query-page-result {:key (str "page-" (:db/id page))}
|
||||
(ui/foldable
|
||||
[:div
|
||||
(page-cp config page)
|
||||
(when alias? [:span.text-sm.font-medium.opacity-50 (str " " (t :property.built-in/alias))])]
|
||||
(fn []
|
||||
(let [{top-level-blocks true others false} (group-by
|
||||
(fn [b] (= (:db/id page) (:db/id (first b))))
|
||||
parent-blocks)
|
||||
sorted-parent-blocks (concat top-level-blocks others)]
|
||||
(for [[parent blocks] sorted-parent-blocks]
|
||||
(let [top-level? (= (:db/id parent) (:db/id page))]
|
||||
(rum/with-key
|
||||
(breadcrumb-with-container blocks (assoc config :top-level? top-level?))
|
||||
(:db/id parent))))))
|
||||
{:debug-id page})])))]
|
||||
|
||||
(and (:ref? config) (:group-by-page? config) (vector? (first blocks)))
|
||||
[:div.flex.flex-col.references-blocks-wrap
|
||||
(let [blocks (sort-by (comp :block/journal-day first) > blocks)
|
||||
scroll-container (or (:scroll-container config)
|
||||
(util/app-scroll-container-node))
|
||||
scroll-container (if (fn? scroll-container)
|
||||
(scroll-container) scroll-container)]
|
||||
(when (seq blocks)
|
||||
(if (:sidebar? config)
|
||||
(for [block blocks]
|
||||
(rum/with-key
|
||||
(ref-block-container config block)
|
||||
(str "ref-" (:container-id config) "-" (:db/id (first block)))))
|
||||
(ui/virtualized-list
|
||||
{:custom-scroll-parent scroll-container
|
||||
:compute-item-key (fn [idx]
|
||||
(let [block (nth blocks idx)]
|
||||
(str "ref-" (:container-id config) "-" (:db/id (first block)))))
|
||||
:total-count (count blocks)
|
||||
:item-content (fn [idx]
|
||||
(let [block (nth blocks idx)]
|
||||
(ref-block-container config block)))}))))]
|
||||
(and (:ref? config) (:group-by-page? config) (vector? (first blocks)))
|
||||
[:div.flex.flex-col.references-blocks-wrap
|
||||
(let [blocks (sort-by (comp :block/journal-day first) > blocks)
|
||||
scroll-container (or (:scroll-container config)
|
||||
(util/app-scroll-container-node))
|
||||
scroll-container (if (fn? scroll-container)
|
||||
(scroll-container) scroll-container)]
|
||||
(when (seq blocks)
|
||||
(if (:sidebar? config)
|
||||
(for [block blocks]
|
||||
(rum/with-key
|
||||
(ref-block-container config block)
|
||||
(str "ref-" (:container-id config) "-" (:db/id (first block)))))
|
||||
(ui/virtualized-list
|
||||
{:custom-scroll-parent scroll-container
|
||||
:compute-item-key (fn [idx]
|
||||
(let [block (nth blocks idx)]
|
||||
(str "ref-" (:container-id config) "-" (:db/id (first block)))))
|
||||
:total-count (count blocks)
|
||||
:item-content (fn [idx]
|
||||
(let [block (nth blocks idx)]
|
||||
(ref-block-container config block)))}))))]
|
||||
|
||||
(and (:group-by-page? config)
|
||||
(vector? (first blocks)))
|
||||
[:div.flex.flex-col
|
||||
(let [blocks (sort-by (comp :block/journal-day first) > blocks)]
|
||||
(for [[page blocks] blocks]
|
||||
(let [blocks (remove nil? blocks)]
|
||||
(when (seq blocks)
|
||||
(let [alias? (:block/alias? page)
|
||||
page (db/entity (:db/id page))]
|
||||
[:div.my-2 {:key (str "page-" (:db/id page))}
|
||||
(ui/foldable
|
||||
[:div
|
||||
(page-cp config page)
|
||||
(when alias? [:span.text-sm.font-medium.opacity-50 (str " " (t :property.built-in/alias))])]
|
||||
(fn []
|
||||
(blocks-container config blocks))
|
||||
{})])))))]
|
||||
(and (:group-by-page? config)
|
||||
(vector? (first blocks)))
|
||||
[:div.flex.flex-col
|
||||
(let [blocks (sort-by (comp :block/journal-day first) > blocks)]
|
||||
(for [[page blocks] blocks]
|
||||
(let [blocks (remove nil? blocks)]
|
||||
(when (seq blocks)
|
||||
(let [alias? (:block/alias? page)
|
||||
page (db/entity (:db/id page))]
|
||||
[:div.my-2 {:key (str "page-" (:db/id page))}
|
||||
(ui/foldable
|
||||
[:div
|
||||
(page-cp config page)
|
||||
(when alias? [:span.text-sm.font-medium.opacity-50 (str " " (t :property.built-in/alias))])]
|
||||
(fn []
|
||||
(blocks-container config blocks))
|
||||
{})])))))]
|
||||
|
||||
:else
|
||||
(blocks-container config blocks))])
|
||||
:else
|
||||
(blocks-container config blocks))])
|
||||
|
||||
@@ -1299,6 +1299,24 @@ html.is-mac {
|
||||
@apply relative inline-block w-full;
|
||||
}
|
||||
|
||||
.block-renderer-container {
|
||||
@apply relative;
|
||||
|
||||
.block-renderer-action-bar {
|
||||
@apply top-1 right-1 absolute z-10 flex items-center opacity-0 transition-opacity;
|
||||
}
|
||||
|
||||
.block-renderer-action-btn {
|
||||
@apply m-1 p-0.5 opacity-70 select-none hover:opacity-90 active:opacity-60;
|
||||
}
|
||||
|
||||
&:hover, &:active, &:focus-within {
|
||||
.block-renderer-action-bar {
|
||||
@apply opacity-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.asset-transfer-placeholder {
|
||||
@apply text-sm text-gray-11 mt-2;
|
||||
}
|
||||
|
||||
169
src/main/frontend/components/plugin_logs.cljs
Normal file
169
src/main/frontend/components/plugin_logs.cljs
Normal file
@@ -0,0 +1,169 @@
|
||||
(ns frontend.components.plugin-logs
|
||||
"Viewer panel for a single plugin's logs powered by PluginLogger."
|
||||
(:require [cljs-bean.core :as bean]
|
||||
[clojure.string :as string]
|
||||
[frontend.context.i18n :refer [t]]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.ui :as ui]
|
||||
[frontend.util :as util]
|
||||
[logseq.shui.hooks :as hooks]
|
||||
[logseq.shui.ui :as shui]
|
||||
[rum.core :as rum]))
|
||||
|
||||
(def ^:private levels ["DEBUG" "INFO" "WARN" "ERROR"])
|
||||
|
||||
(defn- ^js get-plugin-logger
|
||||
[pid]
|
||||
(when-let [^js core (and pid (.-LSPluginCore js/window))]
|
||||
(try (.getPluginLogger core pid) (catch :default _ nil))))
|
||||
|
||||
(defn- get-entries
|
||||
[pid]
|
||||
(when-let [^js logger (get-plugin-logger pid)]
|
||||
(->> (.getEntries logger)
|
||||
(bean/->clj))))
|
||||
|
||||
(defn- format-time
|
||||
[ts]
|
||||
(let [d (js/Date. ts)]
|
||||
(.toLocaleTimeString d)))
|
||||
|
||||
(defn- entry->text
|
||||
[{:keys [ts level tag message]}]
|
||||
(str "[" (format-time ts) "] " level " [" tag "] " message))
|
||||
|
||||
(defn- copy-all!
|
||||
[entries]
|
||||
(-> (string/join "\n" (map entry->text entries))
|
||||
(util/copy-to-clipboard!))
|
||||
(notification/show! (t :plugin/logs-copied) :success))
|
||||
|
||||
(rum/defc ^:large-vars/cleanup-todo plugin-logs-panel
|
||||
[{:keys [pid name]}]
|
||||
(let [[entries set-entries!] (rum/use-state (or (get-entries pid) []))
|
||||
[level-filter set-level-filter!] (rum/use-state nil)
|
||||
[keyword-filter set-keyword-filter!] (rum/use-state "")
|
||||
^js logger (get-plugin-logger pid)
|
||||
refresh! (hooks/use-callback
|
||||
(fn [] (set-entries! (or (get-entries pid) [])))
|
||||
[pid])]
|
||||
|
||||
(hooks/use-effect!
|
||||
(fn []
|
||||
(when logger
|
||||
(let [h (fn [] (refresh!))]
|
||||
(.on logger "change" h)
|
||||
#(.off logger "change" h))))
|
||||
[logger refresh!])
|
||||
|
||||
(let [filtered (cond->> entries
|
||||
(seq level-filter)
|
||||
(filter #(= (:level %) level-filter))
|
||||
|
||||
(not (string/blank? keyword-filter))
|
||||
(filter #(string/includes?
|
||||
(string/lower-case (str (:message %)))
|
||||
(string/lower-case keyword-filter)))
|
||||
|
||||
;; newest first
|
||||
:always reverse)]
|
||||
[:div.cp__plugins-logs.flex.flex-col.gap-3
|
||||
{:style {:min-width "720px" :max-width "960px"}}
|
||||
|
||||
;; Row 1 - title + meta
|
||||
[:div.cp__plugins-logs-head.flex.items-center.justify-between.gap-3.flex-wrap
|
||||
[:h1.text-lg.font-semibold.flex.items-center.gap-2.m-0
|
||||
(ui/icon "file-description")
|
||||
[:span (t :plugin/logs-title)]
|
||||
(when name
|
||||
[:code.opacity-70.text-xs.px-1.5.py-0.5.rounded.bg-gray-03 name])]
|
||||
|
||||
[:div.flex.items-center.gap-3.text-xs.opacity-70
|
||||
[:span (str (count filtered) " / " (count entries))]
|
||||
(when logger
|
||||
[:span.flex.items-center.gap-1
|
||||
"level:" [:code (.getLevel logger)]])]]
|
||||
|
||||
;; Row 2 - controls
|
||||
[:div.cp__plugins-logs-toolbar.flex.items-center.gap-2.flex-wrap
|
||||
;; keyword filter (flex-grow)
|
||||
[:div.relative.flex-1.min-w-48.flex.items-center
|
||||
[:span.absolute.opacity-50.pointer-events-none.flex.items-center
|
||||
{:style {:left "8px" :top "0" :bottom "0"}}
|
||||
(ui/icon "search" {:size 14})]
|
||||
[:input.form-input.text-xs.h-8.w-full
|
||||
{:style {:paddingLeft "28px"}
|
||||
:placeholder (t :plugin/logs-filter-placeholder)
|
||||
:value keyword-filter
|
||||
:on-change #(set-keyword-filter! (util/evalue %))}]]
|
||||
|
||||
;; level filter
|
||||
(shui/select
|
||||
{:value (or level-filter "*")
|
||||
:on-value-change (fn [v] (set-level-filter! (if (= v "*") nil v)))}
|
||||
(shui/select-trigger
|
||||
{:class "w-32 h-8 text-xs shrink-0"}
|
||||
(shui/select-value {:placeholder (t :plugin/logs-level-all)}))
|
||||
(shui/select-content
|
||||
(shui/select-item {:value "*"} (t :plugin/logs-level-all))
|
||||
(for [lvl levels]
|
||||
(shui/select-item {:value lvl :key lvl} lvl))))
|
||||
|
||||
[:div.h-5.w-px.bg-gray-05.shrink-0]
|
||||
|
||||
;; copy (chronological order for readability when pasted)
|
||||
(shui/button
|
||||
{:size :sm :variant :outline
|
||||
:class "h-8 shrink-0"
|
||||
:on-click #(copy-all! (reverse filtered))
|
||||
:title (t :plugin/logs-copy)}
|
||||
(ui/icon "copy" {:size 14})
|
||||
[:span.ml-1.5 (t :plugin/logs-copy)])
|
||||
|
||||
;; clear
|
||||
(shui/button
|
||||
{:size :sm :variant :outline
|
||||
:class "h-8 shrink-0"
|
||||
:on-click (fn []
|
||||
(when logger
|
||||
(.clear logger)
|
||||
(refresh!)))
|
||||
:title (t :plugin/logs-clear)}
|
||||
(ui/icon "trash" {:size 14})
|
||||
[:span.ml-1.5 (t :plugin/logs-clear)])]
|
||||
|
||||
;; Body - log list
|
||||
[:div.cp__plugins-logs-body.text-xs.font-mono.rounded.border
|
||||
{:style {:height "60vh" :overflow "auto" :padding "8px"
|
||||
:background "var(--ls-secondary-background-color)"}}
|
||||
(if (empty? filtered)
|
||||
[:div.opacity-60.p-4.text-center (t :plugin/logs-empty)]
|
||||
(for [[idx e] (map-indexed vector filtered)]
|
||||
[:div.cp__plugins-logs-row.flex.gap-1.py-0.5.items-start
|
||||
{:key (str idx "-" (:ts e))
|
||||
:data-level (:level e)}
|
||||
[:span.opacity-60.shrink-0 {:style {:width "80px"}}
|
||||
(format-time (:ts e))]
|
||||
[:span.shrink-0.font-bold
|
||||
{:style {:width "56px"
|
||||
:color (case (:level e)
|
||||
"ERROR" "var(--ls-error-text-color, #ef4444)"
|
||||
"WARN" "#d97706"
|
||||
"INFO" "var(--ls-active-primary-color)"
|
||||
"DEBUG" "var(--ls-icon-color)"
|
||||
nil)}}
|
||||
(:level e)]
|
||||
[:span.opacity-70.shrink-0 (str "[" (:tag e) "]")]
|
||||
[:span.whitespace-pre-wrap.break-all.flex-1 (:message e)]]))]])))
|
||||
|
||||
(defn open-plugin-logs!
|
||||
[{:keys [_pid _name] :as opts}]
|
||||
(shui/dialog-open!
|
||||
(fn [] (plugin-logs-panel opts))
|
||||
{:label "plugin-logs-modal"
|
||||
:class "lsp-plugin-logs-dialog"
|
||||
:content-props {:on-open-auto-focus #(.preventDefault %)}}))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
[clojure.string :as string]
|
||||
[electron.ipc :as ipc]
|
||||
[frontend.components.plugins-settings :as plugins-settings]
|
||||
[frontend.components.plugin-logs :as plugin-logs]
|
||||
[frontend.components.svg :as svg]
|
||||
[frontend.config :as config]
|
||||
[frontend.context.i18n :refer [interpolate-rich-text interpolate-rich-text-node t]]
|
||||
@@ -258,6 +259,7 @@
|
||||
[:strong (ui/icon "settings")]
|
||||
[:ul.menu-list
|
||||
[:li {:on-click #(plugin-handler/open-plugin-settings! id false)} (t :plugin/open-settings)]
|
||||
[:li {:on-click #(plugin-logs/open-plugin-logs! {:pid id :name name})} (t :plugin/open-logs)]
|
||||
(when (util/electron?)
|
||||
[:li {:on-click #(js/apis.openPath url)} (t :plugin/open-package)])
|
||||
[:li {:on-click #(plugin-handler/open-report-modal! id name)} (t :plugin/report-security)]
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
[frontend.db.model :as db-model]
|
||||
[frontend.handler.db-based.property :as db-property-handler]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.handler.plugin :as plugin-handler]
|
||||
[frontend.handler.property :as property-handler]
|
||||
[frontend.handler.route :as route-handler]
|
||||
[frontend.mixins :as mixins]
|
||||
@@ -65,6 +66,13 @@
|
||||
property)
|
||||
(notification/show! (t :property.validation/invalid-name) :error)))))
|
||||
|
||||
(defn- enable-block-properties-renderers?
|
||||
[{:keys [sidebar? sidebar-properties?]} class?]
|
||||
(and config/lsp-enabled?
|
||||
(not class?)
|
||||
(not sidebar?)
|
||||
(not sidebar-properties?)))
|
||||
|
||||
;; TODO: This component should be cleaned up as it's only used for new properties and used to be used for existing properties
|
||||
(rum/defcs property-type-select <
|
||||
shortcut/disable-all-shortcuts
|
||||
@@ -827,28 +835,66 @@
|
||||
:else
|
||||
(let [remove-properties #{:logseq.property/icon :logseq.property/query}
|
||||
properties' (->> (remove (fn [[k _v]] (contains? remove-properties k))
|
||||
full-properties)
|
||||
(remove (fn [[k _v]] (= k :logseq.property.class/properties))))
|
||||
full-properties)
|
||||
(remove (fn [[k _v]] (= k :logseq.property.class/properties))))
|
||||
page? (entity-util/page? block)
|
||||
class? (entity-util/class? block)]
|
||||
class? (entity-util/class? block)
|
||||
plugin-properties (->> (concat full-properties hidden-properties)
|
||||
(remove (fn [[k _v]] (= k :logseq.property.class/properties)))
|
||||
(into {}))
|
||||
props-for-plugin (when (enable-block-properties-renderers? opts class?)
|
||||
(clj->js {:blockId (str (:block/uuid block))
|
||||
:properties (into {} (map (fn [[k v]]
|
||||
[(subs (str k) 1)
|
||||
(plugin-handler/serialize-property-value-for-plugin v)])
|
||||
plugin-properties))}))
|
||||
plugin-renderers (when props-for-plugin
|
||||
(plugin-handler/get-matched-block-properties-renderers
|
||||
{:block-id (str (:block/uuid block))
|
||||
:properties-map plugin-properties
|
||||
:props props-for-plugin}))
|
||||
prepend-renderers (filter #(= "prepend" (:mode %)) plugin-renderers)
|
||||
replace-renderer (first (filter #(= "replace" (:mode %)) plugin-renderers))
|
||||
append-renderers (remove #(contains? #{"prepend" "replace"} (:mode %)) plugin-renderers)]
|
||||
|
||||
[:div.ls-properties-area
|
||||
{:id id
|
||||
:class (util/classnames [{:ls-page-properties page?}])
|
||||
:tab-index 0}
|
||||
[:<>
|
||||
(properties-section block properties' opts)
|
||||
(bidirectional-properties-section bidirectional-properties)
|
||||
(mapv (fn [r]
|
||||
(when (fn? (:render r))
|
||||
(rum/with-key
|
||||
(js/React.createElement (:render r) props-for-plugin)
|
||||
(str "plugin-prepend-" (:key r)))))
|
||||
prepend-renderers)
|
||||
|
||||
(if replace-renderer
|
||||
(when (fn? (:render replace-renderer))
|
||||
(rum/with-key
|
||||
(js/React.createElement (:render replace-renderer) props-for-plugin)
|
||||
(str "plugin-replace-" (:key replace-renderer))))
|
||||
[:<>
|
||||
(properties-section block properties' opts)
|
||||
(bidirectional-properties-section bidirectional-properties)])
|
||||
|
||||
(when-not class?
|
||||
(hidden-properties-cp block hidden-properties
|
||||
(assoc opts :root-block? root-block?)))
|
||||
(assoc opts :root-block? root-block?)))
|
||||
|
||||
(when (and page? (not class?))
|
||||
(rum/with-key (new-property block opts) (str id "-add-property")))
|
||||
|
||||
(mapv (fn [r]
|
||||
(when (fn? (:render r))
|
||||
(rum/with-key
|
||||
(js/React.createElement (:render r) props-for-plugin)
|
||||
(str "plugin-append-" (:key r)))))
|
||||
append-renderers)
|
||||
|
||||
(when class?
|
||||
(let [properties (->> (:logseq.property.class/properties block)
|
||||
(map (fn [e] [(:db/ident e)])))
|
||||
(map (fn [e] [(:db/ident e)])))
|
||||
opts' (assoc opts :class-schema? true)]
|
||||
[:div.flex.flex-col.gap-1
|
||||
[:div {:style {:font-size 15}}
|
||||
@@ -860,5 +906,5 @@
|
||||
[:div.ml-4
|
||||
(properties-section block properties opts')
|
||||
(hidden-properties-cp block hidden-properties
|
||||
(assoc opts :root-block? root-block?))
|
||||
(assoc opts :root-block? root-block?))
|
||||
(rum/with-key (new-property block opts') (str id "-class-add-property"))]]))]])))]))
|
||||
|
||||
@@ -659,6 +659,63 @@
|
||||
{:left-label (t :settings.sync-server/url)
|
||||
:action (sync-server-url-button)}))
|
||||
|
||||
(rum/defc publish-server-url-settings-container
|
||||
[]
|
||||
(let [current-url (config/get-custom-publish-server-url)
|
||||
[url set-url!] (rum/use-state (or current-url ""))
|
||||
reset-url! (fn []
|
||||
(config/set-custom-publish-server-url! nil)
|
||||
(set-url! "")
|
||||
(notification/show! (t :settings-page/publish-server-url-cleared) :success))]
|
||||
[:div.cp__settings-publish-server-cnt
|
||||
[:h1.mb-2.text-2xl.font-bold (t :settings-page/publish-server-url)]
|
||||
[:div.p-2
|
||||
[:p.text-sm.opacity-70.mb-4 (t :settings-page/publish-server-url-desc)]
|
||||
[:p
|
||||
[:label
|
||||
[:strong "URL"]
|
||||
[:input.form-input.is-small
|
||||
{:value url
|
||||
:placeholder config/default-publish-api-base
|
||||
:style {:width "100%"}
|
||||
:on-change #(set-url! (util/evalue %))}]]]
|
||||
[:p.pt-2.flex.gap-2
|
||||
(shui/button
|
||||
{:size :sm
|
||||
:on-click (fn []
|
||||
(let [trimmed (string/trim url)]
|
||||
(if (string/blank? trimmed)
|
||||
(reset-url!)
|
||||
(if-not (config/valid-publish-server-url? trimmed)
|
||||
(notification/show! (t :settings.sync-server/url-invalid-error) :error)
|
||||
(do
|
||||
(config/set-custom-publish-server-url! trimmed)
|
||||
(notification/show! (t :settings-page/publish-server-url-saved) :success))))))}
|
||||
(t :ui/save))
|
||||
(when (seq url)
|
||||
(shui/button
|
||||
{:size :sm
|
||||
:variant :outline
|
||||
:on-click (fn [] (reset-url!))}
|
||||
(t :settings-page/publish-server-url-reset)))]]]))
|
||||
|
||||
(rum/defc publish-server-url-button
|
||||
[]
|
||||
(let [current-url (config/get-custom-publish-server-url)]
|
||||
(ui/button [:span.flex.items-center
|
||||
[:span.pr-1
|
||||
(if (seq current-url)
|
||||
current-url
|
||||
(t :settings-page/publish-server-url-default))]
|
||||
(ui/icon "edit")]
|
||||
:class "text-sm"
|
||||
:on-click #(state/pub-event! [:go/publish-server-settings]))))
|
||||
|
||||
(defn publish-server-url-row []
|
||||
(row-with-button-action
|
||||
{:left-label (t :settings-page/publish-server-url)
|
||||
:action (publish-server-url-button)}))
|
||||
|
||||
(rum/defc user-proxy-settings
|
||||
[{:keys [type protocol host port] :as agent-opts}]
|
||||
(ui/button [:span.flex.items-center
|
||||
@@ -774,6 +831,7 @@
|
||||
(usage-diagnostics-row t instrument-disabled?)
|
||||
(when-not (mobile-util/native-platform?) (developer-mode-row t developer-mode?))
|
||||
(sync-server-url-row)
|
||||
(publish-server-url-row)
|
||||
(when (util/electron?) (https-user-agent-row https-agent-opts))
|
||||
(when (util/electron?) (auto-chmod-row t))
|
||||
;; (clear-cache-row t)
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
(def USER-POOL-ID "us-east-1_dtagLnju8")
|
||||
(def IDENTITY-POOL-ID "us-east-1:d6d3b034-1631-402b-b838-b44513e93ee0")
|
||||
(def OAUTH-DOMAIN "logseq-prod.auth.us-east-1.amazoncognito.com")
|
||||
(def PUBLISH-API-BASE "https://logseq.io"))
|
||||
(def default-publish-api-base "https://logseq.io"))
|
||||
|
||||
(do (def API-DOMAIN "api-dev.logseq.com")
|
||||
(def COGNITO-IDP "https://cognito-idp.us-east-2.amazonaws.com/")
|
||||
@@ -42,10 +42,10 @@
|
||||
(def USER-POOL-ID "us-east-2_kAqZcxIeM")
|
||||
(def IDENTITY-POOL-ID "us-east-2:cc7d2ad3-84d0-4faf-98fe-628f6b52c0a5")
|
||||
(def OAUTH-DOMAIN "logseq-test2.auth.us-east-2.amazoncognito.com")
|
||||
(def PUBLISH-API-BASE "https://logseq-publish-staging.logseq.workers.dev")))
|
||||
(def default-publish-api-base "https://logseq-publish-staging.logseq.workers.dev")))
|
||||
|
||||
;; Enable for local development
|
||||
;; (def PUBLISH-API-BASE "http://localhost:8787")
|
||||
;; (def default-publish-api-base "http://localhost:8787")
|
||||
|
||||
(goog-define ENABLE-DB-SYNC-LOCAL false)
|
||||
(defonce db-sync-local? ENABLE-DB-SYNC-LOCAL)
|
||||
@@ -114,6 +114,43 @@
|
||||
(custom-url->http-base custom)
|
||||
default-db-sync-http-base))
|
||||
|
||||
(defn get-custom-publish-server-url
|
||||
"Read the user-configured custom publish server URL from localStorage.
|
||||
Returns nil when not set or empty."
|
||||
[]
|
||||
(when-not util/node-test?
|
||||
(let [v (.getItem js/localStorage "publish-server-url")]
|
||||
(when (and (string? v) (not (string/blank? v)))
|
||||
v))))
|
||||
|
||||
(defn set-custom-publish-server-url!
|
||||
"Persist the custom publish server URL to localStorage. Pass nil or empty string to clear."
|
||||
[url]
|
||||
(when-not util/node-test?
|
||||
(if (or (nil? url) (string/blank? url))
|
||||
(.removeItem js/localStorage "publish-server-url")
|
||||
(.setItem js/localStorage "publish-server-url" (string/trim url)))))
|
||||
|
||||
(defn valid-publish-server-url?
|
||||
"Return true when `url` looks like a valid HTTP(S) base URL."
|
||||
[url]
|
||||
(and (string? url)
|
||||
(re-find #"^https?://" url)))
|
||||
|
||||
(defn custom-url->publish-api-base
|
||||
"Normalize a custom publish base URL by stripping trailing slashes. Pure function."
|
||||
[custom-url]
|
||||
(string/replace custom-url #"/+$" ""))
|
||||
|
||||
(defn publish-api-base
|
||||
"Return the base URL for the single-page publish service. Uses the user-configured
|
||||
URL from localStorage when set, otherwise the default from the ENABLE-FILE-SYNC-PRODUCTION
|
||||
branch above. Read on each call so URL changes take effect without a restart."
|
||||
[]
|
||||
(if-let [custom (get-custom-publish-server-url)]
|
||||
(custom-url->publish-api-base custom)
|
||||
default-publish-api-base))
|
||||
|
||||
;; Feature flags
|
||||
;; =============
|
||||
|
||||
|
||||
@@ -22,19 +22,36 @@
|
||||
[reitit.frontend :as rf]
|
||||
[reitit.frontend.easy :as rfe]))
|
||||
|
||||
(defn set-router!
|
||||
(defn- build-router
|
||||
[]
|
||||
(.addEventListener js/window "popstate" route-handler/restore-scroll-pos)
|
||||
(rfe/start!
|
||||
(rf/router (plugins/hook-custom-routes routes/routes) nil)
|
||||
(fn [route]
|
||||
(route-handler/set-route-match! route)
|
||||
(plugin-handler/hook-plugin-app
|
||||
:route-changed (select-keys route [:template :path :parameters])))
|
||||
(rf/router (plugins/hook-custom-routes routes/routes) nil))
|
||||
|
||||
(defn- on-navigate
|
||||
[route]
|
||||
(route-handler/set-route-match! route)
|
||||
(plugin-handler/hook-plugin-app
|
||||
:route-changed (select-keys route [:template :path :parameters])))
|
||||
|
||||
(defn refresh-router!
|
||||
"Rebuilds the reitit router so route renderers registered by plugins after
|
||||
the app started take effect. Safe to call repeatedly; `rfe/start!` stops the
|
||||
previous history instance internally."
|
||||
[]
|
||||
(rfe/start!
|
||||
(build-router)
|
||||
on-navigate
|
||||
;; set to false to enable HistoryAPI
|
||||
{:use-fragment true}))
|
||||
|
||||
(defonce ^:private *popstate-installed? (atom false))
|
||||
|
||||
(defn set-router!
|
||||
[]
|
||||
(when (compare-and-set! *popstate-installed? false true)
|
||||
(.addEventListener js/window "popstate" route-handler/restore-scroll-pos))
|
||||
(refresh-router!)
|
||||
(plugin-handler/set-route-renderer-refresh-fn! refresh-router!))
|
||||
|
||||
(defn display-welcome-message
|
||||
[]
|
||||
(js/console.log
|
||||
|
||||
@@ -237,7 +237,8 @@
|
||||
:show-cloze
|
||||
{:show-cloze? true
|
||||
:hide-children? true}
|
||||
{:show-cloze? true})]
|
||||
{:show-cloze? true
|
||||
:ignore-block-collapsed? true})]
|
||||
(component-block/blocks-container option [block-entity]))
|
||||
[:div.mt-8.pb-2
|
||||
(if (contains? #{:show-cloze :show-answer} next-phase)
|
||||
|
||||
@@ -3455,7 +3455,8 @@
|
||||
[block config]
|
||||
(let [block (or (db/entity (:db/id block)) block)]
|
||||
(or
|
||||
(util/collapsed? block)
|
||||
(and (not (:ignore-block-collapsed? config))
|
||||
(util/collapsed? block))
|
||||
(and (util/mobile?) (ldb/class-instance? (entity-plus/entity-memoized (db/get-db) :logseq.class/Query) block))
|
||||
(and (or (:list-view? config) (:ref? config))
|
||||
(or (:block/_parent block) (:block.temp/has-children? block))
|
||||
@@ -3466,6 +3467,13 @@
|
||||
(or (ldb/page? block)
|
||||
(:table-block-title? config))))))
|
||||
|
||||
(defn load-children?
|
||||
[block temporary-collapsed-state ignore-block-collapsed?]
|
||||
(or ignore-block-collapsed?
|
||||
(not (if-some [result temporary-collapsed-state]
|
||||
result
|
||||
(:block/collapsed? block)))))
|
||||
|
||||
(defn batch-set-heading!
|
||||
[block-ids heading]
|
||||
(db-editor-handler/batch-set-heading! block-ids heading))
|
||||
|
||||
@@ -12,15 +12,9 @@
|
||||
[missionary.core :as m]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defn rtc-collaborators-dialog?
|
||||
[]
|
||||
(= :rtc-collaborators (state/get-modal-id)))
|
||||
|
||||
(defmethod events/handle :rtc/decrypt-user-e2ee-private-key [[_ encrypted-private-key]]
|
||||
(let [private-key-promise (p/deferred)
|
||||
refresh-token (str (state/get-auth-refresh-token))]
|
||||
(when-not (rtc-collaborators-dialog?)
|
||||
(shui/dialog-close-all!))
|
||||
(->
|
||||
(p/let [{:keys [password]} (state/<invoke-db-worker :thread-api/get-e2ee-password refresh-token)
|
||||
private-key (crypt/<decrypt-private-key password encrypted-private-key)]
|
||||
@@ -39,8 +33,6 @@
|
||||
|
||||
(defmethod events/handle :rtc/request-e2ee-password [[_]]
|
||||
(let [password-promise (p/deferred)]
|
||||
(when-not (rtc-collaborators-dialog?)
|
||||
(shui/dialog-close-all!))
|
||||
(shui/dialog-open!
|
||||
#(e2ee/e2ee-request-new-password password-promise)
|
||||
{:auto-width? true
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
(settings/sync-server-url-settings-container)
|
||||
{:id :sync-server-panel :center? true :class "lg:max-w-2xl"}))
|
||||
|
||||
(defmethod events/handle :go/publish-server-settings [[_]]
|
||||
(shui/dialog-open!
|
||||
(settings/publish-server-url-settings-container)
|
||||
{:id :publish-server-panel :center? true :class "lg:max-w-2xl"}))
|
||||
|
||||
(defmethod events/handle :redirect-to-home [_]
|
||||
(page-handler/create-today-journal!)
|
||||
(when (util/capacitor?)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,15 +30,15 @@
|
||||
|
||||
(defn- publish-endpoint
|
||||
[]
|
||||
(str config/PUBLISH-API-BASE "/pages"))
|
||||
(str (config/publish-api-base) "/pages"))
|
||||
|
||||
(defn- publish-page-endpoint
|
||||
[graph-uuid page-uuid]
|
||||
(str config/PUBLISH-API-BASE "/pages/" graph-uuid "/" page-uuid))
|
||||
(str (config/publish-api-base) "/pages/" graph-uuid "/" page-uuid))
|
||||
|
||||
(defn- asset-upload-endpoint
|
||||
[]
|
||||
(str config/PUBLISH-API-BASE "/assets"))
|
||||
(str (config/publish-api-base) "/assets"))
|
||||
|
||||
(defn- asset-content-type
|
||||
[ext]
|
||||
@@ -382,10 +382,11 @@
|
||||
data (bean/->clj json)]
|
||||
(let [short-url (:short_url data)
|
||||
page-id (str (:block/uuid page))
|
||||
api-base (config/publish-api-base)
|
||||
fallback-url (when (and graph-id page-id)
|
||||
(str config/PUBLISH-API-BASE "/page/" graph-id "/" page-id))
|
||||
(str api-base "/page/" graph-id "/" page-id))
|
||||
url (or (when short-url
|
||||
(str config/PUBLISH-API-BASE short-url))
|
||||
(str api-base short-url))
|
||||
fallback-url)]
|
||||
(when (and url (:db/id page))
|
||||
(property-handler/set-block-property! (:db/id page)
|
||||
|
||||
@@ -50,7 +50,8 @@
|
||||
[block & {:as opts}]
|
||||
(op-transact!
|
||||
(when-let [block' (if (de/entity? block)
|
||||
(dissoc (.-kv ^js block) :db/id)
|
||||
(-> (dissoc (.-kv ^js block) :db/id)
|
||||
(assoc :block/uuid (:block/uuid block)))
|
||||
block)]
|
||||
[:save-block [block' opts]])))
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
(s/def :copy/export-block-text-remove-options set?)
|
||||
(s/def :copy/export-block-text-other-options map?)
|
||||
(s/def ::sync-server-url string?)
|
||||
(s/def ::publish-server-url string?)
|
||||
;; Dynamic keys which aren't as easily validated:
|
||||
;; :ls-pdf-last-page-*
|
||||
;; :ls-js-allowed-*
|
||||
@@ -68,4 +69,5 @@
|
||||
:copy/export-block-text-indent-style
|
||||
:copy/export-block-text-remove-options
|
||||
:copy/export-block-text-other-options
|
||||
::sync-server-url]))
|
||||
::sync-server-url
|
||||
::publish-server-url]))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns logseq.sdk.experiments
|
||||
(:require [frontend.components.page :as page]
|
||||
[frontend.handler.plugin :as plugin-handler]
|
||||
[lambdaisland.glogi :as log]
|
||||
[frontend.state :as state]
|
||||
[frontend.util :as util]
|
||||
[logseq.sdk.utils :as sdk-util]))
|
||||
@@ -11,43 +12,89 @@
|
||||
page-name (some-> props1 :page)]
|
||||
(when-let [entity (page/get-page-entity page-name)]
|
||||
(page/page-blocks-cp
|
||||
entity {:container-id (state/get-next-container-id)}))))
|
||||
entity {:container-id (state/get-next-container-id)}))))
|
||||
|
||||
(defn ^:export register_fenced_code_renderer
|
||||
[pid type ^js opts]
|
||||
(when-let [^js _pl (plugin-handler/get-plugin-inst pid)]
|
||||
(plugin-handler/register-fenced-code-renderer
|
||||
(keyword pid) type (reduce #(assoc %1 %2 (aget opts (name %2))) {}
|
||||
[:edit :before :subs :render]))))
|
||||
(keyword pid) type (reduce #(assoc %1 %2 (aget opts (name %2))) {}
|
||||
[:edit :before :subs :render]))))
|
||||
|
||||
(defn ^:export register_route_renderer
|
||||
[pid key ^js opts]
|
||||
(when-let [^js _pl (plugin-handler/get-plugin-inst pid)]
|
||||
(let [key (util/safe-keyword key)]
|
||||
(plugin-handler/register-route-renderer
|
||||
(keyword pid) key
|
||||
(reduce (fn [r k]
|
||||
(assoc r k (cond-> (aget opts (name k))
|
||||
(= :name k)
|
||||
(#(if % (util/safe-keyword %) key)))))
|
||||
{} [:v :name :path :subs :render])))))
|
||||
(keyword pid) key
|
||||
(reduce (fn [r k]
|
||||
(assoc r k (cond-> (aget opts (name k))
|
||||
(= :name k)
|
||||
(#(if % (util/safe-keyword %) key)))))
|
||||
{} [:v :name :path :subs :render])))))
|
||||
|
||||
(defn ^:export register_daemon_renderer
|
||||
[pid key ^js opts]
|
||||
(when-let [^js _pl (plugin-handler/get-plugin-inst pid)]
|
||||
(plugin-handler/register-daemon-renderer
|
||||
(keyword pid) key (reduce #(assoc %1 %2 (aget opts (name %2))) {}
|
||||
[:before :subs :render]))))
|
||||
(keyword pid) key (reduce #(assoc %1 %2 (aget opts (name %2))) {}
|
||||
[:before :subs :render]))))
|
||||
|
||||
(defn- extract-js-renderer-opts
|
||||
"Extract keys from a JS opts object into a clj map.
|
||||
`transforms` is an optional map of {keyword transform-fn} for per-key processing.
|
||||
Keys whose JS value is nil/undefined are omitted."
|
||||
[^js opts ks transforms]
|
||||
(reduce (fn [r k]
|
||||
(let [v (aget opts (name k))]
|
||||
(if (some? v)
|
||||
(assoc r k (if-let [xf (get transforms k)]
|
||||
(xf v)
|
||||
v))
|
||||
r)))
|
||||
{} ks))
|
||||
|
||||
(defn ^:export register_hosted_renderer
|
||||
[pid key ^js opts]
|
||||
(when-let [^js _pl (plugin-handler/get-plugin-inst pid)]
|
||||
(plugin-handler/register-hosted-renderer
|
||||
(keyword pid) key (reduce #(assoc %1 %2 (aget opts (name %2))) {}
|
||||
[:title :type :mode :subs :render]))))
|
||||
(keyword pid) key
|
||||
(extract-js-renderer-opts opts [:title :type :mode :subs :render] nil))))
|
||||
|
||||
(defn ^:export register_block_properties_renderer
|
||||
[pid key ^js opts]
|
||||
(when-let [^js _pl (plugin-handler/get-plugin-inst pid)]
|
||||
(let [clj-opts (extract-js-renderer-opts
|
||||
opts
|
||||
[:when :mode :priority :subs :render]
|
||||
{:when (fn [v]
|
||||
(if (fn? v)
|
||||
v
|
||||
(js->clj v :keywordize-keys true)))})]
|
||||
(plugin-handler/register-hosted-renderer
|
||||
(keyword pid) key (assoc clj-opts :type "block-properties")))))
|
||||
|
||||
(defn ^:export register_block_renderer
|
||||
[pid key ^js opts]
|
||||
(when-let [^js _pl (plugin-handler/get-plugin-inst pid)]
|
||||
(let [when-predicate (aget opts "when")]
|
||||
(if (and (some? when-predicate) (not (fn? when-predicate)))
|
||||
(log/error :register-block-renderer-invalid-when
|
||||
{:pid pid
|
||||
:key key
|
||||
:message "`when` for registerBlockRenderer must be a synchronous predicate function."})
|
||||
(let [include-children (aget opts "includeChildren")
|
||||
clj-opts (extract-js-renderer-opts
|
||||
opts
|
||||
[:when :priority :subs :render]
|
||||
nil)]
|
||||
(plugin-handler/register-hosted-renderer
|
||||
(keyword pid) key (cond-> (assoc clj-opts :type "block")
|
||||
(some? include-children)
|
||||
(assoc :include-children include-children))))))))
|
||||
|
||||
(defn ^:export register_extensions_enhancer
|
||||
[pid type enhancer]
|
||||
(when-let [^js _pl (and (fn? enhancer) (plugin-handler/get-plugin-inst pid))]
|
||||
(plugin-handler/register-extensions-enhancer
|
||||
(keyword pid) type {:enhancer enhancer})))
|
||||
(keyword pid) type {:enhancer enhancer})))
|
||||
|
||||
Reference in New Issue
Block a user