From 3820cb7ff69ff54fc8c70ec79df46ec2c17f80cc Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Mon, 10 Jul 2023 03:01:40 +0800 Subject: [PATCH] feat: structured pages --- deps/db/src/logseq/db/schema.cljs | 6 +- .../graph-parser/src/logseq/graph_parser.cljs | 4 +- .../src/logseq/graph_parser/block.cljs | 74 ++++++++++------- src/main/frontend/components/block.cljs | 19 ----- src/main/frontend/components/property.cljs | 71 ++++++++-------- src/main/frontend/components/property.css | 25 ++++++ src/main/frontend/components/settings.cljs | 2 +- src/main/frontend/db/model.cljs | 10 +++ src/main/frontend/modules/outliner/core.cljs | 80 ++++++++++--------- 9 files changed, 165 insertions(+), 126 deletions(-) diff --git a/deps/db/src/logseq/db/schema.cljs b/deps/db/src/logseq/db/schema.cljs index 7609b1c555..4159d31f19 100644 --- a/deps/db/src/logseq/db/schema.cljs +++ b/deps/db/src/logseq/db/schema.cljs @@ -39,7 +39,7 @@ :block/path-refs {:db/valueType :db.type/ref :db/cardinality :db.cardinality/many} - ;; for pages + ;; Structured classes :block/tags {:db/valueType :db.type/ref :db/cardinality :db.cardinality/many} @@ -62,10 +62,6 @@ :block/properties-order {} ;; map, key -> original property value's content :block/properties-text-values {} - ;; which classes this object belongs to - :block/classes {:db/valueType :db.type/ref - :db/cardinality :db.cardinality/many} - ;; first block that's not a heading or unordered list :block/pre-block? {} diff --git a/deps/graph-parser/src/logseq/graph_parser.cljs b/deps/graph-parser/src/logseq/graph_parser.cljs index 8be3badff4..356a787216 100644 --- a/deps/graph-parser/src/logseq/graph_parser.cljs +++ b/deps/graph-parser/src/logseq/graph_parser.cljs @@ -93,7 +93,7 @@ Options available: :filename-format :legacy} extract-options {:db @conn}) - {:keys [pages blocks ast] + {:keys [pages blocks ast refs] :or {pages [] blocks [] ast []}} @@ -116,7 +116,7 @@ Options available: pages (extract/with-ref-pages pages blocks) pages-index (map #(select-keys % [:block/name]) pages)] ;; does order matter? - {:tx (concat file-content pages-index delete-blocks pages block-ids blocks) + {:tx (concat file-content refs pages-index delete-blocks pages block-ids blocks) :ast ast}) tx (concat tx [(cond-> {:file/path file :file/content content} diff --git a/deps/graph-parser/src/logseq/graph_parser/block.cljs b/deps/graph-parser/src/logseq/graph_parser/block.cljs index 9b18cdfbce..5db1980f6a 100644 --- a/deps/graph-parser/src/logseq/graph_parser/block.cljs +++ b/deps/graph-parser/src/logseq/graph_parser/block.cljs @@ -71,8 +71,11 @@ (= "Macro" (first block))) (let [{:keys [name arguments]} (second block) argument (string/join ", " arguments)] - (when (= name "embed") - (text/page-ref-un-brackets! argument))) + (if (= name "embed") + (text/page-ref-un-brackets! argument) + {:type "macro" + :name name + :arguments arguments})) (and (vector? block) (= "Tag" (first block))) @@ -81,7 +84,9 @@ :else nil)] - (when page (or (block-ref/get-block-ref-id page) page)))) + (when page (or (when (string? page) + (block-ref/get-block-ref-id page)) + page)))) (defn get-block-reference [block] @@ -321,12 +326,13 @@ :else nil)) -(defn- with-page-refs +(defn- with-page-refs-and-tags [{:keys [title body tags refs marker priority] :as block} with-id? db date-formatter] (let [refs (->> (concat tags refs [marker priority]) (remove string/blank?) (distinct)) - *refs (atom refs)] + *refs (atom refs) + *structured-tags (atom #{})] (walk/prewalk (fn [form] ;; skip custom queries @@ -338,25 +344,40 @@ (when-let [tag (get-tag form)] (let [tag (text/page-ref-un-brackets! tag)] (when (gp-util/tag-valid? tag) - (swap! *refs conj tag)))) + (swap! *refs conj tag) + (swap! *structured-tags conj tag)))) form)) (concat title body)) (swap! *refs #(remove string/blank? %)) - (let [children-pages (->> @*refs - (mapcat (fn [p] - (let [p (if (map? p) - (:block/original-name p) - p)] - (when (string? p) - (let [p (or (text/get-nested-page-name p) p)] - (when (text/namespace-page? p) - (gp-util/split-namespace-pages p))))))) - (remove string/blank?) - (distinct)) - refs' (->> (distinct (concat @*refs children-pages)) - (remove nil?) - (map (fn [ref] (page-name->map ref with-id? db true date-formatter))))] - (assoc block :refs refs')))) + (let [ref->map-fn (fn [*col tag?] + (let [col (remove string/blank? @*col) + children-pages (->> (mapcat (fn [p] + (let [p (if (map? p) + (:block/original-name p) + p)] + (when (string? p) + (let [p (or (text/get-nested-page-name p) p)] + (when (text/namespace-page? p) + (gp-util/split-namespace-pages p)))))) + col) + (remove string/blank?) + (distinct)) + col (->> (distinct (concat col children-pages)) + (remove nil?))] + (map + (fn [item] + (let [macro? (and (map? item) + (= "macro" (:type item))) + item-name (if macro? (str "macro." (:name item) " " (string/join " " (:arguments item))) item) + ref-page (cond-> (page-name->map item-name with-id? db true date-formatter) + macro? + (assoc :block/type "macro" + :block/properties {:logseq.macro-name (:name item) + :logseq.macro-arguments (:arguments item)}))] + ref-page)) col)))] + (assoc block + :refs (ref->map-fn *refs false) + :tags (ref->map-fn *structured-tags true))))) (defn- with-block-refs [{:keys [title body] :as block}] @@ -382,14 +403,6 @@ block)) blocks)) -(defn- block-tags->pages - [{:keys [tags] :as block}] - (if (seq tags) - (assoc block :tags (map (fn [tag] - (let [tag (text/page-ref-un-brackets! tag)] - [:block/name (gp-util/page-name-sanity-lc tag)])) tags)) - block)) - (defn get-block-content [utf8-content block format meta block-pattern] (let [content (if-let [end-pos (:end_pos meta)] @@ -426,9 +439,8 @@ (defn- with-page-block-refs [block with-id? db date-formatter] (some-> block - (with-page-refs with-id? db date-formatter) + (with-page-refs-and-tags with-id? db date-formatter) with-block-refs - block-tags->pages (update :refs (fn [col] (remove nil? col))))) (defn- with-path-refs diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs index 41d7f0b9bb..133ba1b481 100644 --- a/src/main/frontend/components/block.cljs +++ b/src/main/frontend/components/block.cljs @@ -1896,22 +1896,6 @@ :html (set-priority block priority)} (priority-text priority)))) -(defn block-tags-cp - [{:block/keys [pre-block? tags] :as _block}] - (when (and (not pre-block?) - (seq tags)) - (->elem - :span - {:class "block-tags"} - (mapv (fn [tag] - (when-let [page (db/entity (:db/id tag))] - (let [tag (:block/name page)] - [:a.tag.mx-1 {:data-ref tag - :key (str "tag-" (:db/id tag)) - :href (rfe/href :page {:name tag})} - (str "#" tag)]))) - tags)))) - (declare block-content) (defn build-block-title @@ -1930,7 +1914,6 @@ (marker-switch t)) marker-cp (marker-cp t) priority (priority-cp t) - tags (block-tags-cp t) bg-color (:background-color properties) ;; `heading-level` is for backward compatibility, will remove it in later releases heading-level (:block/heading-level t) @@ -2001,8 +1984,6 @@ (when (= block-type :whiteboard-shape) [:span.mr-1 (ui/icon "whiteboard-element" {:extension? true})])) [[:span.opacity-50 "Click here to start writing, type '/' to see all the commands."]]) - [tags] - ;; highlight ref block (area) (when area? [(hl-ref)]))))))) diff --git a/src/main/frontend/components/property.cljs b/src/main/frontend/components/property.cljs index b5202bf260..0e3d307eb2 100644 --- a/src/main/frontend/components/property.cljs +++ b/src/main/frontend/components/property.cljs @@ -233,10 +233,10 @@ [block property value {:keys [inline-text page-cp block-cp editor-id dom-id editor-box editor-args - new-item?]}] + new-item? editing?]}] (let [multiple-values? (= :many (:cardinality (:block/schema property))) editor-id (or editor-id (str "ls-property-" (:db/id block) "-" (:db/id property))) - editing? (state/sub [:editor/editing? editor-id]) + editing? (or editing? (state/sub [:editor/editing? editor-id])) repo (state/get-current-repo) type (:type (:block/schema property))] (when (or (not new-item?) editing?) @@ -373,7 +373,7 @@ [:div.ls-property-add.grid.grid-cols-4.gap-1.flex.flex-row.items-center (property-key-input entity *property-key *property-value *search?) (when (and property (not (:class-schema? opts))) - (property-scalar-value entity property @*property-value opts)) + (property-scalar-value entity property @*property-value (assoc opts :editing? true))) [:a.close {:on-mouse-down exit-edit-property} svg/close]] @@ -414,20 +414,19 @@ (reset! *property-value nil))} [:div.block {:style {:height 20 :width 20}} - [:a.add-button-link.block {:style {:margin-left -4}} + [:a.add-button-link.block.mt-1 {:style {:margin-left -4}} (ui/icon "circle-plus")]]]))) (rum/defcs property-key [state block property] (let [repo (state/get-current-repo)] - [:div.relative - [:a.property-key - {:propertyid (:block/uuid property) - :blockid (:block/uuid block) - :title (str "Configure property: " (:block/original-name property)) - :on-click (fn [] - (state/set-sub-modal! #(property-config repo property)))} - (:block/original-name property)]])) + [:a + {:propertyid (:block/uuid property) + :blockid (:block/uuid block) + :title (str "Configure property: " (:block/original-name property)) + :on-click (fn [] + (state/set-sub-modal! #(property-config repo property)))} + (:block/original-name property)])) (rum/defcs multiple-value-item < (rum/local false ::show-close?) [state entity property items item {:keys [dom-id editor-id @@ -440,7 +439,7 @@ editing? (state/sub [:editor/editing? editor-id])] [:div.flex.flex-1.flex-row {:on-mouse-over #(reset! *show-close? true) :on-mouse-out #(reset! *show-close? false)} - (property-scalar-value entity property item opts) + (property-scalar-value entity property item (assoc opts :editing? editing?)) (when (and (or (not new-item?) editing?) @*show-close? (seq items)) @@ -514,7 +513,16 @@ (rum/defcs properties-area < rum/reactive [state block properties properties-text-values edit-input-id opts] (let [repo (state/get-current-repo) - new-property? (= edit-input-id (state/sub :ui/new-property-input-id))] + new-property? (= edit-input-id (state/sub :ui/new-property-input-id)) + class-properties (->> (:block/tags block) + (mapcat (fn [tag] + (when (= "class" (:block/type tag)) + (let [e (db/entity (:db/id tag))] + (:properties (:block/schema e)) )))) + (map (fn [id] + [id nil]))) + properties (->> (concat (seq properties) class-properties) + (util/distinct-by first))] (when-not (and (empty? properties) (not new-property?) (not (:page-configure? opts))) @@ -522,22 +530,21 @@ (when (:selected? opts) {:class "select-none"}) (when (seq properties) - [:div.grid.gap-1 - (for [[prop-uuid-or-built-in-prop v] properties] - (let [v (get properties-text-values prop-uuid-or-built-in-prop v)] - (if (uuid? prop-uuid-or-built-in-prop) - (when-let [property (db/sub-block (:db/id (db/entity [:block/uuid prop-uuid-or-built-in-prop])))] - [:div.grid.grid-cols-4.gap-1 - [:div.property-key.col-span-1 - (property-key block property)] - (if (:class-schema? opts) - [:div.property-description.col-span-3.font-light - (get-in property [:block/schema :description])] - [:div.property-value.col-span-3 - (property-value block property v opts)])]) - ;; TODO: built in properties should have UUID and corresponding schema - ;; builtin - [:div - [:a.mr-2 (str prop-uuid-or-built-in-prop)] - [:span v]])))]) + (for [[prop-uuid-or-built-in-prop v] properties] + (let [v (get properties-text-values prop-uuid-or-built-in-prop v)] + (if (uuid? prop-uuid-or-built-in-prop) + (when-let [property (db/sub-block (:db/id (db/entity [:block/uuid prop-uuid-or-built-in-prop])))] + [:div.property-pair + [:div.property-key.col-span-1 + (property-key block property)] + (if (:class-schema? opts) + [:div.property-description.col-span-3.font-light + (get-in property [:block/schema :description])] + [:div.property-value.col-span-3 + (property-value block property v opts)])]) + ;; TODO: built in properties should have UUID and corresponding schema + ;; builtin + [:div + [:a.mr-2 (str prop-uuid-or-built-in-prop)] + [:span v]])))) (new-property repo block edit-input-id properties new-property? opts)]))) diff --git a/src/main/frontend/components/property.css b/src/main/frontend/components/property.css index 8ced6ce888..0ff5070642 100644 --- a/src/main/frontend/components/property.css +++ b/src/main/frontend/components/property.css @@ -9,6 +9,12 @@ } .ls-properties-area { + @apply grid gap-1; + + .property-pair { + @apply grid grid-cols-4 gap-1 + } + .add-button-link { opacity: 0.5; } @@ -47,6 +53,25 @@ .property-block-container { margin-left: -1.5em; } + + .property-key a { + @apply whitespace-nowrap; + color: var(--ls-primary-text-color); + } + + .property-key a:hover { + color: var(--ls-link-text-hover-color); + } +} + +.block-main-container .ls-properties-area { + margin-top: 0.5em; + + gap: 0; + + .property-pair { + gap: 0; + } } input.simple-input { diff --git a/src/main/frontend/components/settings.cljs b/src/main/frontend/components/settings.cljs index 48bb754dde..017fbd8e7e 100644 --- a/src/main/frontend/components/settings.cljs +++ b/src/main/frontend/components/settings.cljs @@ -570,7 +570,7 @@ (rum/defc user-proxy-settings [{:keys [type protocol host port] :as agent-opts}] (ui/button [:span.flex.items-center - [:strong.pr-1 + [:span.pr-1 (case type "system" "System Default" "direct" "Direct" diff --git a/src/main/frontend/db/model.cljs b/src/main/frontend/db/model.cljs index 385a061dce..5094aa8d04 100644 --- a/src/main/frontend/db/model.cljs +++ b/src/main/frontend/db/model.cljs @@ -1490,6 +1490,16 @@ independent of format as format specific heading characters are stripped" {:id (str uuid) :nonce (get-in properties [:logseq.tldraw.shape :nonce])})))) +(defn get-all-classes + [repo] + (d/q + '[:find [?name ...] + :where + [?page :block/type ?t] + [(= ?t "class")] + [?page :block/original-name ?name]] + (conn/get-db repo))) + (comment ;; For debugging (defn get-all-blocks diff --git a/src/main/frontend/modules/outliner/core.cljs b/src/main/frontend/modules/outliner/core.cljs index b7d0bac903..9f6c41cdd9 100644 --- a/src/main/frontend/modules/outliner/core.cljs +++ b/src/main/frontend/modules/outliner/core.cljs @@ -91,6 +91,40 @@ (let [tx (mapv (fn [page] [:db/retractEntity (:db/id page)]) orphaned-pages)] (swap! txs-state (fn [state] (vec (concat state tx))))))))) +(defn- update-page-when-save-block + [txs-state block-entity m] + (when-let [e (:block/page block-entity)] + (let [m' (cond-> {:db/id (:db/id e) + :block/updated-at (util/time-ms)} + (not (:block/created-at e)) + (assoc :block/created-at (util/time-ms))) + txs (if (or (:block/pre-block? block-entity) + (:block/pre-block? m)) + (let [properties (:block/properties m) + alias (set (:alias properties)) + tags (set (:tags properties)) + alias (map (fn [p] {:block/name (util/page-name-sanity-lc p)}) alias) + tags (map (fn [p] {:block/name (util/page-name-sanity-lc p)}) tags) + deleteable-page-attributes {:block/alias alias + :block/tags tags + :block/properties properties + :block/properties-text-values (:block/properties-text-values m)} + ;; Retract page attributes to allow for deletion of page attributes + page-retractions + (mapv #(vector :db/retract (:db/id e) %) (keys deleteable-page-attributes))] + (conj page-retractions (merge m' deleteable-page-attributes))) + [m'])] + (swap! txs-state into txs)))) + +(defn- remove-orphaned-refs-when-save + [txs-state block-entity m] + (when-not (config/db-based-graph? (state/get-current-repo)) + (let [remove-self-page #(remove (fn [b] + (= (:db/id b) (:db/id (:block/page block-entity)))) %) + old-refs (remove-self-page (:block/refs block-entity)) + new-refs (remove-self-page (:block/refs m))] + (remove-orphaned-page-refs! (:db/id block-entity) txs-state old-refs new-refs)))) + ;; -get-id, -get-parent-id, -get-left-id return block-id ;; the :block/parent, :block/left should be datascript lookup ref @@ -148,15 +182,10 @@ (let [m (-> (:data this) (dissoc :block/children :block/meta :block.temp/top? :block.temp/bottom? :block/title :block/body :block/level) - (gp-util/remove-nils)) - m (block-with-timestamps m) - other-tx (:db/other-tx m) + gp-util/remove-nils + block-with-timestamps) id (:db/id (:data this)) block-entity (db/entity id)] - (when (seq other-tx) - (swap! txs-state (fn [txs] - (vec (concat txs other-tx))))) - (when id ;; Retract attributes to prepare for tx which rewrites block attributes (swap! txs-state (fn [txs] @@ -167,38 +196,17 @@ db-schema/retract-attributes))))) ;; Update block's page attributes - (when-let [e (:block/page block-entity)] - (let [m' (cond-> {:db/id (:db/id e) - :block/updated-at (util/time-ms)} - (not (:block/created-at e)) - (assoc :block/created-at (util/time-ms))) - txs (if (or (:block/pre-block? block-entity) - (:block/pre-block? m)) - (let [properties (:block/properties m) - alias (set (:alias properties)) - tags (set (:tags properties)) - alias (map (fn [p] {:block/name (util/page-name-sanity-lc p)}) alias) - tags (map (fn [p] {:block/name (util/page-name-sanity-lc p)}) tags) - deleteable-page-attributes {:block/alias alias - :block/tags tags - :block/properties properties - :block/properties-text-values (:block/properties-text-values m)} - ;; Retract page attributes to allow for deletion of page attributes - page-retractions - (mapv #(vector :db/retract (:db/id e) %) (keys deleteable-page-attributes))] - (conj page-retractions (merge m' deleteable-page-attributes))) - [m'])] - (swap! txs-state into txs))) + (update-page-when-save-block txs-state block-entity m) ;; Remove orphaned refs from block - (when-not (config/db-based-graph? (state/get-current-repo)) - (let [remove-self-page #(remove (fn [b] - (= (:db/id b) (:db/id (:block/page block-entity)))) %) - old-refs (remove-self-page (:block/refs block-entity)) - new-refs (remove-self-page (:block/refs m))] - (remove-orphaned-page-refs! (:db/id block-entity) txs-state old-refs new-refs)))) + (remove-orphaned-refs-when-save txs-state block-entity m)) - (swap! txs-state conj (dissoc m :db/other-tx)) + ;; handle others txs + (let [other-tx (:db/other-tx m)] + (when (seq other-tx) + (swap! txs-state (fn [txs] + (vec (concat txs other-tx))))) + (swap! txs-state conj (dissoc m :db/other-tx))) this))