Explorar el Código

Merge remote-tracking branch 'upstream/master' into whiteboards

Peng Xiao hace 3 años
padre
commit
37d0ad2524
Se han modificado 44 ficheros con 615 adiciones y 99 borrados
  1. 2 1
      .carve/ignore
  2. 2 2
      android/app/build.gradle
  3. 4 0
      deps/graph-parser/.carve/ignore
  4. 7 1
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  5. 2 1
      deps/graph-parser/src/logseq/graph_parser/cli.cljs
  6. 3 3
      deps/graph-parser/src/logseq/graph_parser/config.cljs
  7. 2 1
      deps/graph-parser/src/logseq/graph_parser/mldoc.cljc
  8. 14 1
      deps/graph-parser/src/logseq/graph_parser/property.cljs
  9. 39 11
      deps/graph-parser/src/logseq/graph_parser/text.cljs
  10. 1 7
      deps/graph-parser/src/logseq/graph_parser/util.cljs
  11. 5 0
      deps/graph-parser/src/logseq/graph_parser/util/page_ref.cljs
  12. 14 1
      deps/graph-parser/test/logseq/graph_parser/property_test.cljs
  13. 6 1
      deps/graph-parser/test/logseq/graph_parser/text_test.cljs
  14. 138 1
      deps/graph-parser/test/logseq/graph_parser_test.cljs
  15. 1 1
      resources/js/preload.js
  16. 1 1
      resources/package.json
  17. 1 1
      shadow-cljs.edn
  18. 18 0
      src/electron/electron/find_in_page.cljs
  19. 8 1
      src/electron/electron/handler.cljs
  20. 2 2
      src/electron/electron/window.cljs
  21. 11 1
      src/main/electron/listener.cljs
  22. 10 3
      src/main/frontend/components/block.cljs
  23. 87 0
      src/main/frontend/components/find_in_page.cljs
  24. 21 0
      src/main/frontend/components/find_in_page.css
  25. 1 1
      src/main/frontend/components/search.cljs
  26. 4 0
      src/main/frontend/components/sidebar.cljs
  27. 1 1
      src/main/frontend/db.cljs
  28. 0 12
      src/main/frontend/db/model.cljs
  29. 10 3
      src/main/frontend/db/query_dsl.cljs
  30. 3 2
      src/main/frontend/handler/editor.cljs
  31. 8 5
      src/main/frontend/handler/page.cljs
  32. 54 1
      src/main/frontend/handler/search.cljs
  33. 21 1
      src/main/frontend/modules/shortcut/config.cljs
  34. 3 0
      src/main/frontend/modules/shortcut/dicts.cljc
  35. 1 0
      src/main/frontend/state.cljs
  36. 7 1
      src/main/frontend/ui.cljs
  37. 5 3
      src/main/frontend/util.cljc
  38. 8 6
      src/main/frontend/utils.js
  39. 1 1
      src/main/frontend/version.cljs
  40. 42 9
      src/test/frontend/db/query_dsl_test.cljs
  41. 19 0
      src/test/frontend/test/frontend_node_test_runner.cljs
  42. 8 0
      src/test/frontend/test/helper.clj
  43. 16 12
      src/test/frontend/test/node_test_runner.cljs
  44. 4 0
      templates/config.edn

+ 2 - 1
.carve/ignore

@@ -75,7 +75,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

+ 2 - 2
android/app/build.gradle

@@ -6,8 +6,8 @@ android {
         applicationId "com.logseq.app"
         applicationId "com.logseq.app"
         minSdkVersion rootProject.ext.minSdkVersion
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 35
-        versionName "0.8.1"
+        versionCode 36
+        versionName "0.8.2"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
         aaptOptions {
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

+ 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")
 ;; TODO read configurable value?
 ;; TODO read configurable value?

+ 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

@@ -6,12 +6,6 @@
             [logseq.graph-parser.log :as log]
             [logseq.graph-parser.log :as log]
             [cljs.reader :as reader]))
             [cljs.reader :as reader]))
 
 
-(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]
@@ -40,7 +34,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]))
 
 
 (def foo-edn
 (def foo-edn
@@ -97,3 +98,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
resources/js/preload.js

@@ -72,7 +72,7 @@ contextBridge.exposeInMainWorld('apis', {
 
 
   showItemInFolder (fullpath) {
   showItemInFolder (fullpath) {
     if (IS_WIN32) {
     if (IS_WIN32) {
-      shell.openPath(path.dirname(fullpath))
+      shell.openPath(path.dirname(fullpath).replaceAll("/", "\\"))
     } else {
     } else {
       shell.showItemInFolder(fullpath)
       shell.showItemInFolder(fullpath)
     }
     }

+ 1 - 1
resources/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "Logseq",
   "name": "Logseq",
-  "version": "0.8.1",
+  "version": "0.8.2",
   "main": "electron.js",
   "main": "electron.js",
   "author": "Logseq",
   "author": "Logseq",
   "license": "AGPL-3.0",
   "license": "AGPL-3.0",

+ 1 - 1
shadow-cljs.edn

@@ -72,7 +72,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

+ 18 - 0
src/electron/electron/find_in_page.cljs

@@ -0,0 +1,18 @@
+(ns electron.find-in-page
+  (:require [electron.utils :as utils]
+            [cljs-bean.core :as bean]))
+
+(defn find!
+  [^js window search option]
+  (when window
+    (let [contents ^js (.-webContents window)]
+      (.findInPage contents search option)
+      (.on contents "found-in-page"
+           (fn [_event result]
+             (utils/send-to-renderer window "foundInPage" (bean/->clj result))))
+      true)))
+
+(defn clear!
+  [^js window]
+  (when window
+    (.stopFindInPage ^js (.-webContents window) "clearSelection")))

+ 8 - 1
src/electron/electron/handler.cljs

@@ -21,7 +21,8 @@
             [electron.plugin :as plugin]
             [electron.plugin :as plugin]
             [electron.window :as win]
             [electron.window :as win]
             [electron.file-sync-rsapi :as rsapi]
             [electron.file-sync-rsapi :as rsapi]
-            [electron.backup-file :as backup-file]))
+            [electron.backup-file :as backup-file]
+            [electron.find-in-page :as find]))
 
 
 (defmulti handle (fn [_window args] (keyword (first args))))
 (defmulti handle (fn [_window args] (keyword (first args))))
 
 
@@ -536,6 +537,12 @@
     (f)
     (f)
     (state/set-state! :window/once-persist-done nil)))
     (state/set-state! :window/once-persist-done nil)))
 
 
+(defmethod handle :find-in-page [^js win [_ search option]]
+  (find/find! win search (bean/->js option)))
+
+(defmethod handle :clear-find-in-page [^js win [_]]
+  (find/clear! win))
+
 (defn set-ipc-handler! [window]
 (defn set-ipc-handler! [window]
   (let [main-channel "main"]
   (let [main-channel "main"]
     (.handle ipcMain main-channel
     (.handle ipcMain main-channel

+ 2 - 2
src/electron/electron/window.cljs

@@ -118,7 +118,7 @@
 (defn setup-window-listeners!
 (defn setup-window-listeners!
   [^js win]
   [^js win]
   (when win
   (when win
-    (let [web-contents (. win -webContents)          
+    (let [web-contents (. win -webContents)
           new-win-handler
           new-win-handler
           (fn [e url]
           (fn [e url]
             (let [url (if (string/starts-with? url "file:")
             (let [url (if (string/starts-with? url "file:")
@@ -140,7 +140,7 @@
 
 
           context-menu-handler
           context-menu-handler
           (context-menu/setup-context-menu! win)]
           (context-menu/setup-context-menu! win)]
-      
+
       (doto web-contents
       (doto web-contents
         (.on "new-window" new-win-handler)
         (.on "new-window" new-win-handler)
         (.on "will-navigate" will-navigate-handler))
         (.on "will-navigate" will-navigate-handler))

+ 11 - 1
src/main/electron/listener.cljs

@@ -17,7 +17,8 @@
             [frontend.ui :as ui]
             [frontend.ui :as ui]
             [frontend.handler.notification :as notification]
             [frontend.handler.notification :as notification]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.repo :as repo-handler]
-            [frontend.handler.user :as user]))
+            [frontend.handler.user :as user]
+            [dommy.core :as dom]))
 
 
 (defn persist-dbs!
 (defn persist-dbs!
   []
   []
@@ -124,6 +125,15 @@
                                        :on-error   error-f}]
                                        :on-error   error-f}]
                          (repo-handler/persist-db! repo handlers))))
                          (repo-handler/persist-db! repo handlers))))
 
 
+  (js/window.apis.on "foundInPage"
+                     (fn [data]
+                       (let [data' (bean/->clj data)]
+                         (state/set-state! [:ui/find-in-page :matches] data')
+                         (dom/remove-style! (dom/by-id "search-in-page-input") :visibility)
+                         (dom/set-text! (dom/by-id "search-in-page-placeholder") "")
+                         (ui/focus-element "search-in-page-input")
+                         true)))
+
   (js/window.apis.on "loginCallback"
   (js/window.apis.on "loginCallback"
                      (fn [code]
                      (fn [code]
                        (user/login-callback code)))
                        (user/login-callback code)))

+ 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.block-ref :as block-ref]
             [logseq.graph-parser.util.block-ref :as block-ref]
             [logseq.graph-parser.util.page-ref :as page-ref]
             [logseq.graph-parser.util.page-ref :as page-ref]
@@ -1821,9 +1822,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)})

+ 87 - 0
src/main/frontend/components/find_in_page.cljs

@@ -0,0 +1,87 @@
+(ns frontend.components.find-in-page
+  (:require [rum.core :as rum]
+            [frontend.ui :as ui]
+            [frontend.state :as state]
+            [frontend.util :as util]
+            [frontend.handler.search :as search-handler :refer [debounced-search]]
+            [goog.dom :as gdom]
+            [frontend.mixins :as mixins]
+            [clojure.string :as string]))
+
+(rum/defc search-input
+  [q matches]
+  [:div.flex.w-48.relative
+   [:input#search-in-page-input.form-input.block.sm:text-sm.sm:leading-5.my-2.border-none.mr-4.outline-none
+    {:auto-focus true
+     :placeholder "Find in page"
+     :aria-label "Find in page"
+     :value q
+     :on-change (fn [e]
+                  (let [value (util/evalue e)]
+                    (state/set-state! [:ui/find-in-page :q] value)
+                    (debounced-search)))}]
+   (when-not (string/blank? q)
+     (when-let [total (:matches matches)]
+      [:div.text-sm.absolute.top-2.right-0.py-2.px-4
+       (:activeMatchOrdinal matches 0)
+       "/"
+       total]))
+   [:div#search-in-page-placeholder.absolute.top-2.left-0.p-2.sm:text-sm]])
+
+(rum/defc search-inner < rum/static
+  (mixins/event-mixin
+   (fn [state]
+     (mixins/hide-when-esc-or-outside
+      state
+      :node (gdom/getElement "search-in-page")
+      :on-hide (fn []
+                 (search-handler/electron-exit-find-in-page!)))))
+  [{:keys [matches match-case? q]}]
+  [:div#search-in-page.flex.flex-row.absolute.top-2.right-4.shadow-lg.px-2.py-1.faster-fade-in.items-center
+
+   (search-input q matches)
+
+   (ui/button
+    (ui/icon "letter-case")
+    :on-click (fn []
+                (state/update-state! [:ui/find-in-page :match-case?] not)
+                (debounced-search))
+    :intent "link"
+    :small? true
+    :title "Match case"
+    :class (str (when match-case? "active ") "text-lg"))
+
+   (ui/button
+    (ui/icon "caret-up")
+    :on-click (fn []
+                (state/set-state! [:ui/find-in-page :backward?] true)
+                (debounced-search))
+    :intent "link"
+    :small? true
+    :class "text-lg"
+    :title "Previous result")
+
+   (ui/button
+    (ui/icon "caret-down")
+    :on-click (fn []
+                (state/set-state! [:ui/find-in-page :backward?] false)
+                (debounced-search))
+    :intent "link"
+    :small? true
+    :class "text-lg"
+    :title "Next result")
+
+   (ui/button
+    (ui/icon "x")
+    :on-click (fn []
+                (search-handler/electron-exit-find-in-page!))
+    :intent "link"
+    :small? true
+    :class "text-lg"
+    :title "Close")])
+
+(rum/defc search < rum/reactive
+  []
+  (let [{:keys [active?] :as opt} (state/sub :ui/find-in-page)]
+    (when active?
+      (search-inner opt))))

+ 21 - 0
src/main/frontend/components/find_in_page.css

@@ -0,0 +1,21 @@
+#search-in-page {
+  z-index: 999;
+  background-color: var(--ls-primary-background-color);
+
+  .form-input:focus {
+      box-shadow: none;
+  }
+
+  .ui__button[intent='link'],
+  .ui__button[intent='link']:focus {
+      border-color: none;
+  }
+
+  .ui__button {
+      margin-top: 0;
+
+      &.active {
+        color: var(--ls-link-text-color);
+      }
+  }
+}

+ 1 - 1
src/main/frontend/components/search.cljs

@@ -297,7 +297,7 @@
     [:div "Recent search:"]
     [:div "Recent search:"]
     (ui/with-shortcut :go/search-in-page "bottom"
     (ui/with-shortcut :go/search-in-page "bottom"
       [:div.flex-row.flex.align-items
       [:div.flex-row.flex.align-items
-       [:div.mr-2 "Search in page:"]
+       [:div.mr-2 "Search blocks in page:"]
        [:div {:style {:margin-top 3}}
        [:div {:style {:margin-top 3}}
         (ui/toggle in-page-search?
         (ui/toggle in-page-search?
                    (fn [_value]
                    (fn [_value]

+ 4 - 0
src/main/frontend/components/sidebar.cljs

@@ -12,6 +12,7 @@
             [frontend.components.svg :as svg]
             [frontend.components.svg :as svg]
             [frontend.components.theme :as theme]
             [frontend.components.theme :as theme]
             [frontend.components.widgets :as widgets]
             [frontend.components.widgets :as widgets]
+            [frontend.components.find-in-page :as find-in-page]
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.db :as db]
@@ -347,6 +348,9 @@
      [:div#main-content-container.scrollbar-spacing.w-full.flex.justify-center.flex-row
      [:div#main-content-container.scrollbar-spacing.w-full.flex.justify-center.flex-row
       {:data-is-margin-less-pages margin-less-pages?}
       {:data-is-margin-less-pages margin-less-pages?}
 
 
+      (when (util/electron?)
+        (find-in-page/search))
+
       (when show-action-bar?
       (when show-action-bar?
         (action-bar/action-bar))
         (action-bar/action-bar))
 
 

+ 1 - 1
src/main/frontend/db.cljs

@@ -48,7 +48,7 @@
   get-files get-files-blocks get-files-full get-journals-length
   get-files get-files-blocks get-files-full get-journals-length
   get-latest-journals get-page get-page-alias get-page-alias-names get-paginated-blocks
   get-latest-journals get-page get-page-alias get-page-alias-names get-paginated-blocks
   get-page-blocks-count get-page-blocks-no-cache get-page-file get-page-format get-page-properties
   get-page-blocks-count get-page-blocks-no-cache get-page-file get-page-format get-page-properties
-  get-page-referenced-blocks get-page-referenced-blocks-full get-page-referenced-pages get-page-unlinked-references get-page-referenced-blocks-no-cache
+  get-page-referenced-blocks get-page-referenced-blocks-full get-page-referenced-pages get-page-unlinked-references
   get-all-pages get-pages get-pages-relation get-pages-that-mentioned-page get-public-pages get-tag-pages
   get-all-pages get-pages get-pages-relation get-pages-that-mentioned-page get-public-pages get-tag-pages
   journal-page? page-alias-set pull-block
   journal-page? page-alias-set pull-block
   set-file-last-modified-at! page-empty? page-exists? page-empty-or-dummy? get-alias-source-page
   set-file-last-modified-at! page-empty? page-exists? page-empty-or-dummy? get-alias-source-page

+ 0 - 12
src/main/frontend/db/model.cljs

@@ -1152,18 +1152,6 @@
                                db-utils/seq-flatten)]
                                db-utils/seq-flatten)]
       (mapv (fn [page] [page (get-page-alias repo page)]) mentioned-pages))))
       (mapv (fn [page] [page (get-page-alias repo page)]) mentioned-pages))))
 
 
-(defn get-page-referenced-blocks-no-cache
-  [page-id]
-  (when-let [repo (state/get-current-repo)]
-    (->>
-     (d/q '[:find (pull ?b [*])
-            :in $ ?page-id
-            :where
-            [?b :block/refs ?page-id]]
-          (conn/get-db repo)
-          page-id)
-     (flatten))))
-
 (defn get-page-referenced-blocks-full
 (defn get-page-referenced-blocks-full
   ([page]
   ([page]
    (get-page-referenced-blocks-full (state/get-current-repo) page nil))
    (get-page-referenced-blocks-full (state/get-current-repo) page nil))

+ 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]})

+ 3 - 2
src/main/frontend/handler/editor.cljs

@@ -2922,8 +2922,9 @@
 
 
 (defn- cut-blocks-and-clear-selections!
 (defn- cut-blocks-and-clear-selections!
   [copy?]
   [copy?]
-  (cut-selection-blocks copy?)
-  (clear-selection!))
+  (when-not (get-in @state/state [:ui/find-in-page :active?])
+    (cut-selection-blocks copy?)
+    (clear-selection!)))
 
 
 (defn shortcut-copy-selection
 (defn shortcut-copy-selection
   [_e]
   [_e]

+ 8 - 5
src/main/frontend/handler/page.cljs

@@ -373,9 +373,9 @@
   ;; update all pages which have references to this page
   ;; update all pages which have references to this page
   (let [repo (state/get-current-repo)
   (let [repo (state/get-current-repo)
         to-page (db/entity [:block/name (util/page-name-sanity-lc new-name)])
         to-page (db/entity [:block/name (util/page-name-sanity-lc new-name)])
-        blocks   (db/get-page-referenced-blocks-no-cache (:db/id page))
-        page-ids (->> (map :block/page blocks)
-                      (remove nil?)
+        blocks (:block/_refs (db/entity (:db/id page)))
+        page-ids (->> (map (fn [b]
+                             {:db/id (:db/id (:block/page b))}) blocks)
                       (set))
                       (set))
         tx       (->> (map (fn [{:block/keys [uuid content properties] :as block}]
         tx       (->> (map (fn [{:block/keys [uuid content properties] :as block}]
                              (let [content    (let [content' (replace-old-page! content old-original-name new-name)]
                              (let [content    (let [content' (replace-old-page! content old-original-name new-name)]
@@ -389,8 +389,11 @@
                                   {:block/uuid       uuid
                                   {:block/uuid       uuid
                                    :block/content    content
                                    :block/content    content
                                    :block/properties properties
                                    :block/properties properties
-                                   :block/properties-order (map first properties)
-                                   :block/refs (rename-update-block-refs! (:block/refs block) (:db/id page) (:db/id to-page))})))) blocks)
+                                   :block/properties-order (when (seq properties)
+                                                             (map first properties))
+                                   :block/refs (->> (rename-update-block-refs! (:block/refs block) (:db/id page) (:db/id to-page))
+                                                    (map :db/id)
+                                                    (set))})))) blocks)
                       (remove nil?))]
                       (remove nil?))]
     (db/transact! repo tx)
     (db/transact! repo tx)
     (doseq [page-id page-ids]
     (doseq [page-id page-ids]

+ 54 - 1
src/main/frontend/handler/search.cljs

@@ -9,7 +9,10 @@
             [promesa.core :as p]
             [promesa.core :as p]
             [logseq.graph-parser.text :as text]
             [logseq.graph-parser.text :as text]
             [frontend.util.drawer :as drawer]
             [frontend.util.drawer :as drawer]
-            [frontend.util.property :as property]))
+            [frontend.util.property :as property]
+            [electron.ipc :as ipc]
+            [goog.functions :refer [debounce]]
+            [dommy.core :as dom]))
 
 
 (defn add-search-to-recent!
 (defn add-search-to-recent!
   [repo q]
   [repo q]
@@ -51,6 +54,56 @@
            (swap! state/state assoc search-key result)
            (swap! state/state assoc search-key result)
            result))))))
            result))))))
 
 
+(defn open-find-in-page!
+  []
+  (when (util/electron?)
+    (let [{:keys [active?]} (:ui/find-in-page @state/state)]
+      (when-not active? (state/set-state! [:ui/find-in-page :active?] true)))))
+
+(defn electron-find-in-page!
+  []
+  (when (util/electron?)
+    (let [{:keys [active? backward? match-case? q]} (:ui/find-in-page @state/state)
+          option (cond->
+                  {}
+
+                   (not active?)
+                   (assoc :findNext true)
+
+                   backward?
+                   (assoc :forward false)
+
+                   match-case?
+                   (assoc :matchCase true))]
+      (open-find-in-page!)
+      (when-not (string/blank? q)
+        (dom/set-style! (dom/by-id "search-in-page-input")
+                        :visibility "hidden")
+        (when (> (count q) 1)
+          (dom/set-html! (dom/by-id "search-in-page-placeholder")
+                         (util/format
+                          "<span><span>%s</span><span style=\"margin-left: -4px;\">%s</span></span>"
+                          (first q)
+                          (str " " (subs q 1)))))
+        (ipc/ipc "find-in-page" q option)))))
+
+(defonce debounced-search (debounce electron-find-in-page! 500))
+
+(defn loop-find-in-page!
+  [backward?]
+  (when (and (get-in @state/state [:ui/find-in-page :active?])
+             (not (state/editing?)))
+    (state/set-state! [:ui/find-in-page :backward?] backward?)
+    (debounced-search)))
+
+(defn electron-exit-find-in-page!
+  [& {:keys [clear-state?]
+      :or {clear-state? true}}]
+  (when (util/electron?)
+    (ipc/ipc "clear-find-in-page")
+    (when clear-state?
+      (state/set-state! :ui/find-in-page nil))))
+
 (defn clear-search!
 (defn clear-search!
   ([]
   ([]
    (clear-search! true))
    (clear-search! true))

+ 21 - 1
src/main/frontend/modules/shortcut/config.cljs

@@ -249,6 +249,20 @@
                                                 (editor-handler/escape-editing)
                                                 (editor-handler/escape-editing)
                                                 (route-handler/go-to-search! :global))}
                                                 (route-handler/go-to-search! :global))}
 
 
+   :go/electron-find-in-page       {:binding "mod+f"
+                                    :fn      #(when (util/electron?)
+                                                (search-handler/open-find-in-page!))}
+
+   :go/electron-jump-to-the-next {:binding ["enter" "mod+g"]
+                                    :fn      (fn [_state _e]
+                                               (when (util/electron?)
+                                                 (search-handler/loop-find-in-page! false)))}
+
+   :go/electron-jump-to-the-previous {:binding ["shift+enter" "mod+shift+g"]
+                                             :fn      (fn [_state _e]
+                                                        (when (util/electron?)
+                                                          (search-handler/loop-find-in-page! true)))}
+
    :go/journals                    {:binding "g j"
    :go/journals                    {:binding "g j"
                                     :fn      route-handler/go-to-journals!}
                                     :fn      route-handler/go-to-journals!}
 
 
@@ -280,7 +294,7 @@
    :graph/open                     {:fn      #(do
    :graph/open                     {:fn      #(do
                                                 (editor-handler/escape-editing)
                                                 (editor-handler/escape-editing)
                                                 (state/set-state! :ui/open-select :graph-open))
                                                 (state/set-state! :ui/open-select :graph-open))
-                                    :binding "mod+shift+g"}
+                                    :binding "alt+shift+g"}
 
 
    :graph/remove                   {:fn      #(do
    :graph/remove                   {:fn      #(do
                                                 (editor-handler/escape-editing)
                                                 (editor-handler/escape-editing)
@@ -501,6 +515,9 @@
                           :ui/toggle-brackets
                           :ui/toggle-brackets
                           :go/search-in-page
                           :go/search-in-page
                           :go/search
                           :go/search
+                          :go/electron-find-in-page
+                          :go/electron-jump-to-the-next
+                          :go/electron-jump-to-the-previous
                           :go/backward
                           :go/backward
                           :go/forward
                           :go/forward
                           :search/re-index
                           :search/re-index
@@ -553,6 +570,9 @@
     :editor/select-all-blocks
     :editor/select-all-blocks
     :go/search
     :go/search
     :go/search-in-page
     :go/search-in-page
+    :go/electron-find-in-page
+    :go/electron-jump-to-the-next
+    :go/electron-jump-to-the-previous
     :editor/undo
     :editor/undo
     :editor/redo
     :editor/redo
     :editor/copy
     :editor/copy

+ 3 - 0
src/main/frontend/modules/shortcut/dicts.cljc

@@ -77,6 +77,9 @@
    :editor/zoom-out                "Zoom out editing block / Backwards otherwise"
    :editor/zoom-out                "Zoom out editing block / Backwards otherwise"
    :ui/toggle-brackets             "Toggle whether to display brackets"
    :ui/toggle-brackets             "Toggle whether to display brackets"
    :go/search-in-page              "Search in the current page"
    :go/search-in-page              "Search in the current page"
+   :go/electron-find-in-page       "Find in page"
+   :go/electron-jump-to-the-next   "Jump to the next match to your Find bar search"
+   :go/electron-jump-to-the-previous "Jump to the previous match to your Find bar search"
    :go/search                      "Full text search"
    :go/search                      "Full text search"
    :go/journals                    "Go to journals"
    :go/journals                    "Go to journals"
    :go/backward                    "Backwards"
    :go/backward                    "Backwards"

+ 1 - 0
src/main/frontend/state.cljs

@@ -232,6 +232,7 @@
 
 
      :encryption/graph-parsing?             false
      :encryption/graph-parsing?             false
 
 
+     :ui/find-in-page                     nil
      })))
      })))
 
 
 ;; block uuid -> {content(String) -> ast}
 ;; block uuid -> {content(String) -> ast}

+ 7 - 1
src/main/frontend/ui.cljs

@@ -166,7 +166,7 @@
    opts))
    opts))
 
 
 (defn button
 (defn button
-  [text & {:keys [background href class intent on-click small? large?]
+  [text & {:keys [background href class intent on-click small? large? title]
            :or   {small? false large? false}
            :or   {small? false large? false}
            :as   option}]
            :as   option}]
   (let [klass (when-not intent ".bg-indigo-600.hover:bg-indigo-700.focus:border-indigo-700.active:bg-indigo-700.text-center")
   (let [klass (when-not intent ".bg-indigo-600.hover:bg-indigo-700.focus:border-indigo-700.active:bg-indigo-700.text-center")
@@ -176,6 +176,7 @@
     [:button.ui__button
     [:button.ui__button
      (merge
      (merge
       {:type  "button"
       {:type  "button"
+       :title title
        :class (str (util/hiccup->class klass) " " class)}
        :class (str (util/hiccup->class klass) " " class)}
       (dissoc option :background :class :small? :large?)
       (dissoc option :background :class :small? :large?)
       (when href
       (when href
@@ -274,6 +275,11 @@
   []
   []
   (gdom/getElement "main-content-container"))
   (gdom/getElement "main-content-container"))
 
 
+(defn focus-element
+  [element]
+  (when-let [element ^js (gdom/getElement element)]
+    (.focus element)))
+
 (defn get-scroll-top []
 (defn get-scroll-top []
   (.-scrollTop (main-node)))
   (.-scrollTop (main-node)))
 
 

+ 5 - 3
src/main/frontend/util.cljc

@@ -79,8 +79,7 @@
    (defn electron?
    (defn electron?
      []
      []
      (when (and js/window (gobj/get js/window "navigator"))
      (when (and js/window (gobj/get js/window "navigator"))
-       (let [ua (string/lower-case js/navigator.userAgent)]
-         (string/includes? ua " electron")))))
+       (gstring/caseInsensitiveContains js/navigator.userAgent " electron"))))
 
 
 #?(:cljs
 #?(:cljs
    (defn mocked-open-dir-path
    (defn mocked-open-dir-path
@@ -486,7 +485,10 @@
 
 
 #?(:cljs
 #?(:cljs
    (defn safe-path-join [prefix & paths]
    (defn safe-path-join [prefix & paths]
-     (apply node-path.join (cons prefix paths))))
+     (let [path (apply node-path.join (cons prefix paths))]
+       (if (and (electron?) (gstring/caseInsensitiveStartsWith path "file://"))
+         (js/decodeURIComponent (subs path 7))
+         path))))
 
 
 (defn trim-safe
 (defn trim-safe
   [s]
   [s]

+ 8 - 6
src/main/frontend/utils.js

@@ -315,12 +315,14 @@ export const nodePath = Object.assign({}, path, {
   join (input, ...paths) {
   join (input, ...paths) {
     let orURI = null
     let orURI = null
 
 
-    try {
-      orURI = new URL(input)
-      input = input.replace(orURI.protocol + '//', '')
-        .replace(orURI.protocol, '')
-        .replace(/^\/+/, '/')
-    } catch (_e) {}
+    if (input.startsWith("file://")) {
+      try {
+        orURI = new URL(input)
+        input = input.replace(orURI.protocol + '//', '')
+          .replace(orURI.protocol, '')
+          .replace(/^\/+/, '/')
+      } catch (_e) {}
+    }
 
 
     input = path.join(input, ...paths)
     input = path.join(input, ...paths)
 
 

+ 1 - 1
src/main/frontend/version.cljs

@@ -1,3 +1,3 @@
 (ns frontend.version)
 (ns frontend.version)
 
 
-(defonce version "0.8.1")
+(defonce version "0.8.2")

+ 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