Просмотр исходного кода

Merge pull request #6336 from logseq/feat/config-for-property-value-links-and-text

Feat: Config option to allow for longer, richer property values
Gabriel Horner 3 лет назад
Родитель
Сommit
0a66ecddfc

+ 2 - 1
.carve/ignore

@@ -72,7 +72,8 @@ frontend.util/trace!
 frontend.util.pool/terminate-pool!
 frontend.util.pool/terminate-pool!
 ;; Repl fn
 ;; Repl fn
 frontend.util.property/add-page-properties
 frontend.util.property/add-page-properties
-;; Test runner used by shadow
+;; Test runners used by shadow
 frontend.test.node-test-runner/main
 frontend.test.node-test-runner/main
+frontend.test.frontend-node-test-runner/main
 ;; Test runner for nbb
 ;; Test runner for nbb
 logseq.graph-parser.nbb-test-runner/run-tests
 logseq.graph-parser.nbb-test-runner/run-tests

+ 4 - 0
deps/graph-parser/.carve/ignore

@@ -18,3 +18,7 @@ logseq.graph-parser.util.page-ref/left-and-right-brackets
 logseq.graph-parser.util.page-ref/->page-ref
 logseq.graph-parser.util.page-ref/->page-ref
 ;; API
 ;; API
 logseq.graph-parser.util.page-ref/get-page-name!
 logseq.graph-parser.util.page-ref/get-page-name!
+;; API
+logseq.graph-parser.property/->block-content
+;; API
+logseq.graph-parser.property/property-value-from-content

+ 7 - 1
deps/graph-parser/src/logseq/graph_parser/block.cljs

@@ -157,6 +157,8 @@
              distinct)
              distinct)
     []))
     []))
 
 
+;; TODO: Use text/parse-property to determine refs rather than maintain this similar
+;; implementation to parse-property
 (defn- get-page-ref-names-from-properties
 (defn- get-page-ref-names-from-properties
   [format properties user-config]
   [format properties user-config]
   (let [page-refs (->>
   (let [page-refs (->>
@@ -174,7 +176,11 @@
                             (and (string? v)
                             (and (string? v)
                                  (not (gp-mldoc/link? format v)))
                                  (not (gp-mldoc/link? format v)))
                             (let [v (string/trim v)
                             (let [v (string/trim v)
-                                  result (text/split-page-refs-without-brackets v {:un-brackets? false})]
+                                  result (if (:rich-property-values? user-config)
+                                           (if (gp-util/wrapped-by-quotes? v)
+                                             []
+                                             (text/extract-page-refs-and-tags v))
+                                           (text/split-page-refs-without-brackets v {:un-brackets? false}))]
                               (if (coll? result)
                               (if (coll? result)
                                 (map text/page-ref-un-brackets! result)
                                 (map text/page-ref-un-brackets! result)
                                 []))
                                 []))

+ 2 - 1
deps/graph-parser/src/logseq/graph_parser/cli.cljs

@@ -44,7 +44,8 @@ TODO: Fail fast when process exits 1"
 
 
 (defn- parse-files
 (defn- parse-files
   [conn files {:keys [config] :as options}]
   [conn files {:keys [config] :as options}]
-  (let [extract-options (merge {:date-formatter (gp-config/get-date-formatter config)}
+  (let [extract-options (merge {:date-formatter (gp-config/get-date-formatter config)
+                                :user-config config}
                                (select-keys options [:verbose]))]
                                (select-keys options [:verbose]))]
     (mapv
     (mapv
      (fn [{:file/keys [path content]}]
      (fn [{:file/keys [path content]}]

+ 3 - 3
deps/graph-parser/src/logseq/graph_parser/config.cljs

@@ -1,7 +1,6 @@
 (ns logseq.graph-parser.config
 (ns logseq.graph-parser.config
   "Config that is shared between graph-parser and rest of app"
   "Config that is shared between graph-parser and rest of app"
-  (:require [logseq.graph-parser.util :as gp-util]
-            [clojure.set :as set]
+  (:require [clojure.set :as set]
             [clojure.string :as string]))
             [clojure.string :as string]))
 
 
 (def app-name
 (def app-name
@@ -12,7 +11,8 @@
 
 
 (defn local-asset?
 (defn local-asset?
   [s]
   [s]
-  (gp-util/safe-re-find (re-pattern (str "^[./]*" local-assets-dir)) s))
+  (and (string? s)
+       (re-find (re-pattern (str "^[./]*" local-assets-dir)) s)))
 
 
 (defonce default-draw-directory "draws")
 (defonce default-draw-directory "draws")
 
 

+ 2 - 1
deps/graph-parser/src/logseq/graph_parser/mldoc.cljc

@@ -154,7 +154,8 @@
                           (remove string/blank?)))
                           (remove string/blank?)))
           tags (:tags properties)
           tags (:tags properties)
           tags (->> (->vec-concat tags filetags)
           tags (->> (->vec-concat tags filetags)
-                    (remove string/blank?))
+                    (remove string/blank?)
+                    vec)
           properties (assoc properties :tags tags :alias alias)
           properties (assoc properties :tags tags :alias alias)
           properties (-> properties
           properties (-> properties
                          (update :filetags (constantly filetags)))
                          (update :filetags (constantly filetags)))

+ 14 - 1
deps/graph-parser/src/logseq/graph_parser/property.cljs

@@ -8,6 +8,19 @@
 
 
 (def colons "Property delimiter for markdown mode" "::")
 (def colons "Property delimiter for markdown mode" "::")
 
 
+(defn ->block-content
+  "Creates a block content string from properties map"
+  [properties]
+  (->> properties
+       (map #(str (name (key %)) (str colons " ") (val %)))
+       (string/join "\n")))
+
+(defn property-value-from-content
+  "Extracts full property value from block content"
+  [property content]
+  (second (re-find (re-pattern (str property colons "\\s+(.*)"))
+                   content)))
+
 (defn properties-ast?
 (defn properties-ast?
   [block]
   [block]
   (and
   (and
@@ -60,7 +73,7 @@
   [content]
   [content]
   (when content
   (when content
     (and (string/includes? content properties-start)
     (and (string/includes? content properties-start)
-         (gp-util/safe-re-find properties-end-pattern content))))
+         (re-find properties-end-pattern content))))
 
 
 (defn ->new-properties
 (defn ->new-properties
   "New syntax: key:: value"
   "New syntax: key:: value"

+ 39 - 11
deps/graph-parser/src/logseq/graph_parser/text.cljs

@@ -5,6 +5,7 @@
             [clojure.set :as set]
             [clojure.set :as set]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.property :as gp-property]
             [logseq.graph-parser.util.page-ref :as page-ref :refer [right-brackets]]))
             [logseq.graph-parser.util.page-ref :as page-ref :refer [right-brackets]]))
 
 
 (defn get-file-basename
 (defn get-file-basename
@@ -109,8 +110,8 @@
 
 
      (and (string? s)
      (and (string? s)
             ;; Either a page ref, a tag or a comma separated collection
             ;; Either a page ref, a tag or a comma separated collection
-            (or (gp-util/safe-re-find page-ref/page-ref-re s)
-                (gp-util/safe-re-find #"[\,|,|#|\"]+" s)))
+            (or (re-find page-ref/page-ref-re s)
+                (re-find #"[\,|,|#|\"]+" s)))
      (let [result (->> (sep-by-quotes s)
      (let [result (->> (sep-by-quotes s)
                        (mapcat
                        (mapcat
                         (fn [s]
                         (fn [s]
@@ -199,7 +200,35 @@
 (defonce non-parsing-properties
 (defonce non-parsing-properties
   (atom #{"background-color" "background_color"}))
   (atom #{"background-color" "background_color"}))
 
 
+(defn parse-non-string-property-value
+  "Return parsed non-string property value or nil if none is found"
+  [v]
+  (cond
+    (= v "true")
+    true
+
+    (= v "false")
+    false
+
+    (re-find #"^\d+$" v)
+    (parse-long v)))
+
+(def ^:private page-ref-or-tag-re
+  (re-pattern (str "#?" (page-ref/->page-ref-re-str "(.*?)") "|"
+                   ;; Don't capture punctuation at end of a tag
+                   "#([\\S]+[^\\s.!,])")))
+
+(defn extract-page-refs-and-tags
+  "Returns set of page-refs and tags in given string or returns string if none
+  are found"
+  [string]
+  (let [refs (map #(or (second %) (get % 2))
+                  (re-seq page-ref-or-tag-re string))]
+    (if (seq refs) (set refs) string)))
+
 (defn parse-property
 (defn parse-property
+  "Property value parsing that takes into account built-in properties, format
+  and user config"
   ([k v config-state]
   ([k v config-state]
    (parse-property :markdown k v config-state))
    (parse-property :markdown k v config-state))
   ([format k v config-state]
   ([format k v config-state]
@@ -212,14 +241,6 @@
                    (get config-state :ignored-page-references-keywords)) k)
                    (get config-state :ignored-page-references-keywords)) k)
        v
        v
 
 
-       (= v "true")
-       true
-       (= v "false")
-       false
-
-       (and (not= k "alias") (gp-util/safe-re-find #"^\d+$" v))
-       (parse-long v)
-
        (gp-util/wrapped-by-quotes? v) ; wrapped in ""
        (gp-util/wrapped-by-quotes? v) ; wrapped in ""
        v
        v
 
 
@@ -229,5 +250,12 @@
        (gp-mldoc/link? format v)
        (gp-mldoc/link? format v)
        v
        v
 
 
+       (contains? gp-property/editable-linkable-built-in-properties (keyword k))
+       (split-page-refs-without-brackets v)
+
        :else
        :else
-       (split-page-refs-without-brackets v)))))
+       (if-some [res (parse-non-string-property-value v)]
+         res
+         (if (:rich-property-values? config-state)
+           (extract-page-refs-and-tags v)
+           (split-page-refs-without-brackets v)))))))

+ 1 - 7
deps/graph-parser/src/logseq/graph_parser/util.cljs

@@ -4,12 +4,6 @@
   (:require [clojure.walk :as walk]
   (:require [clojure.walk :as walk]
             [clojure.string :as string]))
             [clojure.string :as string]))
 
 
-(defn safe-re-find
-  "Copy of frontend.util/safe-re-find. Too basic to couple to main app"
-  [pattern s]
-  (when (string? s)
-    (re-find pattern s)))
-
 (defn path-normalize
 (defn path-normalize
   "Normalize file path (for reading paths from FS, not required by writting)"
   "Normalize file path (for reading paths from FS, not required by writting)"
   [s]
   [s]
@@ -38,7 +32,7 @@
 (defn tag-valid?
 (defn tag-valid?
   [tag-name]
   [tag-name]
   (when (string? tag-name)
   (when (string? tag-name)
-    (not (safe-re-find #"[# \t\r\n]+" tag-name))))
+    (not (re-find #"[# \t\r\n]+" tag-name))))
 
 
 (defn safe-subs
 (defn safe-subs
   ([s start]
   ([s start]

+ 5 - 0
deps/graph-parser/src/logseq/graph_parser/util/page_ref.cljs

@@ -27,6 +27,11 @@ a logseq page-ref e.g. [[page name]]"
   [page-name]
   [page-name]
   (str left-brackets page-name right-brackets))
   (str left-brackets page-name right-brackets))
 
 
+(defn ->page-ref-re-str
+  "Create a page ref regex escaped string given a page name"
+  [page-name]
+  (string/replace (->page-ref page-name) #"([\[\]])" "\\$1"))
+
 (defn get-page-name
 (defn get-page-name
   "Extracts page-name from page-ref string"
   "Extracts page-name from page-ref string"
   [s]
   [s]

+ 14 - 1
deps/graph-parser/test/logseq/graph_parser/property_test.cljs

@@ -1,5 +1,5 @@
 (ns logseq.graph-parser.property-test
 (ns logseq.graph-parser.property-test
-  (:require [cljs.test :refer [are deftest]]
+  (:require [cljs.test :refer [are deftest is]]
             [logseq.graph-parser.property :as gp-property]))
             [logseq.graph-parser.property :as gp-property]))
 
 
 (deftest test->new-properties
 (deftest test->new-properties
@@ -24,3 +24,16 @@
 
 
     "hello\n:PROPERTIES:\n:foo: bar\n:nice\n:END:\nnice"
     "hello\n:PROPERTIES:\n:foo: bar\n:nice\n:END:\nnice"
     "hello\nfoo:: bar\n:nice\nnice"))
     "hello\nfoo:: bar\n:nice\nnice"))
+
+(deftest property-value-from-content
+  (is (= "62b38254-4be7-4627-a2b7-6d9ee20999e5"
+         (gp-property/property-value-from-content
+          "id"
+          "type:: blog-posting\ndesc:: nice walkthrough on creating a blog with #nbb\nid:: 62b38254-4be7-4627-a2b7-6d9ee20999e5"))
+      "Pulls value from end of block content")
+
+  (is (= "nice walkthrough on creating a blog with #nbb"
+         (gp-property/property-value-from-content
+          "desc"
+          "type:: blog-posting\ndesc:: nice walkthrough on creating a blog with #nbb\nid:: 62b38254-4be7-4627-a2b7-6d9ee20999e5"))
+      "Pulls value from middle of block content"))

+ 6 - 1
deps/graph-parser/test/logseq/graph_parser/text_test.cljs

@@ -1,5 +1,5 @@
 (ns logseq.graph-parser.text-test
 (ns logseq.graph-parser.text-test
-  (:require [cljs.test :refer [are deftest testing]]
+  (:require [cljs.test :refer [are deftest testing is]]
             [logseq.graph-parser.text :as text]))
             [logseq.graph-parser.text :as text]))
 
 
 (deftest test-get-page-name
 (deftest test-get-page-name
@@ -109,4 +109,9 @@
       :tags "\"[[foo]], [[bar]]\"" "\"[[foo]], [[bar]]\""
       :tags "\"[[foo]], [[bar]]\"" "\"[[foo]], [[bar]]\""
       :tags "baz, \"[[foo]], [[bar]]\"" #{"baz"})))
       :tags "baz, \"[[foo]], [[bar]]\"" #{"baz"})))
 
 
+(deftest extract-page-refs-and-tags
+  (is (= #{"cljs" "nbb" "js" "amazing"}
+       (text/extract-page-refs-and-tags "This project is written with #cljs, #nbb and #js. #amazing!"))
+      "Don't extract punctation at end of a tag"))
+
 #_(cljs.test/test-ns 'logseq.graph-parser.text-test)
 #_(cljs.test/test-ns 'logseq.graph-parser.text-test)

+ 138 - 1
deps/graph-parser/test/logseq/graph_parser_test.cljs

@@ -1,9 +1,10 @@
 (ns logseq.graph-parser-test
 (ns logseq.graph-parser-test
-  (:require [cljs.test :refer [deftest testing is]]
+  (:require [cljs.test :refer [deftest testing is are]]
             [clojure.string :as string]
             [clojure.string :as string]
             [logseq.graph-parser :as graph-parser]
             [logseq.graph-parser :as graph-parser]
             [logseq.db :as ldb]
             [logseq.db :as ldb]
             [logseq.graph-parser.block :as gp-block]
             [logseq.graph-parser.block :as gp-block]
+            [logseq.graph-parser.property :as gp-property]
             [datascript.core :as d]))
             [datascript.core :as d]))
 
 
 (deftest parse-file
 (deftest parse-file
@@ -68,3 +69,139 @@
     (test-property-order 4))
     (test-property-order 4))
   (testing "Sort order and persistence of 10 properties"
   (testing "Sort order and persistence of 10 properties"
     (test-property-order 10)))
     (test-property-order 10)))
+
+(defn- quoted-property-values-test
+  [user-config]
+  (let [conn (ldb/start-conn)
+        _ (graph-parser/parse-file conn
+                                   "foo.md"
+                                   "- desc:: \"#foo is not a ref\""
+                                   {:extract-options {:user-config user-config}})
+        block (->> (d/q '[:find (pull ?b [* {:block/refs [*]}])
+                       :in $
+                       :where [?b :block/properties]]
+                     @conn)
+                (map first)
+                first)]
+    (is (= {:desc "\"#foo is not a ref\""}
+           (:block/properties block))
+        "Quoted value is unparsed")
+    (is (= ["desc"]
+           (map :block/original-name (:block/refs block)))
+        "No refs from property value")))
+
+(deftest quoted-property-values
+  (testing "With default config"
+    (quoted-property-values-test {}))
+  (testing "With :rich-property-values config"
+    (quoted-property-values-test {:rich-property-values? true})))
+
+(deftest page-properties-persistence
+  (testing "Non-string property values"
+    (let [conn (ldb/start-conn)]
+      (graph-parser/parse-file conn
+                               "lythe-of-heaven.md"
+                               "rating:: 8\nrecommend:: true\narchive:: false"
+                               {})
+      (is (= {:rating 8 :recommend true :archive false}
+             (->> (d/q '[:find (pull ?b [*])
+                         :in $
+                         :where [?b :block/properties]]
+                       @conn)
+                  (map (comp :block/properties first))
+                  first)))))
+
+  (testing "Linkable built-in properties"
+    (let [conn (ldb/start-conn)
+          _ (graph-parser/parse-file conn
+                                     "lol.md"
+                                     "alias:: 233\ntags:: fun, facts"
+                                     {})
+          block (->> (d/q '[:find (pull ?b [:block/properties {:block/alias [:block/name]} {:block/tags [:block/name]}])
+                            :in $
+                            :where [?b :block/name "lol"]]
+                          @conn)
+                     (map first)
+                     first)]
+
+      (is (= {:block/alias [{:block/name "233"}]
+              :block/tags [{:block/name "fun"} {:block/name "facts"}]
+              :block/properties {:alias ["233"] :tags ["fun" "facts"]}}
+             block))
+
+      (is (every? vector? (vals (:block/properties block)))
+          "Linked built-in property values as vectors provides for easier transforms"))))
+
+(defn- property-relationships-test
+  "Runs tests on page properties and block properties. file-properties is what is
+  visible in a file and db-properties is what is pulled out from the db"
+  [file-properties db-properties user-config]
+  (let [conn (ldb/start-conn)
+        page-content (gp-property/->block-content file-properties)
+        ;; Create Block properties from given page ones
+        block-property-transform (fn [m] (update-keys m #(keyword (str "block-" (name %)))))
+        block-content (gp-property/->block-content (block-property-transform file-properties))
+        _ (graph-parser/parse-file conn
+                                   "property-relationships.md"
+                                   (str page-content "\n- " block-content)
+                                   {:extract-options {:user-config user-config}})
+        pages (->> (d/q '[:find (pull ?b [* :block/properties])
+                          :in $
+                          :where [?b :block/name] [?b :block/properties]]
+                        @conn)
+                   (map first))
+        _ (assert (= 1 (count pages)))
+        blocks (->> (d/q '[:find (pull ?b [:block/pre-block? :block/properties
+                                           {:block/refs [:block/original-name]}])
+                           :in $
+                           :where [?b :block/properties] [(missing? $ ?b :block/name)]]
+                         @conn)
+                    (map first)
+                    (map (fn [m] (update m :block/refs #(map :block/original-name %)))))
+        block-db-properties (block-property-transform db-properties)]
+
+    (is (= db-properties (:block/properties (first pages)))
+        "page has expected properties")
+
+    (is (= [true nil] (map :block/pre-block? blocks))
+        "page has 2 blocks, one of which is a pre-block")
+
+    (is (= [db-properties block-db-properties]
+           (map :block/properties blocks))
+        "pre-block/page and block have expected properties")
+
+    ;; has expected refs
+    (are [db-props refs]
+         (= (->> (vals db-props)
+                 ;; ignore string values
+                 (mapcat #(if (coll? %) % []))
+                 (concat (map name (keys db-props)))
+                 set)
+            (set refs))
+         ; pre-block/page has expected refs
+         db-properties (first (map :block/refs blocks))
+         ;; block has expected refs
+         block-db-properties (second (map :block/refs blocks)))))
+
+(deftest property-relationships
+  (let [properties {:single-link "[[bar]]"
+                    :multi-link "[[Logseq]] is the fastest #triples #[[text editor]]"
+                    :desc "This is a multiple sentence description. It has one [[link]]"
+                    :comma-prop "one, two,three"}]
+    (testing "With default config"
+      (property-relationships-test
+       properties
+       {:single-link #{"bar"}
+        :multi-link #{"Logseq" "is the fastest" "triples" "text editor"}
+        :desc #{"This is a multiple sentence description. It has one" "link"}
+        :comma-prop #{"one" "two" "three"}}
+       {}))
+
+    (testing "With :rich-property-values config"
+      (property-relationships-test
+       properties
+       {:single-link #{"bar"}
+        :multi-link #{"Logseq" "triples" "text editor"}
+        :desc #{"link"}
+        :comma-prop "one, two,three"}
+       {:rich-property-values? true}))))

+ 1 - 1
shadow-cljs.edn

@@ -66,7 +66,7 @@
          :devtools        {:enabled false}
          :devtools        {:enabled false}
          ;; disable :static-fns to allow for with-redefs and repl development
          ;; disable :static-fns to allow for with-redefs and repl development
          :compiler-options {:static-fns false}
          :compiler-options {:static-fns false}
-         :main            frontend.test.node-test-runner/main}
+         :main            frontend.test.frontend-node-test-runner/main}
 
 
   :publishing {:target        :browser
   :publishing {:target        :browser
                :module-loader true
                :module-loader true

+ 10 - 3
src/main/frontend/components/block.cljs

@@ -63,6 +63,7 @@
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.text :as text]
             [logseq.graph-parser.text :as text]
+            [logseq.graph-parser.property :as gp-property]
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.util.page-ref :as page-ref]
             [logseq.graph-parser.util.page-ref :as page-ref]
             [logseq.graph-parser.util.block-ref :as block-ref]
             [logseq.graph-parser.util.block-ref :as block-ref]
@@ -1803,9 +1804,15 @@
   [:span ", "])
   [:span ", "])
 
 
 (rum/defc property-cp
 (rum/defc property-cp
-  [config block k v]
-  (let [date (and (= k :date) (date/get-locale-string (str v)))
-        property-pages-enabled? (contains? #{true nil} (:property-pages/enabled? (state/get-config)))]
+  [config block k value]
+  (let [date (and (= k :date) (date/get-locale-string (str value)))
+        user-config (state/get-config)
+        ;; In this mode and when value is a set of refs, display full property text
+        ;; because :block/properties value only contains refs but user wants to see text
+        v (if (and (:rich-property-values? user-config) (coll? value))
+            (gp-property/property-value-from-content (name k) (:block/content block))
+            value)
+        property-pages-enabled? (contains? #{true nil} (:property-pages/enabled? user-config))]
     [:div
     [:div
      (if property-pages-enabled?
      (if property-pages-enabled?
        (page-cp (assoc config :property? true) {:block/name (subs (str k) 1)})
        (page-cp (assoc config :property? true) {:block/name (subs (str k) 1)})

+ 10 - 3
src/main/frontend/db/query_dsl.cljs

@@ -6,7 +6,6 @@
             [clojure.set :as set]
             [clojure.set :as set]
             [clojure.string :as string]
             [clojure.string :as string]
             [clojure.walk :as walk]
             [clojure.walk :as walk]
-            [frontend.state :as state]
             [frontend.date :as date]
             [frontend.date :as date]
             [frontend.db.model :as model]
             [frontend.db.model :as model]
             [frontend.db.query-react :as query-react]
             [frontend.db.query-react :as query-react]
@@ -235,12 +234,20 @@
     (= 4 (count e))
     (= 4 (count e))
     (build-between-three-arg e)))
     (build-between-three-arg e)))
 
 
+
+(defn parse-property-value
+  "Parses non-string property values or any page-ref like values"
+  [v]
+  (if-some [res (text/parse-non-string-property-value v)]
+    res
+    (text/split-page-refs-without-brackets v)))
+
 (defn- build-property-two-arg
 (defn- build-property-two-arg
   [e]
   [e]
   (let [k (string/replace (name (nth e 1)) "_" "-")
   (let [k (string/replace (name (nth e 1)) "_" "-")
         v (nth e 2)
         v (nth e 2)
         v (if-not (nil? v)
         v (if-not (nil? v)
-            (text/parse-property k v (state/get-config))
+            (parse-property-value (str v))
             v)
             v)
         v (if (coll? v) (first v) v)]
         v (if (coll? v) (first v) v)]
     {:query (list 'property '?b (keyword k) v)
     {:query (list 'property '?b (keyword k) v)
@@ -285,7 +292,7 @@
   (let [[k v] (rest e)
   (let [[k v] (rest e)
         k (string/replace (name k) "_" "-")]
         k (string/replace (name k) "_" "-")]
     (if (some? v)
     (if (some? v)
-      (let [v' (text/parse-property k v (state/get-config))
+      (let [v' (parse-property-value (str v))
             val (if (coll? v') (first v') v')]
             val (if (coll? v') (first v') v')]
         {:query (list 'page-property '?p (keyword k) val)
         {:query (list 'page-property '?p (keyword k) val)
          :rules [:page-property]})
          :rules [:page-property]})

+ 42 - 9
src/test/frontend/db/query_dsl_test.cljs

@@ -3,7 +3,7 @@
             [clojure.string :as str]
             [clojure.string :as str]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.db.query-dsl :as query-dsl]
             [frontend.db.query-dsl :as query-dsl]
-            [frontend.test.helper :as test-helper :refer [load-test-files]]))
+            [frontend.test.helper :as test-helper :include-macros true :refer [load-test-files]]))
 
 
 ;; TODO: quickcheck
 ;; TODO: quickcheck
 ;; 1. generate query filters
 ;; 1. generate query filters
@@ -45,7 +45,8 @@
 ;; Tests
 ;; Tests
 ;; =====
 ;; =====
 
 
-(deftest block-property-queries
+(defn- block-property-queries-test
+  []
   (load-test-files [{:file/path "journals/2022_02_28.md"
   (load-test-files [{:file/path "journals/2022_02_28.md"
                      :file/content "a:: b
                      :file/content "a:: b
 - b1
 - b1
@@ -64,7 +65,7 @@ prop-d:: nada"}])
   (testing "Blocks have given property value"
   (testing "Blocks have given property value"
     (is (= #{"b1" "b2"}
     (is (= #{"b1" "b2"}
            (set (map (comp first str/split-lines :block/content)
            (set (map (comp first str/split-lines :block/content)
-                 (dsl-query "(property prop-a val-a)")))))
+                     (dsl-query "(property prop-a val-a)")))))
 
 
     (is (= ["b2"]
     (is (= ["b2"]
            (map (comp first str/split-lines :block/content)
            (map (comp first str/split-lines :block/content)
@@ -112,15 +113,27 @@ prop-d:: nada"}])
               (dsl-query "(property prop-d)")))
               (dsl-query "(property prop-d)")))
       "Blocks that have a property"))
       "Blocks that have a property"))
 
 
-(deftest page-property-queries
+(deftest block-property-queries
+  (testing "block property tests with default config"
+    (test-helper/with-config {}
+      (block-property-queries-test)))
+
+  (test-helper/start-test-db!) ;; reset db
+
+  (testing "block property tests with rich-property-values? config"
+    (test-helper/with-config {:rich-property-values? true}
+      (block-property-queries-test))))
+
+(defn- page-property-queries-test
+  []
   (load-test-files [{:file/path "pages/page1.md"
   (load-test-files [{:file/path "pages/page1.md"
-                     :file/content "parent:: [[child page 1]], [[child-no-space]]"}
+                     :file/content "parent:: [[child page 1]], [[child-no-space]]\ninteresting:: true"}
                     {:file/path "pages/page2.md"
                     {:file/path "pages/page2.md"
-                     :file/content "foo:: bar"}
+                     :file/content "foo:: #bar\ninteresting:: false"}
                     {:file/path "pages/page3.md"
                     {:file/path "pages/page3.md"
-                     :file/content "parent:: [[child page 1]], child page 2\nfoo:: bar"}
+                     :file/content "parent:: [[child page 1]], [[child page 2]]\nfoo:: bar\ninteresting:: false"}
                     {:file/path "pages/page4.md"
                     {:file/path "pages/page4.md"
-                     :file/content "parent:: child page 2\nfoo:: baz"}])
+                     :file/content "parent:: [[child page 2]]\nfoo:: baz"}])
 
 
   (is (= ["page1" "page3" "page4"]
   (is (= ["page1" "page3" "page4"]
          (map :block/name (dsl-query "(page-property parent)")))
          (map :block/name (dsl-query "(page-property parent)")))
@@ -160,7 +173,27 @@ prop-d:: nada"}])
          (map
          (map
           :block/name
           :block/name
           (dsl-query "(and (not (page-property foo bar)) (page-property parent [[child page 2]]))")))
           (dsl-query "(and (not (page-property foo bar)) (page-property parent [[child page 2]]))")))
-      "Page property queries nested NOT in first clause"))
+      "Page property queries nested NOT in first clause")
+
+  (testing "boolean values"
+    (is (= ["page1"]
+           (map :block/name (dsl-query "(page-property interesting true)")))
+        "Boolean true")
+
+    (is (= ["page2" "page3"]
+           (map :block/name (dsl-query "(page-property interesting false)")))
+        "Boolean false")))
+
+(deftest page-property-queries
+  (testing "page property tests with default config"
+    (test-helper/with-config {}
+      (page-property-queries-test)))
+
+  (test-helper/start-test-db!) ;; reset db
+
+  (testing "page property tests with rich-property-values? config"
+    (test-helper/with-config {:rich-property-values? true}
+      (page-property-queries-test))))
 
 
 (deftest task-queries
 (deftest task-queries
   (load-test-files [{:file/path "pages/page1.md"
   (load-test-files [{:file/path "pages/page1.md"

+ 19 - 0
src/test/frontend/test/frontend_node_test_runner.cljs

@@ -0,0 +1,19 @@
+(ns frontend.test.frontend-node-test-runner
+  "This is a custom version of the node-test-runner for the frontend build"
+  {:dev/always true} ;; necessary for test-data freshness
+  (:require [frontend.test.node-test-runner :as node-test-runner]
+            [shadow.test.env :as env]
+            [lambdaisland.glogi.console :as glogi-console]
+            ;; activate humane test output for all tests
+            [pjstadig.humane-test-output]))
+
+;; Needed for new test runners
+(defn ^:dev/after-load reset-test-data! []
+  (-> (env/get-test-data)
+      (env/reset-test-data!)))
+
+(defn main [& args]
+  []
+  (glogi-console/install!) ;; see log messages
+  (reset-test-data!)
+  (node-test-runner/parse-and-run-tests args))

+ 8 - 0
src/test/frontend/test/helper.clj

@@ -0,0 +1,8 @@
+(ns frontend.test.helper)
+
+(defmacro with-config
+  [config & body]
+  `(let [repo# (frontend.state/get-current-repo)]
+     (frontend.state/set-config! repo# ~config)
+     ~@body
+     (frontend.state/set-config! repo# nil)))

+ 16 - 12
src/test/frontend/test/node_test_runner.cljs

@@ -1,6 +1,6 @@
 (ns frontend.test.node-test-runner
 (ns frontend.test.node-test-runner
-  "shadow-cljs test runner for :node-test that provides the same test selection
-  options as
+  "Application agnostic shadow-cljs test runner for :node-test that provides the
+  same test selection options as
   https://github.com/cognitect-labs/test-runner#invoke-with-clojure--m-clojuremain.
   https://github.com/cognitect-labs/test-runner#invoke-with-clojure--m-clojuremain.
   This gives the user a fair amount of control over which tests and namespaces
   This gives the user a fair amount of control over which tests and namespaces
   to call from the commandline. Once this test runner is stable enough we should
   to call from the commandline. Once this test runner is stable enough we should
@@ -12,9 +12,7 @@
             [clojure.set :as set]
             [clojure.set :as set]
             [shadow.test :as st]
             [shadow.test :as st]
             [cljs.test :as ct]
             [cljs.test :as ct]
-            ["util" :as util]
-            ;; activate humane test output for all tests
-            [pjstadig.humane-test-output]))
+            [goog.string :as gstring]))
 
 
 ;; Cljs.test customization
 ;; Cljs.test customization
 ;; Inherit behavior from default reporter
 ;; Inherit behavior from default reporter
@@ -47,10 +45,10 @@
 (defn- print-summary
 (defn- print-summary
   "Print help summary given args and opts strings"
   "Print help summary given args and opts strings"
   [options-summary additional-msg]
   [options-summary additional-msg]
-  (println (util/format "Usage: %s [OPTIONS]\nOptions:\n%s%s"
-                        "$0"
-                        options-summary
-                        additional-msg)))
+  (println (gstring/format "Usage: %s [OPTIONS]\nOptions:\n%s%s"
+                           "$0"
+                           options-summary
+                           additional-msg)))
 
 
 (defn- parse-options
 (defn- parse-options
   "Processes a command's functionality given a cli options definition, arguments
   "Processes a command's functionality given a cli options definition, arguments
@@ -172,9 +170,9 @@ returns selected tests and namespaces to run"
         (st/run-test-vars test-env test-vars))
         (st/run-test-vars test-env test-vars))
       (st/run-all-tests test-env nil))))
       (st/run-all-tests test-env nil))))
 
 
-(defn main [& args]
-  (reset-test-data!)
-
+(defn parse-and-run-tests
+  "Main entry point for custom test runners"
+  [args]
   (let [{:keys [options summary]} (parse-options args cli-options)]
   (let [{:keys [options summary]} (parse-options args cli-options)]
     (if (:help options)
     (if (:help options)
       (do
       (do
@@ -182,3 +180,9 @@ returns selected tests and namespaces to run"
                        "\n\nMultiple options are ANDed. Defaults to running all tests")
                        "\n\nMultiple options are ANDed. Defaults to running all tests")
         (js/process.exit 0))
         (js/process.exit 0))
       (run-tests (keys (env/get-tests)) (env/get-test-vars) options))))
       (run-tests (keys (env/get-tests)) (env/get-test-vars) options))))
+
+(defn main
+  "Main entry point if this ns is configured as a test runner"
+  [& args]
+  (reset-test-data!)
+  (parse-and-run-tests args))

+ 4 - 0
templates/config.edn

@@ -228,6 +228,10 @@
  ;; E.g.:property-pages/excludelist #{:duration :author}
  ;; E.g.:property-pages/excludelist #{:duration :author}
  ;; :property-pages/excludelist
  ;; :property-pages/excludelist
 
 
+ ;; Enables property values to contain a mix of tags, page-refs, special
+ ;; punctuation and free-form text
+ ;; :rich-property-values? true
+
  ;; logbook setup
  ;; logbook setup
  ;; :logbook/settings
  ;; :logbook/settings
  ;; {:with-second-support? false ;limit logbook to minutes, seconds will be eliminated
  ;; {:with-second-support? false ;limit logbook to minutes, seconds will be eliminated