diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs index d15848a5b..2be9eaf9f 100644 --- a/src/main/frontend/components/block.cljs +++ b/src/main/frontend/components/block.cljs @@ -1181,7 +1181,7 @@ only-title? (and (= 1 (count ast)) (= "Properties" (ffirst ast)) (let [m (second (first ast))] - (= (keys m) [:title]))) + (every? #(contains? #{:title :filters} %) (keys m)))) block-cp [:div {:class (if only-title? (util/hiccup->class "pre-block.opacity-50") (util/hiccup->class "pre-block.bg-base-2.p-2.rounded"))} @@ -1569,7 +1569,8 @@ (when ref? (let [children (-> (db/get-block-immediate-children repo uuid) - db/sort-by-pos)] + db/sort-by-pos) + children (block-handler/filter-blocks repo children (:filters config) false)] (when (seq children) [:div.ref-children.ml-12 (blocks-container children (assoc config @@ -1839,7 +1840,8 @@ (let [format (:block/format config)] (for [[k v] (dissoc m :roam_alias :roam_tags)] (when (and (not (and (= k :macros) (empty? v))) ; empty macros -) + (not (= k :title)) + (not (= k :filters))) [:div.property [:span.font-medium.mr-1 (str (name k) ": ")] (if (coll? v) diff --git a/src/main/frontend/components/reference.cljs b/src/main/frontend/components/reference.cljs index e67c8a5aa..30847d49f 100644 --- a/src/main/frontend/components/reference.cljs +++ b/src/main/frontend/components/reference.cljs @@ -10,21 +10,58 @@ [frontend.date :as date] [frontend.components.editor :as editor] [frontend.db-mixins :as db-mixins] - [clojure.string :as string])) + [clojure.string :as string] + [frontend.config :as config] + [frontend.components.svg :as svg] + [frontend.handler.page :as page-handler] + [frontend.handler.block :as block-handler] + [medley.core :as medley])) -(rum/defc references < rum/reactive db-mixins/query +(rum/defc filter-dialog-inner < rum/reactive + [close-fn references page-name] + (let [filter-state (page-handler/get-filter page-name)] + [:div + [:div.sm:flex.sm:items-start + [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-gray-200.text-gray-500.sm:mx-0.sm:h-10.sm:w-10 + (svg/filter-icon)] + [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left + [:h3#modal-headline.text-lg.leading-6.font-medium.text-gray-900 "Filter"] + [:span.text-xs + "Click to include and shift-click to exclude. Click again to remove."]]] + [:div.mt-5.sm:mt-4.sm:flex.sm.gap-1.flex-wrap + (for [reference references] + (let [filtered (get (rum/react filter-state) reference) + color (condp = filtered + true "text-green-400" + false "text-red-400" + nil)] + [:button.border.rounded.px-1 {:key reference :class color :style {:border-color "currentColor"} + :on-click (fn [e] + (swap! filter-state #(if (nil? (get @filter-state reference)) + (assoc % reference (not (.-shiftKey e))) + (dissoc % reference))) + (page-handler/save-filter! page-name @filter-state))} + reference]))]])) + +(defn filter-dialog + [references page-name] + (fn [close-fn] + (filter-dialog-inner close-fn references page-name))) + +(rum/defc references < rum/reactive [page-name marker? priority?] (when page-name (let [block? (util/uuid-string? page-name) block-id (and block? (uuid page-name)) page-name (string/lower-case page-name) journal? (date/valid-journal-title? (string/capitalize page-name)) + repo (state/get-current-repo) ref-blocks (cond priority? - (db/get-blocks-by-priority (state/get-current-repo) page-name) + (db/get-blocks-by-priority repo page-name) marker? - (db/get-marker-blocks (state/get-current-repo) page-name) + (db/get-marker-blocks repo page-name) block-id (db/get-block-referenced-blocks block-id) :else @@ -32,7 +69,14 @@ scheduled-or-deadlines (if journal? (db/get-date-scheduled-or-deadlines (string/capitalize page-name)) nil) - n-ref (count ref-blocks)] + references (db/get-page-linked-refs-refed-pages repo page-name) + filter-state (rum/react (page-handler/get-filter page-name)) + included (filter (fn [[x v]]) filter-state) + filters (when (seq filter-state) + (->> (group-by second filter-state) + (medley/map-vals #(map first %)))) + filtered-ref-blocks (block-handler/filter-blocks repo ref-blocks filters true) + n-ref (count filtered-ref-blocks)] (when (or (> n-ref 0) (seq scheduled-or-deadlines)) [:div.references.mt-6.flex-1.flex-row @@ -53,17 +97,28 @@ {:hiccup ref-hiccup}))])) (ui/foldable - [:h2.font-bold.opacity-50 (let [] - (str n-ref " Linked Reference" - (if (> n-ref 1) "s")))] + [:div.flex.flex-row.flex-1.justify-between + [:h2.font-bold.opacity-50 (let [] + (str n-ref " Linked Reference" + (if (> n-ref 1) "s")))] + [:a.opacity-50.hover:opacity-100 + {:title "Filter" + :on-click #(state/set-modal! (filter-dialog references page-name))} + (svg/filter-icon (cond + (empty? filter-state) nil + (every? true? (vals filter-state)) "text-green-400" + (every? false? (vals filter-state)) "text-red-400" + :else "text-yellow-400"))]] + [:div.references-blocks - (let [ref-hiccup (block/->hiccup ref-blocks + (let [ref-hiccup (block/->hiccup filtered-ref-blocks {:id page-name :start-level 2 :ref? true :breadcrumb-show? true :group-by-page? true - :editor-box editor/box} + :editor-box editor/box + :filters filters} {})] (content/content page-name {:hiccup ref-hiccup}))])]])))) diff --git a/src/main/frontend/components/svg.cljs b/src/main/frontend/components/svg.cljs index 5e8dfd8a0..727fa7448 100644 --- a/src/main/frontend/components/svg.cljs +++ b/src/main/frontend/components/svg.cljs @@ -483,7 +483,7 @@ :stroke-width "2" :stroke-linejoin "round" :stroke-linecap "round"}]]) - + (def page [:svg.h-5.w-4 {:viewBox "0 0 24 24", :fill "none", :xmlns "http://www.w3.org/2000/svg"} [:path {:d "M2 0.5H6.78272L13.5 7.69708V18C13.5 18.8284 12.8284 19.5 12 19.5H2C1.17157 19.5 0.5 18.8284 0.5 18V2C0.5 1.17157 1.17157 0.5 2 0.5Z", :fill "var(--ls-active-primary-color)"}] @@ -492,5 +492,17 @@ (def online (hero-icon "M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0")) +(rum/defc filter-icon + [class] + [:svg + {:stroke "currentColor" + :fill "currentColor" + :view-box "0 0 16.06 16.06" + :width "16" + :height "16" + :class class } + [:path + {:d "M.53.53h15l-5 7v8h-5v-8z" :stroke-width "1.06" :stroke-linejoin "round"}]]) + (def collapse-right (hero-icon "M4 6h16M4 12h16m-7 6h7")) diff --git a/src/main/frontend/db.cljs b/src/main/frontend/db.cljs index 85a33cf2c..3fbc17f0d 100644 --- a/src/main/frontend/db.cljs +++ b/src/main/frontend/db.cljs @@ -46,7 +46,7 @@ get-date-scheduled-or-deadlines get-db-type get-empty-pages get-file get-file-after-blocks get-file-after-blocks-meta get-file-blocks get-file-contents get-file-last-modified-at get-file-no-sub get-file-page get-file-page-id get-file-pages get-files get-files-blocks get-files-full get-files-that-referenced-page get-journals-length - get-latest-journals get-marker-blocks get-matched-blocks get-page get-page-alias get-page-alias-names get-page-blocks + get-latest-journals get-marker-blocks get-matched-blocks get-page get-page-alias get-page-alias-names get-page-blocks get-page-linked-refs-refed-pages get-page-blocks-count get-page-blocks-no-cache get-page-file get-page-format get-page-name get-page-properties get-page-properties-content get-page-referenced-blocks get-page-referenced-pages get-page-unlinked-references get-pages get-pages-relation get-pages-that-mentioned-page get-public-pages get-tag-pages diff --git a/src/main/frontend/db/model.cljs b/src/main/frontend/db/model.cljs index 90fe7d38a..f7deff861 100644 --- a/src/main/frontend/db/model.cljs +++ b/src/main/frontend/db/model.cljs @@ -836,6 +836,24 @@ db-utils/seq-flatten)] (mapv (fn [page] [page (get-page-alias repo page)]) ref-pages)))) +(defn get-page-linked-refs-refed-pages + [repo page] + (when-let [conn (conn/get-conn repo)] + (-> + (d/q + '[:find [?ref-page ...] + :in $ % ?page + :where + [?p :page/name ?page] + [?b :block/path-ref-pages ?p] + [?b :block/ref-pages ?other-p] + [(not= ?p ?other-p)] + [?other-p :page/name ?ref-page]] + conn + rules + page) + (distinct)))) + ;; Ignore files with empty blocks for now (defn get-empty-pages [repo] @@ -905,6 +923,27 @@ (remove (fn [block] (contains? childrens (:db/id block))) blocks) blocks))) +;; TODO: improve perf +(defn with-children-refs + [repo blocks] + (when-let [conn (conn/get-conn repo)] + (when (seq blocks) + (let [block-ids (set (map :db/id blocks)) + refs (d/q + '[:find ?p ?ref + :in $ % ?block-ids + :where + (parent ?p ?b) + [(contains? ?block-ids ?p)] + [?b :block/ref-pages ?ref]] + conn + rules + block-ids) + refs (->> (group-by first refs) + (medley/map-vals #(set (map (fn [[_ id]] {:db/id id}) %))))] + (map (fn [block] (assoc block :block/children-refs + (get refs (:db/id block)))) blocks))))) + (defn get-page-referenced-blocks ([page] (get-page-referenced-blocks (state/get-current-repo) page)) @@ -941,13 +980,15 @@ db-utils/seq-flatten (remove (fn [block] (= page-id (:db/id (:block/page block))))) + (remove-children!) + (with-children-refs repo) sort-blocks db-utils/group-by-page (map (fn [[k blocks]] (let [k (if (contains? aliases (:db/id k)) (assoc k :page/alias? true) k)] - [k (remove-children! blocks)]))))] + [k blocks]))))] result))))) (defn get-date-scheduled-or-deadlines @@ -1265,4 +1306,4 @@ [(contains? ?refs ?b-ref)]))] (conn/get-conn) rules - page-ids)) + page-ids)) diff --git a/src/main/frontend/handler/block.cljs b/src/main/frontend/handler/block.cljs index d84d1f492..436c3a365 100644 --- a/src/main/frontend/handler/block.cljs +++ b/src/main/frontend/handler/block.cljs @@ -6,7 +6,9 @@ [frontend.format.mldoc :as mldoc] [frontend.date :as date] [frontend.config :as config] - [datascript.core :as d])) + [datascript.core :as d] + [clojure.set :as set] + [medley.core :as medley])) (defn blocks->vec-tree [col] (let [col (map (fn [h] (cond-> @@ -141,9 +143,9 @@ (defn pre-block-with-only-title? [repo block-id] (when-let [block (db/entity repo [:block/uuid block-id])] - (let [properties (:page/properties (:block/page block))] - (and (:title properties) - (= 1 (count properties)) + (let [properties (:page/properties (:block/page block)) + property-names (keys properties)] + (and (every? #(contains? #{:title :filters} %) property-names) (let [ast (mldoc/->edn (:block/content block) (mldoc/default-config (:block/format block)))] (or (empty? (rest ast)) @@ -192,3 +194,42 @@ :block/pre-block? false}) default-option)] (conj blocks dummy)))))) + +(defn filter-blocks + [repo ref-blocks filters group-by-page?] + (let [ref-pages (->> (if group-by-page? + (mapcat last ref-blocks) + ref-blocks) + (mapcat (fn [b] (concat (:block/ref-pages b) (:block/children-refs b)))) + (distinct) + (map :db/id) + (db/pull-many repo '[:db/id :page/name])) + ref-pages (zipmap (map :page/name ref-pages) (map :db/id ref-pages)) + exclude-ids (->> (map (fn [page] (get ref-pages page)) (get filters false)) + (remove nil?) + (set)) + include-ids (->> (map (fn [page] (get ref-pages page)) (get filters true)) + (remove nil?) + (set))] + (if (empty? filters) + ref-blocks + (let [filter-f (fn [ref-blocks] + (cond->> ref-blocks + (seq exclude-ids) + (remove (fn [block] + (let [ids (set (concat (map :db/id (:block/ref-pages block)) + (map :db/id (:block/children-refs block))))] + (seq (set/intersection exclude-ids ids))))) + + (seq include-ids) + (remove (fn [block] + (let [ids (set (concat (map :db/id (:block/ref-pages block)) + (map :db/id (:block/children-refs block))))] + (empty? (set/intersection include-ids ids))))) + ))] + (if group-by-page? + (->> (map (fn [[p ref-blocks]] + [p (filter-f ref-blocks)]) ref-blocks) + (remove #(empty? (second %)))) + (->> (filter-f ref-blocks) + (remove nil?))))))) diff --git a/src/main/frontend/handler/page.cljs b/src/main/frontend/handler/page.cljs index ced8968c8..3334d7e8e 100644 --- a/src/main/frontend/handler/page.cljs +++ b/src/main/frontend/handler/page.cljs @@ -26,6 +26,7 @@ [frontend.format.mldoc :as mldoc] [cljs-time.core :as t] [cljs-time.coerce :as tc] + [cljs.reader :as reader] [goog.object :as gobj])) (defn- get-directory @@ -516,6 +517,17 @@ (->> (db/get-modified-pages repo) (remove util/file-page?))) +(defn save-filter! + [page-name filter-state] + (if (empty? filter-state) + (page-remove-property! page-name "filters") + (page-add-properties! page-name {"filters" filter-state}))) + +(defn get-filter + [page-name] + (let [properties (db/get-page-properties page-name)] + (atom (reader/read-string (get-in properties [:filters] "{}"))))) + (defn page-exists? [page-name] (when page-name diff --git a/src/main/frontend/ui.css b/src/main/frontend/ui.css index 9df6bcc7d..22deecc0f 100644 --- a/src/main/frontend/ui.css +++ b/src/main/frontend/ui.css @@ -66,8 +66,7 @@ } &-panel { - @apply relative bg-white rounded-md px-4 pt-5 - pb-4 shadow-xl; + @apply relative bg-white rounded-md p-8 shadow-xl; } &-close { diff --git a/tailwind.config.js b/tailwind.config.js index 5ab90df06..840ef7eae 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -29,7 +29,7 @@ module.exports = { 800: '#075985', 900: '#0c4a6e', }, - red: colors.rose, + red: colors.red, yellow: colors.amber, orange: colors.orange, rose: colors.rose