Merge branch 'feat/db' into enhance/plugin-web

This commit is contained in:
charlie
2024-12-25 08:58:49 +08:00
94 changed files with 2368 additions and 1666 deletions

View File

@@ -21,13 +21,14 @@
:cell (fn [_table row _column]
(component-block/page-cp {} row))
:type :string}
{:id :block/type
:name "Type"
:cell (fn [_table row _column]
(let [type (get row :block/type)]
[:div.capitalize (if (= type "class") "tag" type)]))
:get-value (fn [row] (get row :block/type))
:type :string}
(when (not (config/db-based-graph? (state/get-current-repo)))
{:id :block/type
:name "Page type"
:cell (fn [_table row _column]
(let [type (get row :block/type)]
[:div.capitalize type]))
:get-value (fn [row] (get row :block/type))
:type :string})
{:id :block.temp/refs-count
:name (t :page/backlinks)
:cell (fn [_table row _column] (:block.temp/refs-count row))

View File

@@ -20,7 +20,6 @@
[frontend.components.query :as query]
[frontend.components.query.builder :as query-builder-component]
[frontend.components.svg :as svg]
[frontend.components.title :as title]
[frontend.components.select :as select]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
@@ -701,7 +700,9 @@
(when (and show-icon? (not tag?))
(let [own-icon (get page-entity (pu/get-pid :logseq.property/icon))
emoji? (and (map? own-icon) (= (:type own-icon) :emoji))]
(when-let [icon (icon-component/get-node-icon-cp page-entity {:color? true :not-text-or-page? true})]
(when-let [icon (icon-component/get-node-icon-cp page-entity {:color? true
:not-text-or-page? true
:own-icon? true})]
[:span {:class (str "icon-emoji-wrap " (when emoji? "as-emoji"))}
icon])))
[:span
@@ -722,7 +723,7 @@
(->elem :span (map-inline config label))
show-unique-title?
(title/block-unique-title page-entity)
(block-handler/block-unique-title page-entity)
:else
(let [title (:block/title page-entity)
@@ -755,7 +756,7 @@
(db-content/content-id-ref->page s (:block/refs page-entity))
:else
s)
s (if tag? (str "#" s) s)]
s (if (and tag? (not (:hide-tag-symbol? config))) (str "#" s) s)]
(if (ldb/page? page-entity)
s
(block-title config page-entity))))]
@@ -2588,8 +2589,9 @@
(rum/defcs block-tag <
(rum/local false ::hover?)
[state block tag config popup-opts]
(let [*hover? (::hover? state)]
[:div.block-tag
(let [*hover? (::hover? state)
hover? @*hover?]
[:div.block-tag.items-center
{:key (str "tag-" (:db/id tag))
:on-mouse-over #(reset! *hover? true)
:on-mouse-out #(reset! *hover? false)
@@ -2614,18 +2616,23 @@
:on-click #(db-property-handler/delete-property-value! (:db/id block) :block/tags (:db/id tag))}
"Remove tag")])
popup-opts))}
(if (and hover? (not (ldb/private-tags (:db/ident tag))))
[:a.inline.close.flex.transition-opacity.duration-300.ease-in
{:class (if @*hover? "!opacity-100" "!opacity-0")
:title "Remove this tag"
:on-pointer-down
(fn [e]
(util/stop e)
(db-property-handler/delete-property-value! (:db/id block) :block/tags (:db/id tag)))}
(ui/icon "x" {:size 14
:style {:margin-top 1}})]
[:a.hash-symbol {:style {:margin-left 5}}
"#"])
(page-cp (assoc config
:disable-preview? true
:tag? true
:disable-preview? true)
tag)
[:a.close.flex.transition-opacity.duration-300.ease-in
{:class (if @*hover? "!opacity-100" "!opacity-0")
:title "Remove this tag"
:on-pointer-down
(fn [e]
(util/stop e)
(db-property-handler/delete-property-value! (:db/id block) :block/tags (:db/id tag)))}
(ui/icon "x" {:size 15})]]))
:hide-tag-symbol? true)
tag)]))
(rum/defc tags-cp
"Tags without inline or hidden tags"
@@ -2635,14 +2642,15 @@
(:block/tags block)
(remove (fn [t]
(or (ldb/inline-tag? (:block/raw-title block) t)
(:logseq.property.class/hide-from-node t)))))
(:logseq.property.class/hide-from-node t)
(contains? ldb/internal-tags (:db/ident t))))))
popup-opts {:align :end
:content-props {:on-click (fn [] (shui/popup-hide!))
:class "w-60"}}
tags-count (count block-tags)]
(when (seq block-tags)
(if (< tags-count 3)
[:div.block-tags
[:div.block-tags.gap-1
(for [tag block-tags]
(rum/with-key
(block-tag block tag config popup-opts)
@@ -2653,13 +2661,14 @@
(fn []
(for [tag block-tags]
[:div.flex.flex-row.items-center.gap-1
(shui/button
{:title "Remove tag"
:variant :ghost
:class "!p-1 text-muted-foreground"
:size :sm
:on-click #(db-property-handler/delete-property-value! (:db/id block) :block/tags (:db/id tag))}
(ui/icon "X" {:size 14}))
(when-not (ldb/private-tags (:db/ident tag))
(shui/button
{:title "Remove tag"
:variant :ghost
:class "!p-1 text-muted-foreground"
:size :sm
:on-click #(db-property-handler/delete-property-value! (:db/id block) :block/tags (:db/id tag))}
(ui/icon "X" {:size 14})))
(page-cp (assoc config
:tag? true
:disable-preview? true
@@ -2672,7 +2681,7 @@
:tag? true
:disable-preview? true
:disable-click? true) tag)])
[:div.text-sm.opacity-50
[:div.text-sm.opacity-50.ml-1
(str "+" (- tags-count 2))]])))))
(rum/defc block-positioned-properties

View File

@@ -949,7 +949,7 @@ html.is-mac {
.positioned-properties.block-right {
button {
@apply whitespace-nowrap mr-0.5;
@apply whitespace-nowrap;
}
.block-title-wrap {
@@ -970,14 +970,18 @@ html.is-mac {
}
.block-tag {
@apply pr-1 flex flex-row items-center gap-1;
@apply flex flex-row items-center;
}
.block-tag a.tag {
@apply flex text-sm font-normal items-center opacity-70;
}
.block-tag a.tag:hover {
.block-tag a.hash-symbol {
@apply text-sm text-sm font-normal opacity-70;
}
.block-tag a.tag:hover, .block-tag a.hash-symbol:hover {
@apply opacity-100;
}
@@ -1013,7 +1017,7 @@ html.is-mac {
}
.ls-page-title .block-tags {
@apply relative -right-1 min-h-full;
@apply relative min-h-full;
}
.ls-code-editor-wrap {

View File

@@ -4,7 +4,6 @@
[electron.ipc :as ipc]
[frontend.components.block :as block]
[frontend.components.cmdk.list-item :as list-item]
[frontend.components.title :as title]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.db :as db]
@@ -18,6 +17,7 @@
[frontend.handler.page :as page-handler]
[frontend.handler.route :as route-handler]
[frontend.handler.whiteboard :as whiteboard-handler]
[frontend.handler.block :as block-handler]
[frontend.mixins :as mixins]
[frontend.modules.shortcut.core :as shortcut]
[frontend.modules.shortcut.utils :as shortcut-utils]
@@ -235,7 +235,7 @@
"whiteboard"
:else
"page")
title (title/block-unique-title page)
title (block-handler/block-unique-title page)
title' (if source-page (str title " -> alias: " (:block/title source-page)) title)]
(hash-map :icon icon
:icon-theme :gray
@@ -245,7 +245,7 @@
(defn- block-item
[repo block current-page !input]
(let [id (:block/uuid block)
text (title/block-unique-title block)
text (block-handler/block-unique-title block)
icon "letter-n"]
{:icon icon
:icon-theme :gray

View File

@@ -15,7 +15,6 @@
[frontend.components.block :as block]
[dommy.core :as d]
[frontend.components.content :as cp-content]
[frontend.components.title :as title]
[frontend.config :as config]
[frontend.context.i18n :refer [t tt]]
[frontend.db :as db]
@@ -32,6 +31,7 @@
[frontend.handler.user :as user-handler]
[frontend.handler.whiteboard :as whiteboard-handler]
[frontend.handler.recent :as recent-handler]
[frontend.handler.block :as block-handler]
[frontend.mixins :as mixins]
[frontend.mobile.action-bar :as action-bar]
[frontend.mobile.footer :as footer]
@@ -139,7 +139,7 @@
:class "w-60"}})
(util/stop e))}
(ldb/object? page)
(assoc :title (title/block-unique-title page)))
(assoc :title (block-handler/block-unique-title page)))
[:span.page-icon icon]
[:span.page-title {:class (when untitled? "opacity-50")
:style {:display "ruby"}}
@@ -214,7 +214,7 @@
db-based?
(concat [:tag/tasks :tag/assets])
(not db-based?)
(#(cons :whiteboards %)) )
(#(cons :whiteboards %)))
[checked-navs set-checked-navs!] (rum/use-state (or (storage/get :ls-sidebar-navigations)
[:whiteboards :flashcards :graph-view :all-pages]))]
@@ -362,7 +362,7 @@
(for [page pages]
[:li.recent-item.select-none.font-medium
{:key (str "recent-" (:db/id page))
:title (title/block-unique-title page)
:title (block-handler/block-unique-title page)
:draggable true
:on-drag-start (fn [event] (editor-handler/block->data-transfer! (:block/name page) event true))
:data-ref name}

View File

@@ -446,7 +446,7 @@
flex: 1;
.page {
@apply px-6;
@apply px-4;
}
}

View File

@@ -4,21 +4,22 @@
[frontend.db :as db]
[frontend.db-mixins :as db-mixins]
[logseq.shui.ui :as shui]
[rum.core :as rum]))
[rum.core :as rum]
[frontend.util :as util]))
(rum/defc configure-property < rum/reactive db-mixins/query
[page]
(let [page (db/sub-block (:db/id page))]
[:div.pb-4.-ml-1
(shui/button
{:variant "ghost"
:class "opacity-50 hover:opacity-90"
:size :sm
:on-click (fn [^js e]
(shui/popup-show! (.-target e)
(fn []
(property-config/dropdown-editor page nil {:debug? (.-altKey e)}))
{:content-props {:class "ls-property-dropdown-editor as-root"}
:align "start"
:as-dropdown? true}))}
"Configure property")]))
(shui/tabs-trigger
{:value "configure"
:class "py-1 text-xs"
:on-pointer-down (fn [e]
(util/stop e))
:on-click (fn [^js e]
(shui/popup-show! (.-target e)
(fn []
(property-config/dropdown-editor page nil {:debug? (.-altKey e)}))
{:content-props {:class "ls-property-dropdown-editor as-root"}
:align "start"
:as-dropdown? true}))}
"Configure property")))

View File

@@ -6,7 +6,6 @@
[frontend.components.file-based.datetime :as datetime-comp]
[frontend.components.search :as search]
[frontend.components.svg :as svg]
[frontend.components.title :as title]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.date :as date]
@@ -19,6 +18,7 @@
[frontend.handler.paste :as paste-handler]
[frontend.handler.property.util :as pu]
[frontend.handler.search :as search-handler]
[frontend.handler.block :as block-handler]
[frontend.mixins :as mixins]
[frontend.search :refer [fuzzy-search]]
[frontend.state :as state]
@@ -35,7 +35,8 @@
[logseq.shui.ui :as shui]
[promesa.core :as p]
[react-draggable]
[rum.core :as rum]))
[rum.core :as rum]
[logseq.db.frontend.class :as db-class]))
(defn filter-commands
[page? commands]
@@ -156,7 +157,12 @@
;; reorder, shortest and starts-with first.
(let [matched-pages-with-new-page
(fn [partial-matched-pages]
(if (or (db/page-exists? q (if db-tag? "class" "page"))
(if (or (db/page-exists? q (if db-tag?
#{:logseq.class/Tag}
;; Page existence here should be the same as entity-util/page?.
;; Don't show 'New page' if a page has any of these tags
(into #{:logseq.class/Page :logseq.class/Tag :logseq.class/Property}
db-class/page-children-classes)))
(and db-tag? (some ldb/class? (:block/_alias (db/get-page q)))))
partial-matched-pages
(if db-tag?
@@ -215,7 +221,7 @@
(if (ldb/class? target)
(str (:block/title block) " -> alias: " (:block/title target))
(:block/title block)))
(title/block-unique-title block))]
(block-handler/block-unique-title block))]
(search-handler/highlight-exact-query title q))]]))
:empty-placeholder [:div.text-gray-500.text-sm.px-4.py-2 (if db-tag?
"Search for a tag"

View File

@@ -693,7 +693,7 @@
[:div.cp__file-sync-page-histories-right
[:h1.title.text-xl
"Current version"]
(page/page-blocks-cp (state/get-current-repo) page-entity nil)]
(page/page-blocks-cp page-entity nil)]
;; ready loading
[:div.flex.items-center.h-full.justify-center.w-full.absolute.ready-loading

View File

@@ -61,7 +61,9 @@
(defn get-node-icon-cp
[node-entity opts]
(let [opts' (merge {:size 14} opts)
node-icon (get-node-icon node-entity)]
node-icon (if (:own-icon? opts)
(get node-entity (pu/get-pid :logseq.property/icon))
(get-node-icon node-entity))]
(when-not (or (string/blank? node-icon) (and (contains? #{"letter-n" "page"} node-icon) (:not-text-or-page? opts)))
[:div.icon-cp-container.flex.items-center
(merge {:style {:color (or (:color node-icon) "inherit")}}

View File

@@ -154,53 +154,45 @@
(if loading?
(ui/skeleton)
[:div.flex.flex-col.gap-2.mt-2
(ui/foldable
[:div.font-medium.opacity-60.as-toggle
"Tagged Nodes"]
(fn []
[:div.mt-2
(views/view view-entity {:config config
:data data
:set-data! set-data!
:views-title (class-views class views view-entity {:set-view-entity! set-view-entity!
:set-views! set-views!})
:columns columns
:add-new-object! (if (= :logseq.class/Asset (:db/ident class))
(fn [_e]
(shui/dialog-open!
(fn []
[:div.flex.flex-col.gap-2
[:div.font-medium "Add assets"]
(filepicker/picker
{:on-change (fn [_e files]
(p/do!
(editor-handler/upload-asset! nil files :markdown editor-handler/*asset-uploading? true)
(set-data! (get-class-objects class))
(shui/dialog-close!)))})])))
#(add-new-class-object! class set-data!))
:show-add-property? true
:add-property! (fn []
(state/pub-event! [:editor/new-property {:block class
:class-schema? true}]))
:on-delete-rows (fn [table selected-rows]
(let [pages (filter ldb/page? selected-rows)
blocks (remove ldb/page? selected-rows)]
(p/do!
(ui-outliner-tx/transact!
{:outliner-op :delete-blocks}
(when (seq blocks)
(outliner-op/delete-blocks! blocks nil))
(let [page-ids (map :db/id pages)
tx-data (map (fn [pid] [:db/retract pid :block/tags (:db/id class)]) page-ids)]
(when (seq tx-data)
(outliner-op/transact! tx-data {:outliner-op :save-block}))))
(set-data! (get-class-objects class))
(when-let [f (get-in table [:data-fns :set-row-selection!])]
(f {})))))})])
{:disable-on-pointer-down? true
:default-collapsed? (:sidebar? config)})])))
(views/view view-entity
{:config config
:data data
:set-data! set-data!
:views-title (class-views class views view-entity {:set-view-entity! set-view-entity!
:set-views! set-views!})
:columns columns
:add-new-object! (if (= :logseq.class/Asset (:db/ident class))
(fn [_e]
(shui/dialog-open!
(fn []
[:div.flex.flex-col.gap-2
[:div.font-medium "Add assets"]
(filepicker/picker
{:on-change (fn [_e files]
(p/do!
(editor-handler/upload-asset! nil files :markdown editor-handler/*asset-uploading? true)
(set-data! (get-class-objects class))
(shui/dialog-close!)))})])))
#(add-new-class-object! class set-data!))
:show-add-property? true
:add-property! (fn []
(state/pub-event! [:editor/new-property {:block class
:class-schema? true}]))
:on-delete-rows (fn [table selected-rows]
(let [pages (filter ldb/page? selected-rows)
blocks (remove ldb/page? selected-rows)]
(p/do!
(ui-outliner-tx/transact!
{:outliner-op :delete-blocks}
(when (seq blocks)
(outliner-op/delete-blocks! blocks nil))
(let [page-ids (map :db/id pages)
tx-data (map (fn [pid] [:db/retract pid :block/tags (:db/id class)]) page-ids)]
(when (seq tx-data)
(outliner-op/transact! tx-data {:outliner-op :save-block}))))
(set-data! (get-class-objects class))
(when-let [f (get-in table [:data-fns :set-row-selection!])]
(f {})))))}))))
(rum/defcs class-objects < rum/reactive db-mixins/query mixins/container-id
[state class {:keys [current-page? sidebar?]}]
@@ -251,35 +243,32 @@
[])
(when (false? loading?)
(ui/foldable
[:div.font-medium.opacity-50 "Nodes with Property"]
[:div.mt-2
(views/view view-entity {:config config
:data data
:set-data! set-data!
:title-key :views.table/property-nodes
:columns columns
:add-new-object! #(add-new-property-object! property set-data!)
(views/view view-entity
{:config config
:data data
:set-data! set-data!
:title-key :views.table/property-nodes
:columns columns
:add-new-object! #(add-new-property-object! property set-data!)
;; TODO: Add support for adding column
:show-add-property? false
:on-delete-rows (when-not (contains? #{:logseq.property/built-in? :logseq.property/parent}
(:db/ident property))
(fn [table selected-rows]
(let [pages (filter ldb/page? selected-rows)
blocks (remove ldb/page? selected-rows)]
(p/do!
(ui-outliner-tx/transact!
{:outliner-op :delete-blocks}
(when (seq blocks)
(outliner-op/delete-blocks! blocks nil))
(let [page-ids (map :db/id pages)
tx-data (map (fn [pid] [:db/retract pid (:db/ident property)]) page-ids)]
(when (seq tx-data)
(outliner-op/transact! tx-data {:outliner-op :save-block}))))
(set-data! (get-property-related-objects (state/get-current-repo) property))
(when-let [f (get-in table [:data-fns :set-row-selection!])]
(f {}))))))})]
{:disable-on-pointer-down? true}))))
:show-add-property? false
:on-delete-rows (when-not (contains? #{:logseq.property/built-in? :logseq.property/parent}
(:db/ident property))
(fn [table selected-rows]
(let [pages (filter ldb/page? selected-rows)
blocks (remove ldb/page? selected-rows)]
(p/do!
(ui-outliner-tx/transact!
{:outliner-op :delete-blocks}
(when (seq blocks)
(outliner-op/delete-blocks! blocks nil))
(let [page-ids (map :db/id pages)
tx-data (map (fn [pid] [:db/retract pid (:db/ident property)]) page-ids)]
(when (seq tx-data)
(outliner-op/transact! tx-data {:outliner-op :save-block}))))
(set-data! (get-property-related-objects (state/get-current-repo) property))
(when-let [f (get-in table [:data-fns :set-row-selection!])]
(f {}))))))}))))
;; Show all nodes containing the given property
(rum/defcs property-related-objects < rum/reactive db-mixins/query mixins/container-id

View File

@@ -196,7 +196,7 @@
(rum/defcs page-blocks-cp < rum/reactive db-mixins/query
{:will-mount (fn [state]
(let [page-e (second (:rum/args state))
(let [page-e (first (:rum/args state))
page-name (:block/name page-e)]
(when (and page-name
(db/journal-page? page-name)
@@ -204,7 +204,7 @@
(date/journal-title->int (date/today))))
(state/pub-event! [:journal/insert-template page-name])))
state)}
[state repo page-e {:keys [sidebar? whiteboard?] :as config}]
[state page-e {:keys [sidebar? whiteboard?] :as config}]
(when page-e
(let [page-name (or (:block/name page-e)
(str (:block/uuid page-e)))
@@ -220,44 +220,35 @@
(remove (fn [b] (some? (get b (:db/ident block)))) children)
:else
children)
db-based? (config/db-based-graph? repo)]
[:<>
(let [blocks (cond
(and
(not block?)
(empty? children) page-e)
(dummy-block page-e)
children)]
(cond
(and
(not block?)
(empty? children) page-e)
(dummy-block page-e)
:else
(let [document-mode? (state/sub :document/mode?)
hiccup-config (merge
{:id (if block? (str block-id) page-name)
:db/id (:db/id block)
:block? block?
:editor-box editor/box
:document/mode? document-mode?}
config)
config (common-handler/config-with-document-mode hiccup-config)
blocks (if block? [block] (db/sort-by-order children block))]
(let [add-button? (not (or config/publishing?
(let [last-child-id (model/get-block-deep-last-open-child-id (db/get-db) (:db/id (last blocks)))
block' (if last-child-id (db/entity last-child-id) (last blocks))]
(string/blank? (:block/title block')))))]
[:div
{:class (when add-button? "show-add-button")}
(page-blocks-inner page-e blocks config sidebar? whiteboard? block-id)
(let [args (if block-id
{:block-uuid block-id}
{:page page-name})]
(add-button args (:container-id config)))])))]
(if (and db-based? (or (ldb/class? block) (ldb/property? block)))
[:div.mt-4.ml-2.-mb-1
(ui/foldable
[:div.font-medium.as-toggle {:class "pl-0.5"} "Notes"]
[:div.ml-1.-mb-2 blocks]
{:disable-on-pointer-down? true})]
blocks))])))
:else
(let [document-mode? (state/sub :document/mode?)
hiccup-config (merge
{:id (if block? (str block-id) page-name)
:db/id (:db/id block)
:block? block?
:editor-box editor/box
:document/mode? document-mode?}
config)
config (common-handler/config-with-document-mode hiccup-config)
blocks (if block? [block] (db/sort-by-order children block))]
(let [add-button? (not (or config/publishing?
(let [last-child-id (model/get-block-deep-last-open-child-id (db/get-db) (:db/id (last blocks)))
block' (if last-child-id (db/entity last-child-id) (last blocks))]
(string/blank? (:block/title block')))))]
[:div
{:class (when add-button? "show-add-button")}
(page-blocks-inner page-e blocks config sidebar? whiteboard? block-id)
(let [args (if block-id
{:block-uuid block-id}
{:page page-name})]
(add-button args (:container-id config)))]))))))
(rum/defc today-queries < rum/reactive
[repo today? sidebar?]
@@ -301,10 +292,11 @@
(rum/defc page-title-editor < rum/reactive
[page {:keys [*input-value *title-value *edit? untitled? page-name old-name whiteboard-page?]}]
(let [input-ref (rum/create-ref)
tag-idents (map :db/ident (:block/tags page))
collide? #(and (not= (util/page-name-sanity-lc page-name)
(util/page-name-sanity-lc @*title-value))
(db/page-exists? page-name (:block/type page))
(db/page-exists? @*title-value (:block/type page)))
(db/page-exists? page-name tag-idents)
(db/page-exists? @*title-value tag-idents))
rollback-fn #(let [old-name (if untitled? "" old-name)]
(reset! *title-value old-name)
(gobj/set (rum/deref input-ref) "value" old-name)
@@ -559,6 +551,65 @@
(plugins/hook-ui-slot :page-head-actions-slotted nil)
(plugins/hook-ui-items :pagebar)])))
(rum/defc tabs
[page opts]
(let [class? (ldb/class? page)
property? (ldb/property? page)
both? (and class? property?)
default-tab (cond
both?
"tag"
class?
"tag"
:else
"property")]
[:div.page-tabs
(shui/tabs
{:defaultValue default-tab
:class (str "w-full")}
(when (or both? property?)
[:div.flex.flex-row.gap-1.items-center
(shui/tabs-list
{:class "h-8"}
(when class?
(shui/tabs-trigger
{:value "tag"
:class "py-1 text-xs"}
"Tagged nodes"))
(when property?
(shui/tabs-trigger
{:value "property"
:class "py-1 text-xs"}
"Nodes with property"))
(when property?
(db-page/configure-property page)))])
(when class?
(shui/tabs-content
{:value "tag"}
(objects/class-objects page opts)))
(when property?
(shui/tabs-content
{:value "property"}
(objects/property-related-objects page (:current-page? opts)))))]))
(rum/defc sidebar-page-properties
[config page]
(let [[collapsed? set-collapsed!] (rum/use-state true)]
[:div.ls-sidebar-page-properties.flex.flex-col.gap-2.mt-2
[:div
(shui/button
{:variant :ghost
:size :sm
:class "px-1 text-muted-foreground"
:on-click #(set-collapsed! (not collapsed?))}
[:span.text-xs (str (if collapsed? "Open" "Hide")) " properties"])]
(when-not collapsed?
[:<>
(component-block/db-properties-cp config page {:sidebar-properties? true})
[:hr.my-4]])]))
;; A page is just a logical block
(rum/defcs ^:large-vars/cleanup-todo page-inner < rum/reactive db-mixins/query mixins/container-id
(rum/local false ::all-collapsed?)
@@ -605,9 +656,8 @@
(if (and whiteboard-page? (not sidebar?))
[:div ((state/get-component :whiteboard/tldraw-preview) (:block/uuid page))] ;; FIXME: this is not reactive
[:div.relative.page-inner
(when (or (and db-based? (not block?))
(and (not db-based?) (not sidebar?) (not block?)))
[:div.relative.grid.gap-8.page-inner
(when-not (or block? sidebar?)
[:div.flex.flex-row.space-between
(when (and (or (mobile-util/native-platform?) (util/mobile?)) (not db-based?))
[:div.flex.flex-row.pr-2
@@ -625,14 +675,9 @@
:preview? preview?})))
(lsp-pagebar-slot)])
(when (and db-based? (ldb/property? page))
(db-page/configure-property page))
(when (and db-based? class-page?)
(objects/class-objects page {:current-page? option :sidebar? sidebar?}))
(when (and db-based? (ldb/property? page))
(objects/property-related-objects page (:current-page? option)))
(when (and db-based? sidebar?)
[:div.-mb-8
(sidebar-page-properties config page)])
(when (and block? (not sidebar?) (not whiteboard?))
(let [config (merge config {:id "block-parent"
@@ -640,10 +685,13 @@
[:div.mb-4
(component-block/breadcrumb config repo block-id {:level-limit 3})]))
(when (and db-based? (or class-page? (ldb/property? page)))
(tabs page {:current-page? option :sidebar? sidebar?}))
[:div.ls-page-blocks
(page-blocks-cp repo page (merge option {:sidebar? sidebar?
:container-id (:container-id state)
:whiteboard? whiteboard?}))]])
(page-blocks-cp page (merge option {:sidebar? sidebar?
:container-id (:container-id state)
:whiteboard? whiteboard?}))]])
(when (not preview?)
[:div {:style {:padding-left 9}}

View File

@@ -65,8 +65,6 @@
@apply rounded-sm;
&.title {
@apply mb-3;
.block-main-container {
@apply gap-2;
}
@@ -258,5 +256,5 @@ html.is-native-ios {
}
.ls-page-blocks {
@apply min-h-[60px] -mb-2;
}
@apply min-h-[60px];
}

View File

@@ -13,6 +13,7 @@
[frontend.handler.property.util :as pu]
[frontend.config :as config]
[frontend.db :as db]
[frontend.db.model :as db-model]
[frontend.db-mixins :as db-mixins]
[frontend.db.async :as db-async]
[frontend.handler.db-based.property :as db-property-handler]
@@ -99,7 +100,8 @@
add-class-property? (and (ldb/class? block) class-schema?)]
(when *property (reset! *property property))
(p/do!
(when *show-new-property-config? (reset! *show-new-property-config? false))
(when *show-new-property-config?
(reset! *show-new-property-config? false))
(when (= (:type schema) :node) (reset! *show-class-select? true))
(components-pu/update-property! property property-name schema)
(cond
@@ -142,30 +144,42 @@
(rum/defc property-select
[exclude-properties select-opts]
(let [[properties set-properties!] (rum/use-state nil)
[classes set-classes!] (rum/use-state nil)
[excluded-properties set-excluded-properties!] (rum/use-state nil)]
(rum/use-effect!
(fn []
(p/let [properties (db-async/<db-based-get-all-properties (state/get-current-repo))]
(p/let [repo (state/get-current-repo)
properties (db-async/<db-based-get-all-properties repo)
classes (->> (db-model/get-all-classes repo)
(remove ldb/built-in?))]
(set-classes! classes)
(set-properties! (remove exclude-properties properties))
(set-excluded-properties! (->> properties
(filter exclude-properties)
(map :block/title)
set))))
[])
[:div.ls-property-add.flex.flex-row.items-center.property-key
[:div.ls-property-key
(select/select (merge
{:items (map (fn [x]
{:label (:block/title x)
:value (:block/uuid x)}) properties)
:extract-fn :label
:dropdown? false
:close-modal? false
:new-case-sensitive? true
:show-new-when-not-exact-match? true
:exact-match-exclude-items (fn [s] (contains? excluded-properties s))
:input-default-placeholder "Add or change property"}
select-opts))]]))
(let [items (concat
(map (fn [x]
{:label (:block/title x)
:value (:block/uuid x)}) properties)
(map (fn [x]
{:label (:block/title x)
:value (:block/uuid x)
:group "Tags"}) classes))]
[:div.ls-property-add.flex.flex-row.items-center.property-key
[:div.ls-property-key
(select/select (merge
{:items items
:grouped? true
:extract-fn :label
:dropdown? false
:close-modal? false
:new-case-sensitive? true
:show-new-when-not-exact-match? true
:exact-match-exclude-items (fn [s] (contains? excluded-properties s))
:input-default-placeholder "Add or change property"}
select-opts))]])))
(rum/defc property-icon
[property property-type]
@@ -194,7 +208,7 @@
(fn [{:keys [value label]}]
(reset! *property-key (if (uuid? value) label value))
(let [property (when (uuid? value) (db/entity [:block/uuid value]))]
(when (and *show-new-property-config? (not property))
(when (and *show-new-property-config? (not (ldb/property? property)))
(reset! *show-new-property-config? true))
(reset! *property property)
(when property
@@ -221,10 +235,26 @@
(not (seq (:property/closed-values property))))
(pv/<create-new-block! block property "")
;; using class as property
(and property (ldb/class? property))
(let [schema (assoc (:block/schema property)
:type :node)]
(p/do!
(db/transact! (state/get-current-repo)
[{:db/id (:db/id property)
:db/ident (:db/ident property)
:db/cardinality :db.cardinality/one
:db/valueType :db.type/ref
:db/index true
:block/tags :logseq.class/Property
:block/schema schema
:property/schema.classes (:db/id property)}]
{:outliner-op :save-block})
(reset! *show-new-property-config? false)))
(or (not= :default type)
(and (= :default type) (seq (:property/closed-values property))))
(p/do!
(reset! *show-new-property-config? false))))))))
(reset! *show-new-property-config? false)))))))
(rum/defc property-key-title
[block property class-schema?]
@@ -333,17 +363,20 @@
*show-new-property-config? (::show-new-property-config? state)
*show-class-select? (::show-class-select? state)
*property-schema (::property-schema state)
block-type (keyword (get block :block/type :block))
page? (ldb/page? block)
block-types (let [types (ldb/get-entity-types block)]
(cond-> types
(and page? (not (contains? types :page)))
(conj :page)
(empty? types)
#{:block}))
exclude-properties (fn [m]
(let [view-context (get-in m [:block/schema :view-context] :all)
block-types (if (and page? (not= block-type :page))
#{:page block-type}
#{block-type})]
(let [view-context (get-in m [:block/schema :view-context] :all)]
(or (contains? #{:logseq.property/query} (:db/ident m))
(and (not page?) (contains? #{:block/alias} (:db/ident m)))
;; Filters out properties from being in wrong :view-context and :never view-contexts
(and (not= view-context :all) (not (contains? block-types view-context))))))
(and (not= view-context :all) (not (contains? block-types view-context)))
(and (ldb/built-in? block) (contains? #{:logseq.property/parent} (:db/ident m))))))
property (rum/react *property)
property-key (rum/react *property-key)]
[:div.ls-property-input.flex.flex-1.flex-row.items-center.flex-wrap.gap-1
@@ -392,7 +425,7 @@
(rum/defcs new-property < rum/reactive
[state block opts]
(when (and (not config/publishing?) (:class-schema? opts))
(when-not config/publishing?
[:div.ls-new-property {:style {:margin-left 6 :margin-top 1}}
[:a.fade-link.flex.jtrigger
{:tab-index 0
@@ -553,7 +586,7 @@
:will-remount (fn [state]
(let [block (db/entity (:db/id (::block state)))]
(assoc state ::classes (async-load-classes! block))))}
[state _target-block {:keys [class-schema?] :as opts}]
[state _target-block {:keys [class-schema? sidebar-properties?] :as opts}]
(let [id (::id state)
db-id (:db/id (::block state))
block (db/sub-block db-id)
@@ -563,7 +596,6 @@
(and (set? ids) (contains? ids (:block/uuid block))))))
_ (doseq [class (::classes state)]
(db/sub-block (:db/id class)))
page? (db/page? block)
class? (ldb/class? block)
block-properties (:block/properties block)
properties (cond
@@ -652,20 +684,38 @@
(when (and class? (nil? (:logseq.property.class/properties block)))
[[:logseq.property.class/properties nil]]))
remove-built-in-or-other-position-properties)]
(when-not (and (empty? full-properties) (not (:class-schema? opts)))
[:div.ls-properties-area
{:id id
:class (util/classnames [{:class-properties class-schema?
:ls-page-properties (and page? (not class-schema?))}])
:tab-index 0
:on-key-up #(when-let [block (and (= "Escape" (.-key %))
(.closest (.-target %) "[blockid]"))]
(let [target (.-target %)]
(when-not (d/has-class? target "ls-popup-closed")
(state/set-selection-blocks! [block])
(some-> js/document.activeElement (.blur)))
(d/remove-class! target "ls-popup-closed")))}
(let [properties' (remove (fn [[k _v]] (contains? #{:logseq.property/icon :logseq.property/query} k)) full-properties)]
(properties-section block (if class-schema? properties properties') opts))
(cond
(and (empty? full-properties) (not (:class-schema? opts)))
(when sidebar-properties?
(rum/with-key (new-property block opts) (str id "-add-property")))
(rum/with-key (new-property block opts) (str id "-add-property"))])))
:else
(let [remove-properties #{:logseq.property/icon :logseq.property/query}
properties' (remove (fn [[k _v]] (contains? remove-properties k)) full-properties)
properties'' (cond->> properties'
(not class-schema?)
(remove (fn [[k _v]] (= k :logseq.property.class/properties))))
page? (ldb/page? block)]
[:div.ls-properties-area
{:id id
:class (util/classnames [{:class-properties class-schema?
:ls-page-properties (and page? (not class-schema?))}])
:tab-index 0
:on-key-up #(when-let [block (and (= "Escape" (.-key %))
(.closest (.-target %) "[blockid]"))]
(let [target (.-target %)]
(when-not (d/has-class? target "ls-popup-closed")
(state/set-selection-blocks! [block])
(some-> js/document.activeElement (.blur)))
(d/remove-class! target "ls-popup-closed")))}
(properties-section block (if class-schema? properties properties'') opts)
(when page?
(rum/with-key (new-property block opts) (str id "-add-property")))
(when page?
(let [properties'' (filter (fn [[k _v]] (= k :logseq.property.class/properties)) properties')]
(when (seq properties'')
[:<>
(when-not class-schema? [:hr.my-4])
(properties-section block (if class-schema? properties properties'') opts)])))]))))

View File

@@ -29,9 +29,7 @@
margin-left: 0;
}
.ls-block .property-pair,
.property-block .property-value,
.block-property-value {
.ls-block .property-pair, .ls-sidebar-page-properties .property-pair, .property-block .property-value, .block-property-value {
margin-left: 7px;
}

View File

@@ -96,7 +96,7 @@
(let [toggle-fn #(do
(when (fn? on-hide) (on-hide))
(shui/popup-hide! id))
classes (model/get-all-classes (state/get-current-repo) {:except-root-class? true})
classes (model/get-all-readable-classes (state/get-current-repo) {:except-root-class? true})
options (map (fn [class]
{:label (:block/title class)
:value (:block/uuid class)})

View File

@@ -5,7 +5,6 @@
[dommy.core :as d]
[frontend.components.icon :as icon-component]
[frontend.components.select :as select]
[frontend.components.title :as title]
[frontend.config :as config]
[frontend.date :as date]
[frontend.db :as db]
@@ -34,7 +33,8 @@
[logseq.db.frontend.property.type :as db-property-type]
[logseq.shui.ui :as shui]
[promesa.core :as p]
[rum.core :as rum]))
[rum.core :as rum]
[clojure.set :as set]))
(rum/defc property-empty-btn-value
[property & opts]
@@ -506,13 +506,16 @@
(when (and property-type (not= property-type :node))
(if (= property-type :page)
(not (db/page? node))
(not= property-type (some-> (:block/type node) keyword))))))
(not (contains? (ldb/get-entity-types node) property-type))))))
result)))))
options (map (fn [node]
(let [id (or (:value node) (:db/id node))
[header label] (if (integer? id)
(let [title (subs (title/block-unique-title node) 0 256)
(let [node-title (if (seq (:property/schema.classes property))
(:block/title node)
(block-handler/block-unique-title node))
title (subs node-title 0 256)
node (or (db/entity id) node)
icon (get-node-icon node)
header (when-not (db/page? node)
@@ -529,7 +532,10 @@
:header header
:label-value (:block/title node)
:label label
:value id))) nodes)
:value id
:disabled? (and tags? (contains?
(set/union #{:logseq.class/Journal :logseq.class/Whiteboard} ldb/internal-tags)
(:db/ident node)))))) nodes)
classes' (remove (fn [class] (= :logseq.class/Root (:db/ident class))) classes)
opts' (cond->
(merge
@@ -547,7 +553,10 @@
"Choose nodes"
:else
"Choose node")
:show-new-when-not-exact-match? (if (and parent-property? (contains? (set children-pages) (:db/id block)))
:show-new-when-not-exact-match? (if (or (and parent-property? (contains? (set children-pages) (:db/id block)))
;; Don't allow creating private tags
(seq (set/intersection (set (map :db/ident classes))
ldb/private-tags)))
false
true)
:extract-chosen-fn :value
@@ -815,6 +824,9 @@
(= value :logseq.property/empty-placeholder)
(property-empty-btn-value property)
closed-values?
(closed-value-item value opts)
(or (ldb/page? value)
(and (seq (:block/tags value))
;; FIXME: page-cp should be renamed to node-cp and
@@ -832,9 +844,6 @@
(when-let [reference (state/get-component :block/reference)]
(reference {} (:block/uuid value)))
closed-values?
(closed-value-item value opts)
(de/entity? value)
(when-some [content (str (db-property/property-value-content value))]
(inline-text-cp content))
@@ -966,7 +975,9 @@
(let [type (get schema :type :default)
date? (= type :date)
*el (rum/use-ref nil)
items (if (de/entity? v) #{v} v)]
items (cond->> (if (de/entity? v) #{v} v)
(= (:db/ident property) :block/tags)
(remove (fn [v] (contains? ldb/hidden-tags (:db/ident v)))))]
(rum/use-effect!
(fn []
(when editing?
@@ -1002,7 +1013,7 @@
(do (some-> (rum/deref *el) (.click))
(util/stop e))
:dune))
:class "flex flex-1 flex-row items-center flex-wrap gap-x-2 gap-y-2 pr-4"}
:class "flex flex-1 flex-row items-center flex-wrap gap-x-2 gap-y-2"}
(let [not-empty-value? (not= (map :db/ident items) [:logseq.property/empty-placeholder])]
(if (and (seq items) not-empty-value?)
(concat

View File

@@ -227,7 +227,7 @@
db-based? (config/db-based-graph? repo)]
(rum/use-effect!
(fn []
(let [result (db-model/get-all-classes repo {:except-root-class? true})]
(let [result (db-model/get-all-readable-classes repo {:except-root-class? true})]
(set-values! result)))
[])
(let [items (->> values

View File

@@ -307,8 +307,8 @@
db-based? (config/db-based-graph? current-repo)
repos (sort-repos-with-metadata-local repos)
repos (distinct
(if (and (or (seq remotes) (seq rtc-graphs)) login?)
(repo-handler/combine-local-&-remote-graphs repos (concat remotes rtc-graphs)) repos))
(if (and (or (seq remotes) (seq rtc-graphs)) login?)
(repo-handler/combine-local-&-remote-graphs repos (concat remotes rtc-graphs)) repos))
items-fn #(repos-dropdown-links repos current-repo downloading-graph-id opts)
header-fn #(when (> (count repos) 1) ; show switch to if there are multiple repos
[:div.font-medium.text-sm.opacity-50.px-1.py-1.flex.flex-row.justify-between.items-center
@@ -318,14 +318,14 @@
(if remotes-loading?
(ui/loading "")
(shui/button
{:variant :ghost
:size :sm
:title "Refresh remote graphs"
:class "!h-6 !px-1 relative right-[-4px]"
:on-click (fn []
(file-sync/load-session-graphs)
(rtc-handler/<get-remote-graphs))}
(ui/icon "refresh" {:size 15}))))])
{:variant :ghost
:size :sm
:title "Refresh remote graphs"
:class "!h-6 !px-1 relative right-[-4px]"
:on-click (fn []
(file-sync/load-session-graphs)
(rtc-handler/<get-remote-graphs))}
(ui/icon "refresh" {:size 15}))))])
_remote? (and current-repo (:remote? (first (filter #(= current-repo (:url %)) repos))))
_repo-name (when current-repo (db/get-repo-name current-repo))]
@@ -339,23 +339,23 @@
(if hr
(shui/dropdown-menu-separator)
(shui/dropdown-menu-item
(assoc options
:title hover-detail
:on-click (fn [^js e]
(when on-click'
(when-not (false? (on-click' e))
(shui/popup-hide! contentid)))))
(or item
(if href'
[:a.flex.items-center.w-full
{:href href' :on-click #(shui/popup-hide! contentid)
:style {:color "inherit"}} title]
[:span.flex.items-center.gap-1.w-full
icon [:div title]]))))))]
(assoc options
:title hover-detail
:on-click (fn [^js e]
(when on-click'
(when-not (false? (on-click' e))
(shui/popup-hide! contentid)))))
(or item
(if href'
[:a.flex.items-center.w-full
{:href href' :on-click #(shui/popup-hide! contentid)
:style {:color "inherit"}} title]
[:span.flex.items-center.gap-1.w-full
icon [:div title]]))))))]
(repos-footer multiple-windows? db-based?)]))
(rum/defcs repos-dropdown < rum/reactive
(rum/local false ::electron-multiple-windows?)
(rum/local false ::electron-multiple-windows?)
[state & {:as opts}]
(let [current-repo (state/sub :git/current-repo)
login? (boolean (state/sub :auth/id-token))]
@@ -365,38 +365,38 @@
db-based? (config/db-based-graph? current-repo)
repos (sort-repos-with-metadata-local repos)
repos (distinct
(if (and (or (seq remotes) (seq rtc-graphs)) login?)
(repo-handler/combine-local-&-remote-graphs repos (concat remotes rtc-graphs)) repos))]
(if (and (or (seq remotes) (seq rtc-graphs)) login?)
(repo-handler/combine-local-&-remote-graphs repos (concat remotes rtc-graphs)) repos))]
(let [remote? (and current-repo (:remote? (first (filter #(= current-repo (:url %)) repos))))
repo-name (when current-repo (db/get-repo-name current-repo))
short-repo-name (if current-repo
(db/get-short-repo-name repo-name)
"Select a Graph")]
(shui/trigger-as :a
{:tab-index 0
:class "item cp__repos-select-trigger"
:on-pointer-down
(fn [^js e]
(check-multiple-windows? state)
(some-> (.-target e)
(.closest "a.item")
(shui/popup-show!
(fn [{:keys [id]}] (repos-dropdown-content (assoc opts :contentid id)))
{:as-dropdown? true
:auto-focus? false
:align "start"
:content-props {:class "repos-list"
:data-mode (when db-based? "db")}})))
:title repo-name} ;; show full path on hover
[:div.flex.relative.graph-icon.rounded
(shui/tabler-icon "database" {:size 15})]
{:tab-index 0
:class "item cp__repos-select-trigger"
:on-pointer-down
(fn [^js e]
(check-multiple-windows? state)
(some-> (.-target e)
(.closest "a.item")
(shui/popup-show!
(fn [{:keys [id]}] (repos-dropdown-content (assoc opts :contentid id)))
{:as-dropdown? true
:auto-focus? false
:align "start"
:content-props {:class "repos-list"
:data-mode (when db-based? "db")}})))
:title repo-name} ;; show full path on hover
[:div.flex.relative.graph-icon.rounded
(shui/tabler-icon "database" {:size 15})]
[:div.repo-switch.pr-2.whitespace-nowrap
[:span.repo-name.font-medium
[:span.repo-text.overflow-hidden.text-ellipsis
(if (config/demo-graph? short-repo-name) "Demo" short-repo-name)]
(when remote? [:span.pl-1 (ui/icon "cloud")])]
[:span.dropdown-caret]])))))
[:div.repo-switch.pr-2.whitespace-nowrap
[:span.repo-name.font-medium
[:span.repo-text.overflow-hidden.text-ellipsis
(if (config/demo-graph? short-repo-name) "Demo" short-repo-name)]
(when remote? [:span.pl-1 (ui/icon "cloud")])]
[:span.dropdown-caret]])))))
(rum/defcs graphs-selector < rum/reactive
[_state]
@@ -413,10 +413,10 @@
[:a.item.flex.items-center.gap-1.select-none
{:on-click (fn [^js e]
(shui/popup-show! (.closest (.-target e) "a")
(fn [{:keys [id]}] (repos-dropdown-content {:contentid id}))
{:as-dropdown? true
:content-props {:class "repos-list"}
:align :start}))}
(fn [{:keys [id]}] (repos-dropdown-content {:contentid id}))
{:as-dropdown? true
:content-props {:class "repos-list"}
:align :start}))}
[:span.thumb (shui/tabler-icon (if remote? "cloud" (if db-based? "database" "folder")) {:size 16})]
[:strong short-repo-name]
(shui/tabler-icon "selector" {:size 18})]]))

View File

@@ -97,8 +97,8 @@
(let [lookup (if (integer? db-id) db-id [:block/uuid db-id])
page (db/entity repo lookup)]
(if (ldb/page? page)
[[:.flex.items-center.page-title
(icon/get-node-icon-cp page {:class "text-md mr-2"})
[[:.flex.items-center.page-title.gap-1
(icon/get-node-icon-cp page {:class "text-md"})
[:span.overflow-hidden.text-ellipsis (:block/title page)]]
(page-cp repo (str (:block/uuid page)))]
(block-with-breadcrumb repo page idx [repo db-id block-type] false)))

View File

@@ -38,7 +38,3 @@ html[data-theme=light] {
@apply opacity-100;
}
}
.sidebar-panel-content {
@apply pt-3;
}

View File

@@ -29,7 +29,8 @@
(when multiple-choices?
(ui/checkbox {:checked (boolean (selected-choices (:value result)))
:on-click (fn [e]
(.preventDefault e))}))
(.preventDefault e))
:disabled (:disabled? result)}))
value]
(when (and (map? result) (:id result))
[:div.tip.flex
@@ -67,7 +68,7 @@
:will-unmount (fn [state]
(shui/dialog-close! :ls-select-modal)
state)}
[state {:keys [items limit on-chosen empty-placeholder
[state {:keys [items limit on-chosen empty-placeholder grouped?
prompt-key input-default-placeholder close-modal?
extract-fn extract-chosen-fn host-opts on-input input-opts
item-cp transform-fn tap-*input-val
@@ -138,7 +139,8 @@
[:div.item-results-wrap
(ui/auto-complete
search-result
{:item-render (or item-cp (fn [result chosen?]
{:grouped? grouped?
:item-render (or item-cp (fn [result chosen?]
(render-item result chosen? multiple-choices? *selected-choices)))
:class "cp__select-results"
:on-chosen (fn [raw-chosen e]

View File

@@ -1,30 +0,0 @@
(ns frontend.components.title
(:require [clojure.string :as string]
[frontend.db :as db]
[logseq.db :as ldb]
[datascript.impl.entity :as de]))
(defn block-unique-title
"Multiple pages/objects may have the same `:block/title`.
Notice: this doesn't prevent for pages/objects that have the same tag or created by different clients."
[block]
(let [block-e (cond
(de/entity? block)
block
(uuid? (:block/uuid block))
(db/entity [:block/uuid (:block/uuid block)])
:else
block)
tags (remove (fn [t] (some-> (:block/raw-title block-e) (ldb/inline-tag? t)))
(map (fn [tag] (if (number? tag) (db/entity tag) tag)) (:block/tags block)))]
(if (and (seq tags)
(not (ldb/journal? block)))
(str (:block/title block)
" "
(string/join
", "
(keep (fn [tag]
(when-let [title (:block/title tag)]
(str "#" title)))
tags)))
(:block/title block))))

View File

@@ -80,11 +80,11 @@
{:variant "text"
:class "h-8 !pl-4 !px-2 !py-0 hover:text-foreground w-full justify-start"
:on-click #(column-toggle-sorting! column)}
(let [title (str (:name column))]
[:span {:title title
:class "max-w-full overflow-hidden text-ellipsis"}
title])
(case asc?
(let [title (str (:name column))]
[:span {:title title
:class "max-w-full overflow-hidden text-ellipsis"}
title])
(case asc?
true
(ui/icon "arrow-up")
false
@@ -529,7 +529,6 @@
(defn- get-property-values
[rows property]
(let [property-ident (:db/ident property)
block-type? (= property-ident :block/type)
values (->> (mapcat (fn [e] (let [e' (db/entity (:db/id e))
v (get e' property-ident)]
(if (set? v) v #{v}))) rows)
@@ -537,9 +536,8 @@
(distinct))]
(->>
(map (fn [e]
(let [label (get-property-value-content e)
label' (if (and block-type? (= label "class")) "tag" label)]
{:label (str label') :value e}))
(let [label (get-property-value-content e)]
{:label (str label) :value e}))
values)
(sort-by :label))))
@@ -607,7 +605,7 @@
(do
(shui/popup-hide!)
(let [property internal-property
new-filter [(:db/ident property) (if (= (:db/ident property) :block/type) :is :text-contains)]
new-filter [(:db/ident property) :text-contains]
filters' (if (seq filters)
(conj filters new-filter)
[new-filter])]
@@ -685,15 +683,14 @@
[:before :after]
(concat
[:is :is-not]
(when-not (= :block/type (:db/ident property))
(case (get-in property [:block/schema :type])
(:default :url :node)
[:text-contains :text-not-contains]
(:date)
[:date-before :date-after]
:number
[:number-gt :number-lt :number-gte :number-lte :between]
nil)))))
(case (get-in property [:block/schema :type])
(:default :url :node)
[:text-contains :text-not-contains]
(:date)
[:date-before :date-after]
:number
[:number-gt :number-lt :number-gte :number-lte :between]
nil))))
(defn- get-filter-with-changed-operator
[_property operator value]
@@ -821,16 +818,11 @@
{:class "!px-2 rounded-none border-r"
:variant "ghost"
:size :sm}
(let [block-type? (= (:db/ident property) :block/type)
value (cond
(let [value (cond
(uuid? value)
(db/entity [:block/uuid value])
(and (coll? value) (every? uuid? value))
(set (map #(db/entity [:block/uuid %]) value))
(and block-type? (coll? value))
(map (fn [v] (if (= v "class") "tag" v)) value)
(and block-type? (= value "class"))
"tag"
:else
value)]
[:div.flex.flex-row.items-center.gap-1.text-xs
@@ -1012,15 +1004,15 @@
(rum/defc new-record-button < rum/static
[table view-entity]
(let [asset? (and (:logseq.property/built-in? view-entity)
(= (:block/name view-entity) "asset"))]
(= (:block/name view-entity) "asset"))]
(ui/tooltip
(shui/button
{:variant "ghost"
:class "!px-1 text-muted-foreground"
:size :sm
:on-click (get-in table [:data-fns :add-new-object!])}
(ui/icon (if asset? "upload" "plus")))
[:div "New record"])))
(shui/button
{:variant "ghost"
:class "!px-1 text-muted-foreground"
:size :sm
:on-click (get-in table [:data-fns :add-new-object!])}
(ui/icon (if asset? "upload" "plus")))
[:div "New record"])))
(rum/defc add-new-row < rum/static
[table]
@@ -1084,8 +1076,8 @@
*rows-wrap (rum/use-ref nil)]
(rum/use-effect!
(fn [] (set-ready? true))
[])
(fn [] (set-ready? true))
[])
(shui/table
(let [columns' (:columns table)
@@ -1097,17 +1089,17 @@
(table-header table columns' option selected-rows)
(ui/virtualized-list
{:ref #(reset! *scroller-ref %)
:custom-scroll-parent (or (some-> (rum/deref *rows-wrap) (.closest ".sidebar-item-list"))
(gdom/getElement "main-content-container"))
:increase-viewport-by {:top 300 :bottom 300}
:compute-item-key (fn [idx]
(let [block (nth rows idx)]
(str "table-row-" (:db/id block))))
:total-count (count rows)
:item-content (fn [idx]
(let [row (nth rows idx)]
(table-row table row columns' {} option)))})
{:ref #(reset! *scroller-ref %)
:custom-scroll-parent (or (some-> (rum/deref *rows-wrap) (.closest ".sidebar-item-list"))
(gdom/getElement "main-content-container"))
:increase-viewport-by {:top 300 :bottom 300}
:compute-item-key (fn [idx]
(let [block (nth rows idx)]
(str "table-row-" (:db/id block))))
:total-count (count rows)
:item-content (fn [idx]
(let [row (nth rows idx)]
(table-row table row columns' {} option)))})
(when add-new-object!
(shui/table-footer (add-new-row table)))])]))))
@@ -1245,39 +1237,42 @@
[:div.flex.flex-col.gap-2.grid
{:ref *view-ref}
[:div.flex.flex-wrap.items-center.justify-between.gap-1
(when-not render-empty-title?
[:div.flex.flex-row.items-center.gap-2
(or
views-title
[:div.font-medium.opacity-50.text-sm
(t (or title-key :views.table/default-title)
(count (:rows table)))])])
[:div.view-actions.flex.items-center.gap-1
(ui/foldable
[:div.flex.flex-1.flex-wrap.items-center.justify-between.gap-1
(when-not render-empty-title?
[:div.flex.flex-row.items-center.gap-2
(or
views-title
[:div.font-medium.opacity-50.text-sm
(t (or title-key :views.table/default-title)
(count (:rows table)))])])
[:div.view-actions.flex.items-center.gap-1
(filter-properties columns table)
(filter-properties columns table)
(search input {:on-change set-input!
:set-input! set-input!})
(search input {:on-change set-input!
:set-input! set-input!})
[:div.text-muted-foreground.text-sm
(pv/property-value view-entity (db/entity :logseq.property.view/type)
(db/entity display-type) {})]
[:div.text-muted-foreground.text-sm
(pv/property-value view-entity (db/entity :logseq.property.view/type)
(db/entity display-type) {})]
(more-actions columns table)
(more-actions columns table)
(when add-new-object! (new-record-button table view-entity))]]
(when add-new-object! (new-record-button table view-entity))]]
(fn []
[:div.ls-view-body.flex.flex-col.gap-2.grid
(filters-row table)
(filters-row table)
(case display-type
:logseq.property.view/type.list
(list-view (:config option) view-entity (:rows table))
(case display-type
:logseq.property.view/type.list
(list-view (:config option) view-entity (:rows table))
:logseq.property.view/type.gallery
(gallery-view (:config option) table view-entity (:rows table) *scroller-ref)
:logseq.property.view/type.gallery
(gallery-view (:config option) table view-entity (:rows table) *scroller-ref)
(table-view table option row-selection add-new-object! *scroller-ref))]))
(table-view table option row-selection add-new-object! *scroller-ref))])
{:title-trigger? false})]))
(rum/defcs view
"Provides a view for data like query results and tagged objects, multiple

View File

@@ -31,10 +31,10 @@
(defn tt
[& keys]
(some->
(medley/find-first
#(not (string/starts-with? (t %) "{Missing key"))
keys)
t))
(medley/find-first
#(not (string/starts-with? (t %) "{Missing key"))
keys)
t))
(defn- fetch-local-language []
(.. js/window -navigator -language))

View File

@@ -55,7 +55,7 @@
[graph & {:keys [remove-built-in-property? remove-non-queryable-built-in-property?]
:or {remove-built-in-property? true
remove-non-queryable-built-in-property? false}}]
(let [result (->> (d/datoms (db/get-db graph) :avet :block/type "property")
(let [result (->> (d/datoms (db/get-db graph) :avet :block/tags :logseq.class/Property)
(map (fn [datom] (db/entity (:e datom))))
(sort-by (juxt ldb/built-in? :block/title)))]
(cond->> result

View File

@@ -383,10 +383,10 @@ independent of format as format specific heading characters are stripped"
(defn page-exists?
"Whether a page exists."
[page-name type]
[page-name tags]
(let [repo (state/get-current-repo)]
(when-let [db (conn/get-db repo)]
(ldb/page-exists? db page-name type))))
(ldb/page-exists? db page-name tags))))
(defn page-empty?
"Whether a page is empty. Does it has a non-page block?
@@ -531,14 +531,23 @@ independent of format as format specific heading characters are stripped"
(defn get-journals-length
[]
(let [today (date-time-util/date->int (js/Date.))]
(d/q '[:find (count ?page) .
:in $ ?today
:where
[?page :block/type "journal"]
[?page :block/journal-day ?journal-day]
[(<= ?journal-day ?today)]]
(conn/get-db (state/get-current-repo))
today)))
(if (config/db-based-graph?)
(d/q '[:find (count ?page) .
:in $ ?today
:where
[?page :block/tags :logseq.class/Journal]
[?page :block/journal-day ?journal-day]
[(<= ?journal-day ?today)]]
(conn/get-db (state/get-current-repo))
today)
(d/q '[:find (count ?page) .
:in $ ?today
:where
[?page :block/type "journal"]
[?page :block/journal-day ?journal-day]
[(<= ?journal-day ?today)]]
(conn/get-db (state/get-current-repo))
today))))
(defn get-latest-journals
([n]
@@ -749,17 +758,29 @@ independent of format as format specific heading characters are stripped"
(defn get-all-whiteboards
[repo]
(d/q
'[:find [(pull ?page [:db/id
:block/uuid
:block/name
:block/title
:block/created-at
:block/updated-at]) ...]
:where
[?page :block/name]
[?page :block/type "whiteboard"]]
(conn/get-db repo)))
(if (config/db-based-graph?)
(d/q
'[:find [(pull ?page [:db/id
:block/uuid
:block/name
:block/title
:block/created-at
:block/updated-at]) ...]
:where
[?page :block/name]
[?page :block/tags :logseq.class/Whiteboard]]
(conn/get-db repo))
(d/q
'[:find [(pull ?page [:db/id
:block/uuid
:block/name
:block/title
:block/created-at
:block/updated-at]) ...]
:where
[?page :block/name]
[?page :block/type "whiteboard"]]
(conn/get-db repo))))
(defn get-whiteboard-id-nonces
[repo page-id]
@@ -777,16 +798,27 @@ independent of format as format specific heading characters are stripped"
:nonce (:nonce shape)}))))))
(defn get-all-classes
[repo & {:keys [except-root-class?]
:or {except-root-class? false}}]
[repo & {:keys [except-root-class? except-private-tags?]
:or {except-root-class? false
except-private-tags? true}}]
(let [db (conn/get-db repo)
classes (->> (d/datoms db :avet :block/type "class")
classes (->> (d/datoms db :avet :block/tags :logseq.class/Tag)
(map (fn [d]
(db-utils/entity db (:e d)))))]
(db-utils/entity db (:e d))))
(remove (fn [d]
(and except-private-tags?
(contains? ldb/private-tags (:db/ident d))))))]
(if except-root-class?
(keep (fn [e] (when-not (= :logseq.class/Root (:db/ident e)) e)) classes)
classes)))
(defn get-all-readable-classes
"Gets all classes that are used in a read only context e.g. querying or used
for property value selection. This should _not_ be used in a write context e.g.
adding a tag to a node or creating a new node with a tag"
[repo opts]
(get-all-classes repo (merge opts {:except-private-tags? false})))
(defn get-structured-children
[repo eid]
(->>
@@ -802,13 +834,15 @@ independent of format as format specific heading characters are stripped"
(defn get-class-objects
[repo class-id]
(when-let [class (db-utils/entity repo class-id)]
(if (first (:logseq.property/_parent class)) ; has children classes
(let [all-classes (conj (->> (get-structured-children repo class-id)
(map #(db-utils/entity repo %)))
class)]
(->> (mapcat :block/_tags all-classes)
distinct))
(:block/_tags class))))
(->>
(if (first (:logseq.property/_parent class)) ; has children classes
(let [all-classes (conj (->> (get-structured-children repo class-id)
(map #(db-utils/entity repo %)))
class)]
(->> (mapcat :block/_tags all-classes)
distinct))
(:block/_tags class))
(remove ldb/hidden?))))
(defn sub-class-objects
[repo class-id]
@@ -829,7 +863,8 @@ independent of format as format specific heading characters are stripped"
(rules/extract-rules rules/db-query-dsl-rules [:has-property-or-default-value]
{:deps rules/rules-dependencies})
(:db/ident property))
(map #(db-utils/entity repo %)))))
(map #(db-utils/entity repo %))
(remove ldb/hidden?))))
(defn get-all-namespace-relation
[repo]
@@ -849,18 +884,30 @@ independent of format as format specific heading characters are stripped"
(defn get-pages-relation
[repo with-journal?]
(when-let [db (conn/get-db repo)]
(let [q (if with-journal?
'[:find ?p ?ref-page
:where
[?block :block/page ?p]
[?block :block/refs ?ref-page]]
'[:find ?p ?ref-page
:where
[?block :block/page ?p]
[(get-else $ ?p :block/type "N/A") ?type]
[(not= ?type "journal")]
[?block :block/refs ?ref-page]])]
(d/q q db))))
(if (config/db-based-graph?)
(let [q (if with-journal?
'[:find ?p ?ref-page
:where
[?block :block/page ?p]
[?block :block/refs ?ref-page]]
'[:find ?p ?ref-page
:where
[?block :block/page ?p]
[?p :block/tags]
(not [?p :block/tags :logseq.class/Journal])
[?block :block/refs ?ref-page]])]
(d/q q db))
(let [q (if with-journal?
'[:find ?p ?ref-page
:where
[?block :block/page ?p]
[?block :block/refs ?ref-page]]
'[:find ?p ?ref-page
:where
[?block :block/page ?p]
(not [?p :block/type "journal"])
[?block :block/refs ?ref-page]])]
(d/q q db)))))
(defn get-namespace-pages
"Accepts both sanitized and unsanitized namespaces"

View File

@@ -323,7 +323,7 @@
(or (some->> (name property-name)
(db-utils/q '[:find [(pull ?b [:db/ident]) ...]
:in $ ?title
:where [?b :block/type "property"] [?b :block/title ?title]])
:where [?b :block/tags :logseq.class/Property] [?b :block/title ?title]])
first
:db/ident)
;; Don't return nil as that incorrectly matches all properties

View File

@@ -22,7 +22,8 @@
[frontend.handler.property.util :as pu]
[dommy.core :as dom]
[goog.object :as gobj]
[promesa.core :as p]))
[promesa.core :as p]
[datascript.impl.entity :as de]))
;; Fns
@@ -188,6 +189,32 @@
(-> (property-util/remove-built-in-properties format content)
(drawer/remove-logbook))))
(defn block-unique-title
"Multiple pages/objects may have the same `:block/title`.
Notice: this doesn't prevent for pages/objects that have the same tag or created by different clients."
[block]
(let [block-e (cond
(de/entity? block)
block
(uuid? (:block/uuid block))
(db/entity [:block/uuid (:block/uuid block)])
:else
block)
tags (remove (fn [t]
(or (some-> (:block/raw-title block-e) (ldb/inline-tag? t))
(ldb/private-tags (:db/ident t))))
(map (fn [tag] (if (number? tag) (db/entity tag) tag)) (:block/tags block)))]
(if (seq tags)
(str (:block/title block)
" "
(string/join
", "
(keep (fn [tag]
(when-let [title (:block/title tag)]
(str "#" title)))
tags)))
(:block/title block))))
(defn edit-block!
[block pos & {:keys [_container-id custom-content tail-len save-code-editor?]
:or {tail-len 0

View File

@@ -4,15 +4,16 @@
[frontend.db :as db]
[frontend.handler.editor :as editor-handler]
[frontend.handler.common.page :as page-common-handler]
[frontend.handler.db-based.property :as db-property-handler]
[frontend.handler.notification :as notification]
[frontend.state :as state]
[frontend.modules.outliner.ui :as ui-outliner-tx]
[logseq.outliner.validate :as outliner-validate]
[logseq.db.frontend.class :as db-class]
[logseq.common.util :as common-util]
[logseq.common.util.page-ref :as page-ref]
[datascript.impl.entity :as de]
[promesa.core :as p]))
[promesa.core :as p]
[logseq.db]))
(defn- valid-tag?
"Returns a boolean indicating whether the new tag passes all valid checks.
@@ -32,27 +33,23 @@
(throw e)))))
(defn add-tag [repo block-id tag-entity]
(ui-outliner-tx/transact!
{:outliner-op :save-block}
(p/do!
(editor-handler/save-current-block!)
;; Check after save-current-block to get most up to date block content
(when (valid-tag? repo (db/entity repo [:block/uuid block-id]) tag-entity)
(let [tx-data [[:db/add [:block/uuid block-id] :block/tags (:db/id tag-entity)]
;; TODO: Move this to outliner.core to consistently add refs for tags
[:db/add [:block/uuid block-id] :block/refs (:db/id tag-entity)]]]
(db/transact! repo tx-data {:outliner-op :save-block}))))))
(p/do!
(editor-handler/save-current-block!)
;; Check after save-current-block to get most up to date block content
(when (valid-tag? repo (db/entity repo [:block/uuid block-id]) tag-entity)
(db-property-handler/set-block-property! block-id :block/tags (:db/id tag-entity)))))
(defn convert-to-tag!
[page-entity]
(if (db/page-exists? (:block/title page-entity) "class")
(if (db/page-exists? (:block/title page-entity) #{:logseq.class/Tag})
(notification/show! (str "A tag with the name \"" (:block/title page-entity) "\" already exists.") :warning false)
(let [class (db-class/build-new-class (db/get-db)
{:db/id (:db/id page-entity)
:block/title (:block/title page-entity)
:block/created-at (:block/created-at page-entity)})]
(let [txs [(db-class/build-new-class (db/get-db)
{:db/id (:db/id page-entity)
:block/title (:block/title page-entity)
:block/created-at (:block/created-at page-entity)})
[:db/retract (:db/id page-entity) :block/tags :logseq.class/Page]]]
(db/transact! (state/get-current-repo) [class] {:outliner-op :save-block}))))
(db/transact! (state/get-current-repo) txs {:outliner-op :save-block}))))
(defn <create-class!
"Creates a class page and provides class-specific error handling"

View File

@@ -11,8 +11,7 @@
[frontend.storage :as storage]
[logseq.graph-parser.db :as gp-db]
[logseq.db.sqlite.create-graph :as sqlite-create-graph]
[logseq.db :as ldb]
[frontend.components.title :as title]))
[logseq.db :as ldb]))
(defn- build-links
[links]
@@ -45,7 +44,7 @@
size (int (* 8 (max 1.0 (js/Math.cbrt n))))]
(cond->
{:id (str (:db/id p))
:label (title/block-unique-title p)
:label page-title
:size size
:color color
:block/created-at (:block/created-at p)}
@@ -72,8 +71,8 @@
(defn- normalize-page-name
[{:keys [nodes links]}]
(let [nodes' (->> (remove-uuids-and-files! nodes)
(util/distinct-by (fn [node] (:id node)))
(remove nil?))]
(util/distinct-by (fn [node] (:id node)))
(remove nil?))]
{:nodes nodes'
:links links}))
@@ -207,15 +206,15 @@
(let [search-nodes (fn [forward?]
(let [links (group-by (if forward? :source :target) links)]
(loop [nodes nodes
level level]
(if (zero? level)
nodes
(recur (distinct (apply concat nodes
(map
(fn [id]
(->> (get links id) (map (if forward? :target :source))))
nodes)))
(dec level))))))
level level]
(if (zero? level)
nodes
(recur (distinct (apply concat nodes
(map
(fn [id]
(->> (get links id) (map (if forward? :target :source))))
nodes)))
(dec level))))))
nodes (concat (search-nodes true) (search-nodes false))
nodes (set nodes)]
(update graph :nodes

View File

@@ -15,7 +15,7 @@
(when (and page (state/enable-journals? (state/get-current-repo)))
(p/do!
(db-async/<get-block (state/get-current-repo) page :children? false)
(if (db-model/page-exists? page "journal")
(if (db-model/page-exists? page #{:logseq.class/Journal})
(route-handler/redirect! {:to :page
:path-params {:name page}})
(page-handler/<create! page)))))

View File

@@ -425,7 +425,6 @@
(<create! title {:redirect? false
:split-namespace? false
:create-first-block? (not template)
:journal? true
:today-journal? true})
(state/pub-event! [:journal/insert-template today-page])
(ui-handler/re-render-root!)

View File

@@ -69,7 +69,7 @@
{:db/id (:db/id page-entity)
:block/title page-name
:block/name (util/page-name-sanity-lc page-name)
:block/type "whiteboard"
:block/tags :logseq.class/Whiteboard
:block/format :markdown
:logseq.property/ls-type :whiteboard-page
:logseq.property.tldraw/page tldraw-page

View File

@@ -4,7 +4,8 @@
[frontend.db.transact]
[frontend.db.conn]
[logseq.outliner.op]
[frontend.modules.outliner.op])))
[frontend.modules.outliner.op]
[logseq.db])))
(defmacro transact!
[opts & body]

View File

@@ -511,42 +511,53 @@
empty-placeholder
item-render
class
header]}]
header
grouped?]}]
(let [*current-idx (get state ::current-idx)
*groups (atom #{})]
*groups (atom #{})
render-f (fn [matched]
(for [[idx item] (medley/indexed matched)]
(let [react-key (str idx)
item-cp
[:div.menu-link-wrap
{:key react-key
;; mouse-move event to indicate that cursor moved by user
:on-mouse-move #(reset! *current-idx idx)}
(let [chosen? (= @*current-idx idx)]
(menu-link
{:id (str "ac-" react-key)
:tab-index "0"
:class (when chosen? "chosen")
;; TODO: should have more tests on touch devices
;:on-pointer-down #(util/stop %)
:on-click (fn [e]
(util/stop e)
(when-not (:disabled? item)
(if (and (gobj/get e "shiftKey") on-shift-chosen)
(on-shift-chosen item)
(on-chosen item e))))}
(if item-render (item-render item chosen?) item)))]]
(let [group-name (and (fn? get-group-name) (get-group-name item))]
(if (and group-name (not (contains? @*groups group-name)))
(do
(swap! *groups conj group-name)
[:div
[:div.ui__ac-group-name group-name]
item-cp])
item-cp)))))]
[:div#ui__ac {:class class}
(if (seq matched)
[:div#ui__ac-inner.hide-scrollbar
(when header header)
(for [[idx item] (medley/indexed matched)]
(let [react-key (str idx)
item-cp
[:div.menu-link-wrap
{:key react-key
;; mouse-move event to indicate that cursor moved by user
:on-mouse-move #(reset! *current-idx idx)}
(let [chosen? (= @*current-idx idx)]
(menu-link
{:id (str "ac-" react-key)
:tab-index "0"
:class (when chosen? "chosen")
;; TODO: should have more tests on touch devices
;:on-pointer-down #(util/stop %)
:on-click (fn [e]
(util/stop e)
(if (and (gobj/get e "shiftKey") on-shift-chosen)
(on-shift-chosen item)
(on-chosen item e)))}
(if item-render (item-render item chosen?) item)))]]
(let [group-name (and (fn? get-group-name) (get-group-name item))]
(if (and group-name (not (contains? @*groups group-name)))
(do
(swap! *groups conj group-name)
[:div
[:div.ui__ac-group-name group-name]
item-cp])
item-cp))))]
(if grouped?
(for [[group matched] (group-by :group matched)]
(if group
[:div
[:div.ui__ac-group-name group]
(render-f matched)]
(render-f matched)))
(render-f matched))]
(when empty-placeholder
empty-placeholder))]))
@@ -593,28 +604,27 @@
(rum/local false ::control?)
[state {:keys [on-pointer-down header title-trigger? collapsed?]}]
(let [control? (get state ::control?)]
[:div.content
[:div.ls-foldable-title.content
[:div.flex-1.flex-row.foldable-title (cond->
{:on-mouse-over #(reset! control? true)
:on-mouse-out #(reset! control? false)}
title-trigger?
(assoc :on-pointer-down on-pointer-down
:class "cursor"))
[:div.flex.flex-row.items-center.ls-foldable-header
[:div.flex.flex-row.items-center.ls-foldable-header.gap-1
{:on-click (fn [^js e]
(let [^js target (.-target e)]
(when (some-> target (.closest ".as-toggle"))
(reset! collapsed? (not @collapsed?)))))}
(when-not (mobile-util/native-platform?)
[:a.block-control.opacity-50.hover:opacity-100.mr-2
(cond->
{:style {:width 14
:height 16
:margin-left -30}}
(not title-trigger?)
(assoc :on-pointer-down on-pointer-down))
[:span {:class (if (or @control? @collapsed?) "control-show cursor-pointer" "control-hide")}
(rotating-arrow @collapsed?)]])
(let [style {:width 14 :height 16}]
[:a.ls-foldable-title-control.block-control.opacity-50.hover:opacity-100
(cond->
{:style style}
(not title-trigger?)
(assoc :on-pointer-down on-pointer-down))
[:span {:class (if (or @control? @collapsed?) "control-show cursor-pointer" "control-hide")}
(rotating-arrow @collapsed?)]]))
(if (fn? header)
(header @collapsed?)
header)]]]))

View File

@@ -329,4 +329,8 @@ input[type='range'] {
.as-toggle {
@apply opacity-60 cursor-pointer select-none active:opacity-50;
}
}
}
.ls-foldable-title-control {
margin-left: -27px;
}

View File

@@ -417,6 +417,34 @@
:block/title title'})))))
datoms)))
(defn- replace-block-type-with-tags
[conn _search-db]
(let [db @conn
block-type-entity (d/entity db :block/type)
;; Not using (d/datoms db :avet :block/type) here because some old graphs
;; don't have :block/type indexed
datoms (->> (d/datoms db :eavt)
(filter (fn [d] (= :block/type (:a d)))))
journal-entity (d/entity db :logseq.class/Journal)
tx-data (mapcat (fn [{:keys [e _a v]}]
(let [tag (case v
"page" :logseq.class/Page
"class" :logseq.class/Tag
"property" :logseq.class/Property
"journal" :logseq.class/Journal
"whiteboard" :logseq.class/Whiteboard
"closed value" nil
(throw (ex-info "unsupported block/type" {:type v})))]
(cond->
[[:db/retract e :block/type]]
(some? tag)
(conj [:db/add e :block/tags tag])))) datoms)]
(concat
;; set journal's tag to `#Page`
[[:db/add (:db/id journal-entity) :block/tags :logseq.class/Page]]
tx-data
[[:db/retractEntity (:db/id block-type-entity)]])))
(defn- deprecate-logseq-user-ns
[conn _search-db]
(let [db @conn]
@@ -514,9 +542,10 @@
[47 {:fix replace-hidden-type-with-schema}]
[48 {:properties [:logseq.property/default-value :logseq.property/scalar-default-value]}]
[49 {:fix replace-special-id-ref-with-id-ref}]
[50 {:properties [:logseq.property.user/name :logseq.property.user/email :logseq.property.user/avatar]}]
[51 {:properties [:logseq.property.user/name :logseq.property.user/email :logseq.property.user/avatar]
:fix deprecate-logseq-user-ns}]])
[50 {:properties [:logseq.property.user/name :logseq.property.user/email :logseq.property.user/avatar]
:fix deprecate-logseq-user-ns}]
[51 {:classes [:logseq.class/Property :logseq.class/Tag :logseq.class/Page :logseq.class/Whiteboard]}]
[52 {:fix replace-block-type-with-tags}]])
(let [max-schema-version (apply max (map first schema-version->updates))]
(assert (<= db-schema/version max-schema-version))
@@ -534,17 +563,20 @@
(into {})
sqlite-create-graph/build-initial-properties*
(map (fn [b] (assoc b :logseq.property/built-in? true))))
new-classes (->> (select-keys db-class/built-in-classes classes)
;; class already exists, this should never happen
(remove (fn [[k _]]
(when (d/entity db k)
(assert (str "DB migration: class already exists " k)))))
classes' (->> (concat [:logseq.class/Property :logseq.class/Tag :logseq.class/Page :logseq.class/Journal :logseq.class/Whiteboard] classes)
distinct)
new-classes (->> (select-keys db-class/built-in-classes classes')
;; class already exists, this should never happen
(remove (fn [[k _]] (d/entity db k)))
(into {})
(#(sqlite-create-graph/build-initial-classes* % (zipmap properties properties)))
(map (fn [b] (assoc b :logseq.property/built-in? true))))
new-class-idents (keep (fn [class]
(when-let [db-ident (:db/ident class)]
{:db/ident db-ident})) new-classes)
fixes (when (fn? fix)
(fix conn search-db))
tx-data (if db-based? (concat new-properties new-classes fixes) fixes)
tx-data (if db-based? (concat new-class-idents new-properties new-classes fixes) fixes)
tx-data' (concat
[(sqlite-util/kv :logseq.kv/schema-version version)]
tx-data)]
@@ -555,30 +587,31 @@
"Migrate 'frontend' datascript schema and data. To add a new migration,
add an entry to schema-version->updates and bump db-schema/version"
[conn search-db]
(let [db @conn
version-in-db (or (:kv/value (d/entity db :logseq.kv/schema-version)) 0)]
(cond
(= version-in-db db-schema/version)
nil
(when (ldb/db-based-graph? @conn)
(let [db @conn
version-in-db (or (:kv/value (d/entity db :logseq.kv/schema-version)) 0)]
(cond
(= version-in-db db-schema/version)
nil
(< db-schema/version version-in-db) ; outdated client, db version could be synced from server
(< db-schema/version version-in-db) ; outdated client, db version could be synced from server
;; FIXME: notify users to upgrade to the latest version asap
nil
nil
(> db-schema/version version-in-db)
(try
(let [db-based? (ldb/db-based-graph? @conn)
updates (keep (fn [[v updates]]
(when (and (< version-in-db v) (<= v db-schema/version))
[v updates]))
schema-version->updates)]
(println "DB schema migrated from" version-in-db)
(doseq [[v m] updates]
(upgrade-version! conn search-db db-based? v m)))
(catch :default e
(prn :error (str "DB migration failed to migrate to " db-schema/version " from " version-in-db ":"))
(js/console.error e)
(throw e))))))
(> db-schema/version version-in-db)
(try
(let [db-based? (ldb/db-based-graph? @conn)
updates (keep (fn [[v updates]]
(when (and (< version-in-db v) (<= v db-schema/version))
[v updates]))
schema-version->updates)]
(println "DB schema migrated from" version-in-db)
(doseq [[v m] updates]
(upgrade-version! conn search-db db-based? v m)))
(catch :default e
(prn :error (str "DB migration failed to migrate to " db-schema/version " from " version-in-db ":"))
(js/console.error e)
(throw e)))))))
;; Backend migrations
;; ==================

View File

@@ -318,7 +318,7 @@
(when-not db-based?
(try
(when-not (ldb/page-exists? @conn common-config/views-page-name "page")
(when-not (ldb/page-exists? @conn common-config/views-page-name #{:logseq.class/Page})
(ldb/transact! conn (sqlite-create-graph/build-initial-views)))
(catch :default _e)))

View File

@@ -6,8 +6,7 @@
[logseq.graph-parser.property :as gp-property]
[logseq.outliner.tree :as otree]
[cljs-bean.core :as bean]
[logseq.db.sqlite.util :as sqlite-util]
[clojure.string :as string]))
[logseq.db.sqlite.util :as sqlite-util]))
(defn- safe-keywordize
[block]
@@ -64,23 +63,17 @@
(:keys result)))))
(group-by first)
(mapcat (fn [[_id col]]
(let [type (some (fn [[_e a v _t]]
(when (= a :block/type)
v)) col)
ident (some (fn [[_e a v _t]]
(let [ident (some (fn [[_e a v _t]]
(when (= a :db/ident)
v)) col)]
v)) col)
journal (some (fn [[_e a v _t]]
(when (= a :block/journal-day)
v)) col)]
(map
(fn [[e a v t]]
(cond
(and (contains? #{:block/title :block/name} a)
(or
;; normal page or block
(not (contains? #{"class" "property" "journal" "closed value"} type))
;; class/property created by user
(and ident
(contains? #{"class" "property"} type)
(not (string/starts-with? (namespace ident) "logseq")))))
(not (or ident journal)))
[e a (str "debug " e) t]
(= a :block/uuid)

View File

@@ -30,8 +30,8 @@
* :create-first-block? - when true, create an empty block if the page is empty.
* :uuid - when set, use this uuid instead of generating a new one.
* :class? - when true, adds a :block/type 'class'
* :whiteboard? - when true, adds a :block/type 'whiteboard'
* :class? - when true, adds a :block/tags ':logseq.class/Tag'
* :whiteboard? - when true, adds a :block/tags ':logseq.class/Whiteboard'
* :tags - tag uuids that are added to :block/tags
* :persist-op? - when true, add an update-page op
* :properties - properties to add to the page

View File

@@ -13,30 +13,30 @@
[logseq.db.sqlite.util :as sqlite-util]
[logseq.graph-parser.block :as gp-block]
[logseq.graph-parser.text :as text]
[logseq.outliner.validate :as outliner-validate]))
[logseq.outliner.validate :as outliner-validate]
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.malli-schema :as db-malli-schema]))
(defn- build-page-tx [conn properties page {:keys [whiteboard? class? tags]}]
(when (:block/uuid page)
(let [page (assoc page :block/type (cond class? "class"
whiteboard? "whiteboard"
(:block/type page) (:block/type page)
:else "page"))
page' (cond-> page
(seq tags)
(update :block/tags
(fnil into [])
(mapv (fn [tag]
(let [v (if (uuid? tag)
(d/entity @conn [:block/uuid tag])
tag)]
(cond
(de/entity? v)
(:db/id v)
(map? v)
(:db/id v)
:else
v)))
tags)))
(let [type-tag (cond class? :logseq.class/Tag
whiteboard? :logseq.class/Whiteboard
:else :logseq.class/Page)
tags' (if (:block/journal-day page) tags (conj tags type-tag))
page' (update page :block/tags
(fnil into [])
(mapv (fn [tag]
(let [v (if (uuid? tag)
(d/entity @conn [:block/uuid tag])
tag)]
(cond
(de/entity? v)
(:db/id v)
(map? v)
(:db/id v)
:else
v)))
tags'))
property-vals-tx-m
;; Builds property values for built-in properties like logseq.property.pdf/file
(db-property-build/build-property-values-tx-m
@@ -95,10 +95,12 @@
(defn- split-namespace-pages
[db page date-formatter]
(let [{:block/keys [title] block-uuid :block/uuid block-type :block/type} page]
(let [{:block/keys [title] block-uuid :block/uuid} page]
(->>
(if (and (contains? #{"page" "class"} block-type) (ns-util/namespace-page? title))
(let [class? (= block-type "class")
(if (and (or (entity-util/class? page)
(entity-util/page? page))
(ns-util/namespace-page? title))
(let [class? (entity-util/class? page)
parts (->> (string/split title ns-util/parent-re)
(map string/trim)
(remove string/blank?))
@@ -168,15 +170,24 @@
(let [db @conn
date-formatter (:logseq.property.journal/title-format (d/entity db :logseq.class/Journal))
title (sanitize-title title*)
type (cond class?
"class"
whiteboard?
"whiteboard"
today-journal?
"journal"
:else
"page")]
(when-not (ldb/page-exists? db title type)
types (cond class?
#{:logseq.class/Tag}
whiteboard?
#{:logseq.class/Whiteboard}
today-journal?
#{:logseq.class/Journal}
:else
#{:logseq.class/Page})]
(if-let [existing-page-id (first (ldb/page-exists? db title types))]
(let [existing-page (d/entity db existing-page-id)
tx-meta {:persist-op? persist-op?
:outliner-op :save-block}]
(when (and class?
(not (ldb/class? existing-page))
(or (ldb/property? existing-page) (ldb/internal-page? existing-page)))
;; Convert existing user property or page to class
(let [tx-data (db-class/build-new-class db (select-keys existing-page [:block/title :block/uuid :db/ident :block/created-at]))]
(ldb/transact! conn tx-data tx-meta))))
(let [format :markdown
page (-> (gp-block/page-name->map title @conn true date-formatter
{:class? class?
@@ -189,9 +200,12 @@
(let [pages (split-namespace-pages db page date-formatter)]
[(last pages) (butlast pages)])
[page nil])]
(when page
(when (and page (or (nil? (:db/ident page))
;; New page creation must not override built-in entities
(not (db-malli-schema/internal-ident? (:db/ident page)))))
;; Don't validate journal names because they can have '/'
(when (not= "journal" type)
(when-not (or (contains? types :logseq.class/Journal)
(contains? (set (:block/tags page)) :logseq.class/Journal))
(outliner-validate/validate-page-title-characters (str (:block/title page)) {:node page})
(doseq [parent parents]
(outliner-validate/validate-page-title-characters (str (:block/title parent)) {:node parent})))
@@ -205,7 +219,8 @@
page-txs)
(build-first-block-tx (:block/uuid (first page-txs)) format))
txs (concat
parents
;; transact doesn't support entities
(remove de/entity? parents)
page-txs
first-block-tx)]
(when (seq txs)

View File

@@ -22,7 +22,7 @@
(def ^:private watched-attrs
#{:block/title :block/created-at :block/updated-at :block/alias
:block/tags :block/type :block/schema :block/link :block/journal-day
:block/tags :block/schema :block/link :block/journal-day
:property/schema.classes :property.value/content
:db/index :db/valueType :db/cardinality})

View File

@@ -366,7 +366,6 @@
:block/updated-at
:block/created-at
:block/alias
:block/type
:block/schema
:block/tags
:block/link

View File

@@ -332,13 +332,6 @@ DROP TRIGGER IF EXISTS blocks_au;
(drop-tables-and-triggers! db)
(create-tables-and-triggers! db))
(comment
(defn- property-value-when-closed
"Returns property value if the given entity is type 'closed value' or nil"
[ent]
(when (= (:block/type ent) "closed value")
(:block/title ent))))
(comment
(defn- get-db-properties-str
"Similar to db-pu/readable-properties but with a focus on making property values searchable"

View File

@@ -4,8 +4,10 @@
[frontend.db :as db]
[frontend.test.helper :as test-helper]
[datascript.core :as d]
[logseq.outliner.property :as outliner-property]
[logseq.db.frontend.class :as db-class]))
[logseq.db.frontend.class :as db-class]
[logseq.db :as ldb]
[logseq.db.test.helper :as db-test]
[frontend.db.conn :as conn]))
(def repo test-helper/test-db-name-db-version)
@@ -27,7 +29,9 @@
_ (test-helper/create-page! "class2" opts)]
(is (= (set
(concat
(map :title (vals db-class/built-in-classes))
(map :title (vals (remove (fn [[ident _]]
(contains? ldb/private-tags ident))
db-class/built-in-classes)))
["class1" "class2"]))
(set (map :block/title (model/get-all-classes repo)))))))
@@ -51,19 +55,16 @@
(:db/id (db/entity [:block/uuid sbid]))])))))
(deftest get-classes-with-property-test
(let [opts {:redirect? false :create-first-block? false :class? true}
_ (test-helper/create-page! "class1" opts)
_ (test-helper/create-page! "class2" opts)
class1 (db/get-case-page "class1")
class2 (db/get-case-page "class2")
conn (db/get-db false)]
(outliner-property/upsert-property! conn :user.property/property-1 {:type :node} {})
(outliner-property/class-add-property! conn (:db/id class1) :user.property/property-1)
(outliner-property/class-add-property! conn (:db/id class2) :user.property/property-1)
(let [property (db/entity :user.property/property-1)
classes (model/get-classes-with-property (:db/ident property))]
(is (= (set (map :db/id classes))
#{(:db/id class1) (:db/id class2)})))))
(let [conn (db-test/create-conn-with-blocks
{:properties {:prop1 {:block/schema {:type :default}}}
:classes
{:Class1 {:build/schema-properties [:prop1]}
:Class2 {:build/schema-properties [:prop1]}}})
property (d/entity @conn :user.property/prop1)
classes (with-redefs [conn/get-db (constantly @conn)]
(model/get-classes-with-property (:db/ident property)))]
(is (= ["Class1" "Class2"]
(map :block/title classes)))))
(deftest hidden-page-test
(let [opts {:redirect? false :create-first-block? false}

View File

@@ -9,12 +9,8 @@
(let [conn (db-test/create-conn)
_ (worker-db-page/create! conn "movie" {:class? true})
_ (worker-db-page/create! conn "Movie" {:class? true})
movie-class (->> (d/q '[:find [(pull ?b [*]) ...] :in $ ?title :where [?b :block/title ?title]]
@conn "movie")
first)
Movie-class (->> (d/q '[:find [(pull ?b [*]) ...] :in $ ?title :where [?b :block/title ?title]]
@conn "Movie")
first)]
movie-class (ldb/get-case-page @conn "movie")
Movie-class (ldb/get-case-page @conn "Movie")]
(is (ldb/class? movie-class) "Creates a class")
(is (ldb/class? Movie-class) "Creates another class with a different case sensitive name")
@@ -44,9 +40,12 @@
"Child class with new parent has correct parents")
(worker-db-page/create! conn "foo/class1/baz3" {:split-namespace? true})
(is (= #{"class" "page"}
(set (d/q '[:find [?type ...]
:where [?b :block/type ?type] [?b :block/title "class1"]] @conn)))
(is (= #{"Tag" "Page"}
(set (d/q '[:find [?tag-title ...]
:where
[?b :block/title "class1"]
[?b :block/tags ?t]
[?t :block/title ?tag-title]] @conn)))
"Using an existing class page in a multi-parent namespace doesn't allow a page to have a class parent and instead creates a new page")))
(testing "Child pages with same name and different parents"
@@ -79,10 +78,23 @@
(let [conn (db-test/create-conn)
[_ page-uuid] (worker-db-page/create! conn "fooz" {})]
(is (= "fooz" (:block/title (d/entity @conn [:block/uuid page-uuid])))
"Valid page created")
"Page created correctly")
(is (thrown-with-msg?
js/Error
#"can't include \"/"
(worker-db-page/create! conn "foo/bar" {}))
"Page can't have '/'n title")))
"Page can't have '/'n title")))
(deftest create-journal
(let [conn (db-test/create-conn)
[_ page-uuid] (worker-db-page/create! conn "Dec 16th, 2024" {})]
(is (= "Dec 16th, 2024" (:block/title (d/entity @conn [:block/uuid page-uuid])))
"Journal created correctly")
(is (= [:logseq.class/Journal]
(->> (d/entity @conn [:block/uuid page-uuid])
:block/tags
(map #(:db/ident (d/entity @conn (:db/id %))))))
"New journal only has Journal tag")))

View File

@@ -9,16 +9,14 @@
[frontend.worker.rtc.db-listener :as subject]
[frontend.worker.rtc.fixture :as r.fixture]
[frontend.worker.state :as worker-state]
[logseq.db.frontend.schema :as db-schema]
[logseq.outliner.batch-tx :as batch-tx]
[logseq.outliner.core :as outliner-core]))
[logseq.outliner.core :as outliner-core]
[logseq.db.test.helper :as db-test]))
(t/use-fixtures :each
test-helper/db-based-start-and-destroy-db-map-fixture
r.fixture/listen-test-db-to-gen-rtc-ops-fixture)
(def empty-db (d/empty-db db-schema/schema-for-db-based-graph))
(defn- tx-data=>e->a->add?->v->t
[tx-data]
(let [datom-vec-coll (map vec tx-data)
@@ -27,11 +25,11 @@
(deftest entity-datoms=>ops-test
(testing "remove whiteboard page-block"
(let [conn (d/conn-from-db empty-db)
(let [conn (db-test/create-conn)
block-uuid (random-uuid)
_create-whiteboard-page-block
(d/transact! conn [{:block/uuid block-uuid
:block/type "whiteboard"
:block/tags :logseq.class/Whiteboard
:block/name "block-name"
:block/title "BLOCK-NAME"}])
remove-whiteboard-page-block
@@ -44,20 +42,20 @@
(map (fn [[op-type _t op-value]] [op-type op-value]) r)))))
(testing "update-schema op"
(let [conn (d/conn-from-db empty-db)
tx-data [[:db/add 69 :db/index true]
[:db/add 69 :block/uuid #uuid "66558abf-6512-469d-9e83-8f1ba0be9305"]
[:db/add 69 :db/valueType :db.type/ref]
[:db/add 69 :block/updated-at 1716882111476]
[:db/add 69 :block/created-at 1716882111476]
[:db/add 69 :block/schema {:type :number}]
[:db/add 69 :block/format :markdown]
[:db/add 69 :db/cardinality :db.cardinality/one]
[:db/add 69 :db/ident :user.property/qqq]
[:db/add 69 :block/type "property"]
[:db/add 69 :block/order "b0T"]
[:db/add 69 :block/name "qqq"]
[:db/add 69 :block/title "qqq"]]
(let [conn (db-test/create-conn)
tx-data [[:db/add 1000000 :db/index true]
[:db/add 1000000 :block/uuid #uuid "66558abf-6512-469d-9e83-8f1ba0be9305"]
[:db/add 1000000 :db/valueType :db.type/ref]
[:db/add 1000000 :block/updated-at 1716882111476]
[:db/add 1000000 :block/created-at 1716882111476]
[:db/add 1000000 :block/schema {:type :number}]
[:db/add 1000000 :block/format :markdown]
[:db/add 1000000 :db/cardinality :db.cardinality/one]
[:db/add 1000000 :db/ident :user.property/qqq]
[:db/add 1000000 :block/tags :logseq.class/Property]
[:db/add 1000000 :block/order "b0T"]
[:db/add 1000000 :block/name "qqq"]
[:db/add 1000000 :block/title "qqq"]]
{:keys [db-before db-after tx-data]} (d/transact! conn tx-data)
ops (#'subject/entity-datoms=>ops db-before db-after
(tx-data=>e->a->add?->v->t tx-data)
@@ -72,10 +70,11 @@
[:block/updated-at "[\"~#'\",1716882111476]"]
[:block/created-at "[\"~#'\",1716882111476]"]
[:block/schema "[\"^ \",\"~:type\",\"~:number\"]"]
[:block/tags #uuid "00000002-1038-7670-4800-000000000000"]
[:block/title "[\"~#'\",\"qqq\"]"]
[:db/cardinality "[\"~#'\",\"~:db.cardinality/one\"]"]
;; [:db/ident "[\"~#'\",\"~:user.property/qqq\"]"]
[:block/type "[\"~#'\",\"property\"]"]]}]]
]}]]
(map (fn [[op-type _t op-value]]
[op-type (cond-> op-value
(:av-coll op-value)
@@ -83,16 +82,16 @@
ops)))))
(testing "create user-class"
(let [conn (d/conn-from-db empty-db)
tx-data [[:db/add 62 :block/uuid #uuid "66856a29-6eb3-4122-af97-8580a853c6a6" 536870954]
[:db/add 62 :block/updated-at 1720019497643 536870954]
[:db/add 62 :logseq.property/parent 4 536870954]
[:db/add 62 :block/created-at 1720019497643 536870954]
[:db/add 62 :block/format :markdown 536870954]
[:db/add 62 :db/ident :user.class/zzz 536870954]
[:db/add 62 :block/type "class" 536870954]
[:db/add 62 :block/name "zzz" 536870954]
[:db/add 62 :block/title "zzz" 536870954]]
(let [conn (db-test/create-conn)
tx-data [[:db/add 1000000 :block/uuid #uuid "66856a29-6eb3-4122-af97-8580a853c6a6" 536870954]
[:db/add 1000000 :block/updated-at 1720019497643 536870954]
[:db/add 1000000 :logseq.property/parent :logseq.class/Root 536870954]
[:db/add 1000000 :block/created-at 1720019497643 536870954]
[:db/add 1000000 :block/format :markdown 536870954]
[:db/add 1000000 :db/ident :user.class/zzz 536870954]
[:db/add 1000000 :block/tags :logseq.class/Tag 536870954]
[:db/add 1000000 :block/name "zzz" 536870954]
[:db/add 1000000 :block/title "zzz" 536870954]]
{:keys [db-before db-after tx-data]} (d/transact! conn tx-data)
ops (#'subject/entity-datoms=>ops db-before db-after
(tx-data=>e->a->add?->v->t tx-data)
@@ -103,9 +102,9 @@
:av-coll
[[:block/updated-at "[\"~#'\",1720019497643]"]
[:block/created-at "[\"~#'\",1720019497643]"]
[:block/tags #uuid "00000002-5389-0208-3000-000000000000"]
[:block/title "[\"~#'\",\"zzz\"]"]
[:block/type "[\"~#'\",\"class\"]"]
[:logseq.property/parent "[\"~#'\",4]"]
[:logseq.property/parent #uuid "00000002-2737-8382-7000-000000000000"]
;;1. shouldn't have :db/ident, :db/ident is special, will be handled later
]}]]
(map (fn [[op-type _t op-value]]

View File

@@ -9,7 +9,7 @@
(deftest remote-op-value->tx-data-test
(let [[block-uuid ref-uuid1 ref-uuid2] (repeatedly random-uuid)
db (d/db-with (d/empty-db db-schema/schema-for-db-based-graph)
(sqlite-create-graph/build-db-initial-data ""))]
(sqlite-create-graph/build-db-initial-data "{}" {}))]
(testing ":block/title"
(let [db (d/db-with db [{:block/uuid block-uuid
:block/title "local-content"}])