Browse Source

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

Peng Xiao 3 years ago
parent
commit
d3dae5fddc
50 changed files with 962 additions and 700 deletions
  1. 1 1
      .github/workflows/build.yml
  2. 1 1
      CODE_OF_CONDUCT.md
  3. 3 1
      README.md
  4. 2 2
      android/app/build.gradle
  5. 63 45
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  6. 1 1
      deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs
  7. 1 1
      deps/graph-parser/src/logseq/graph_parser/util.cljs
  8. 7 7
      deps/graph-parser/test/logseq/graph_parser/block_test.cljs
  9. 23 5
      docs/develop-logseq.md
  10. 2 0
      e2e-tests/basic.spec.ts
  11. 1 1
      e2e-tests/page-search.spec.ts
  12. 4 4
      ios/App/App.xcodeproj/project.pbxproj
  13. 7 2
      ios/App/App/FileContainer.swift
  14. 2 1
      libs/src/LSPlugin.ts
  15. 1 1
      package.json
  16. 1 1
      resources/package.json
  17. 46 41
      src/main/frontend/commands.cljs
  18. 16 15
      src/main/frontend/components/block.cljs
  19. 6 1
      src/main/frontend/components/block.css
  20. 3 4
      src/main/frontend/components/datetime.cljs
  21. 243 192
      src/main/frontend/components/editor.cljs
  22. 3 3
      src/main/frontend/components/header.cljs
  23. 1 4
      src/main/frontend/components/journal.cljs
  24. 4 4
      src/main/frontend/components/onboarding.cljs
  25. 13 8
      src/main/frontend/components/plugins.cljs
  26. 1 3
      src/main/frontend/components/reference.cljs
  27. 1 1
      src/main/frontend/components/search.cljs
  28. 2 2
      src/main/frontend/components/widgets.cljs
  29. 1 1
      src/main/frontend/core.cljs
  30. 34 0
      src/main/frontend/db/model.cljs
  31. 14 12
      src/main/frontend/dicts.cljc
  32. 30 13
      src/main/frontend/extensions/html_parser.cljs
  33. 50 40
      src/main/frontend/extensions/srs.cljs
  34. 5 3
      src/main/frontend/extensions/srs/handler.cljs
  35. 1 1
      src/main/frontend/extensions/zotero/handler.cljs
  36. 1 1
      src/main/frontend/fs/watcher_handler.cljs
  37. 201 156
      src/main/frontend/handler/editor.cljs
  38. 2 1
      src/main/frontend/handler/events.cljs
  39. 29 9
      src/main/frontend/handler/page.cljs
  40. 33 5
      src/main/frontend/search.cljs
  41. 38 46
      src/main/frontend/state.cljs
  42. 19 35
      src/main/frontend/ui.cljs
  43. 5 0
      src/main/frontend/util.cljc
  44. 4 3
      src/main/frontend/util/cursor.cljs
  45. 10 8
      src/main/frontend/util/text.cljs
  46. 1 1
      src/main/frontend/version.cljs
  47. 1 1
      src/test/frontend/db/query_dsl_test.cljs
  48. 8 1
      src/test/frontend/extensions/calc_test.cljc
  49. 3 3
      src/test/frontend/util/text_test.cljs
  50. 13 8
      yarn.lock

+ 1 - 1
.github/workflows/build.yml

@@ -179,7 +179,7 @@ jobs:
         run: xvfb-run -- yarn e2e-test
         run: xvfb-run -- yarn e2e-test
         env:
         env:
           CI: true
           CI: true
-          DEBUG: "pw:test"
+          DEBUG: "pw:api"
 
 
       - name: Save test artifacts
       - name: Save test artifacts
         if: ${{ failure() }}
         if: ${{ failure() }}

+ 1 - 1
CODE_OF_CONDUCT.md

@@ -13,4 +13,4 @@ This Code of Conduct applies within all community spaces, and also applies when
 - Please respect each other. Do not dismiss, abuse, harass, attack, insult, or discriminate against others.
 - Please respect each other. Do not dismiss, abuse, harass, attack, insult, or discriminate against others.
 - Likewise, any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
 - Likewise, any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
 - Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.
 - Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.
-- If you feel being harassed or made uncomfortable by a community member, please report the incident(s) either by contacting the moderators on the [Discord](https://discord.gg/KpN4eHY) channel, Tienson Qin (@tiensonqin) on GitHub, or the official [Twitter](https://twitter.com/logseq). We will work with you to resolve the issue promptly.
+- If you feel being harassed or made uncomfortable by a community member, please report the incident(s) either by contacting the moderators on the [Forum](https://discuss.logseq.com), [Discord](https://discord.gg/KpN4eHY) channel, Tienson Qin (@tiensonqin) on GitHub, or the official [Twitter](https://twitter.com/logseq). We will work with you to resolve the issue promptly.

+ 3 - 1
README.md

@@ -3,6 +3,7 @@
 [![latest release version](https://img.shields.io/github/v/release/logseq/logseq)](https://github.com/logseq/logseq/releases)
 [![latest release version](https://img.shields.io/github/v/release/logseq/logseq)](https://github.com/logseq/logseq/releases)
 [![License](https://img.shields.io/github/license/logseq/logseq?color=blue)](https://github.com/logseq/logseq/blob/master/LICENSE.md)
 [![License](https://img.shields.io/github/license/logseq/logseq?color=blue)](https://github.com/logseq/logseq/blob/master/LICENSE.md)
 [![Twitter follow](https://img.shields.io/badge/follow-%40logseq-blue.svg?style=flat&logo=twitter)](https://twitter.com/logseq)
 [![Twitter follow](https://img.shields.io/badge/follow-%40logseq-blue.svg?style=flat&logo=twitter)](https://twitter.com/logseq)
+[![forum](https://img.shields.io/badge/forum-Logseq-blue.svg?style=flat&logo=discourse)](https://discuss.logseq.com)
 [![discord](https://img.shields.io/discord/725182569297215569?label=discord&logo=Discord&color=blue)](https://discord.gg/KpN4eHY)
 [![discord](https://img.shields.io/discord/725182569297215569?label=discord&logo=Discord&color=blue)](https://discord.gg/KpN4eHY)
 [![total](https://opencollective.com/logseq/tiers/badge.svg?color=blue)](https://opencollective.com/logseq)
 [![total](https://opencollective.com/logseq/tiers/badge.svg?color=blue)](https://opencollective.com/logseq)
 
 
@@ -68,7 +69,8 @@ Logseq is also made possible by the following projects:
 
 
 - Our blog: https://logseq.com/blog - Please be sure to visit our [About page](https://logseq.com/blog/about) for the latest updates of the app
 - Our blog: https://logseq.com/blog - Please be sure to visit our [About page](https://logseq.com/blog/about) for the latest updates of the app
 - Twitter: https://twitter.com/logseq
 - Twitter: https://twitter.com/logseq
-- Discord: https://discord.gg/KpN4eHY - Where we answer questions, discuss workflows and share tips
+- Forum: https://discuss.logseq.com - Where we answer questions, discuss workflows and share tips
+- Discord: https://discord.gg/KpN4eHY
 - 中文 Discord:https://discord.gg/xYqcrXWymg
 - 中文 Discord:https://discord.gg/xYqcrXWymg
 - Github: https://github.com/logseq/logseq - everyone is encouraged to report issues!
 - Github: https://github.com/logseq/logseq - everyone is encouraged to report issues!
 
 

+ 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 29
-        versionName "0.7.5"
+        versionCode 30
+        versionName "0.7.6"
         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.

+ 63 - 45
deps/graph-parser/src/logseq/graph_parser/block.cljs

@@ -139,52 +139,78 @@
    (vector? block)
    (vector? block)
    (= "Timestamp" (first block))))
    (= "Timestamp" (first block))))
 
 
-;; TODO: we should move this to mldoc
-(defn extract-properties
-  [format properties user-config]
-  (when (seq properties)
-    (let [properties (seq properties)
-          page-refs (->>
+(defn- get-page-ref-names-from-properties
+  [format properties]
+  (let [page-refs (->>
                      properties
                      properties
                      (remove (fn [[k _]]
                      (remove (fn [[k _]]
                                (contains? #{:background-color :background_color} (keyword k))))
                                (contains? #{:background-color :background_color} (keyword k))))
                      (map last)
                      (map last)
                      (map (fn [v]
                      (map (fn [v]
-                            (when (and (string? v)
-                                       (not (gp-mldoc/link? format v)))
+                            (cond
+                              (and (string? 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 (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)
-                                  [])))))
-                     (apply concat)
-                     (remove string/blank?))
+                                  []))
+
+                              (coll? v)
+                              (map (fn [s]
+                                     (when-not (and (string? v)
+                                                    (gp-mldoc/link? format v))
+                                       (text/page-ref-un-brackets! s))) v)
+
+                              :else
+                              nil)))
+                     (apply concat))
+        property-keys-page-refs (some->> properties
+                                         (map (comp name first))
+                                         (remove string/blank?)
+                                         (distinct))]
+    (->> (concat page-refs property-keys-page-refs)
+         (remove string/blank?)
+         distinct)))
+
+(defn- invalid-property-key?
+  [s]
+  (string/includes? s "`"))
+
+(defn extract-properties
+  [format properties user-config]
+  (when (seq properties)
+    (let [properties (seq properties)
+          properties (into {} properties)
+          page-refs (get-page-ref-names-from-properties format properties)
           properties (->> properties
           properties (->> properties
                           (map (fn [[k v]]
                           (map (fn [[k v]]
                                  (let [k (-> (string/lower-case (name k))
                                  (let [k (-> (string/lower-case (name k))
                                              (string/replace " " "-")
                                              (string/replace " " "-")
-                                             (string/replace "_" "-"))
-                                       k (if (contains? #{"custom_id" "custom-id"} k)
-                                           "id"
-                                           k)
-                                       v (if (coll? v)
-                                           (remove string/blank? v)
-                                           (cond
-                                             (string/blank? v)
-                                             nil
-                                             (and (= (keyword k) :file-path)
-                                                  (string/starts-with? v "file:"))
-                                             v
-                                             :else
-                                             (text/parse-property format k v user-config)))
-                                       k (keyword k)
-                                       v (if (and
-                                              (string? v)
-                                              (contains? #{:alias :aliases :tags} k))
-                                           (set [v])
-                                           v)
-                                       v (if (coll? v) (set v) v)]
-                                   [k v])))
+                                             (string/replace "_" "-")
+                                             (string/replace #"[\"|^|(|)|{|}]+" ""))]
+                                   (when-not (invalid-property-key? k)
+                                     (let [k (if (contains? #{"custom_id" "custom-id"} k)
+                                               "id"
+                                               k)
+                                           v (if (coll? v)
+                                               (remove string/blank? v)
+                                               (cond
+                                                 (string/blank? v)
+                                                 nil
+                                                 (and (= (keyword k) :file-path)
+                                                      (string/starts-with? v "file:"))
+                                                 v
+                                                 :else
+                                                 (text/parse-property format k v user-config)))
+                                           k (keyword k)
+                                           v (if (and
+                                                  (string? v)
+                                                  (contains? #{:alias :aliases :tags} k))
+                                               (set [v])
+                                               v)
+                                           v (if (coll? v) (set v) v)]
+                                       [k v])))))
                           (remove #(nil? (second %))))]
                           (remove #(nil? (second %))))]
       {:properties (into {} properties)
       {:properties (into {} properties)
        :properties-order (map first properties)
        :properties-order (map first properties)
@@ -437,17 +463,8 @@
       (d/squuid)))
       (d/squuid)))
 
 
 (defn get-page-refs-from-properties
 (defn get-page-refs-from-properties
-  [properties db date-formatter]
-  (let [page-refs (mapcat (fn [v] (cond
-                                   (coll? v)
-                                   v
-
-                                   (text/page-ref? v)
-                                   [(text/page-ref-un-brackets! v)]
-
-                                   :else
-                                   nil)) (vals properties))
-        page-refs (remove string/blank? page-refs)]
+  [format properties db date-formatter]
+  (let [page-refs (get-page-ref-names-from-properties format properties)]
     (map (fn [page] (page-name->map page true db true date-formatter)) page-refs)))
     (map (fn [page] (page-name->map page true db true date-formatter)) page-refs)))
 
 
 (defn- with-page-block-refs
 (defn- with-page-block-refs
@@ -461,6 +478,7 @@
 (defn- with-pre-block-if-exists
 (defn- with-pre-block-if-exists
   [blocks body pre-block-properties encoded-content {:keys [supported-formats db date-formatter]}]
   [blocks body pre-block-properties encoded-content {:keys [supported-formats db date-formatter]}]
   (let [first-block (first blocks)
   (let [first-block (first blocks)
+        format (or (:block/format first-block) :markdown)
         first-block-start-pos (get-in first-block [:block/meta :start_pos])
         first-block-start-pos (get-in first-block [:block/meta :start_pos])
 
 
         ;; Add pre-block
         ;; Add pre-block
@@ -471,7 +489,7 @@
                    (let [content (utf8/substring encoded-content 0 first-block-start-pos)
                    (let [content (utf8/substring encoded-content 0 first-block-start-pos)
                          {:keys [properties properties-order]} pre-block-properties
                          {:keys [properties properties-order]} pre-block-properties
                          id (get-custom-id-or-new-id {:properties properties})
                          id (get-custom-id-or-new-id {:properties properties})
-                         property-refs (->> (get-page-refs-from-properties properties db date-formatter)
+                         property-refs (->> (get-page-refs-from-properties format properties db date-formatter)
                                             (map :block/original-name))
                                             (map :block/original-name))
                          block {:uuid id
                          block {:uuid id
                                 :content content
                                 :content content

+ 1 - 1
deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs

@@ -142,7 +142,7 @@
   ;; only increase over time as the docs graph rarely has deletions
   ;; only increase over time as the docs graph rarely has deletions
   (testing "Counts"
   (testing "Counts"
     (is (= 211 (count files)) "Correct file count")
     (is (= 211 (count files)) "Correct file count")
-    (is (= 40945 (count (d/datoms db :eavt))) "Correct datoms count")
+    (is (= 44212 (count (d/datoms db :eavt))) "Correct datoms count")
 
 
     (is (= 3600
     (is (= 3600
            (ffirst
            (ffirst

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

@@ -116,7 +116,7 @@
                       (remove-boundary-slashes)
                       (remove-boundary-slashes)
                       (path-normalize))]
                       (path-normalize))]
      (if replace-slash?
      (if replace-slash?
-       (string/replace page #"/" "%2A")
+       (string/replace page #"/" "%2F")
        page))))
        page))))
 
 
 (defn page-name-sanity-lc
 (defn page-name-sanity-lc

+ 7 - 7
deps/graph-parser/test/logseq/graph_parser/block_test.cljs

@@ -25,12 +25,12 @@
     [["file-path" "file:///home/x, y.pdf"]] {:file-path "file:///home/x, y.pdf"})
     [["file-path" "file:///home/x, y.pdf"]] {:file-path "file:///home/x, y.pdf"})
 
 
   (are [x y] (= (vec (:page-refs (gp-block/extract-properties :markdown x {}))) y)
   (are [x y] (= (vec (:page-refs (gp-block/extract-properties :markdown x {}))) y)
-    [["year" "1000"]] []
-    [["year" "\"1000\""]] []
-    [["foo" "[[bar]] test"]] ["bar" "test"]
-    [["foo" "[[bar]] test [[baz]]"]] ["bar" "test" "baz"]
-    [["foo" "[[bar]] test [[baz]] [[nested [[baz]]]]"]] ["bar" "test" "baz" "nested [[baz]]"]
-    [["foo" "#bar, #baz"]] ["bar" "baz"]
-    [["foo" "[[nested [[page]]]], test"]] ["nested [[page]]" "test"]))
+    [["year" "1000"]] ["year"]
+    [["year" "\"1000\""]] ["year"]
+    [["foo" "[[bar]] test"]] ["bar" "test" "foo"]
+    [["foo" "[[bar]] test [[baz]]"]] ["bar" "test" "baz" "foo"]
+    [["foo" "[[bar]] test [[baz]] [[nested [[baz]]]]"]] ["bar" "test" "baz" "nested [[baz]]" "foo"]
+    [["foo" "#bar, #baz"]] ["bar" "baz" "foo"]
+    [["foo" "[[nested [[page]]]], test"]] ["nested [[page]]" "test" "foo"]))
 
 
 #_(cljs.test/run-tests)
 #_(cljs.test/run-tests)

+ 23 - 5
docs/develop-logseq.md

@@ -24,12 +24,14 @@ yarn watch
 
 
 Then open the browser <http://localhost:3001>.
 Then open the browser <http://localhost:3001>.
 
 
-### Production
+### Production Build
 
 
 ```bash
 ```bash
 yarn release
 yarn release
 ```
 ```
 
 
+The released files will be at `resources/` directory.
+
 ## Desktop app development
 ## Desktop app development
 
 
 ### Development
 ### Development
@@ -37,23 +39,39 @@ yarn release
 1. Install npm packages for building the desktop app
 1. Install npm packages for building the desktop app
 
 
 ``` bash
 ``` bash
-yarn install && cd static && yarn install && cd ..
+yarn install
 ```
 ```
+
 2. Compile to JavaScript and open the dev app
 2. Compile to JavaScript and open the dev app
 
 
 ```bash
 ```bash
 yarn watch
 yarn watch
-# Wait until watch is finished building and then in a different shell
-# If you have opened desktop logseq, you should close it. Otherwise, the following command will fail.
+# Wait until watch reports `Build Completed.` for `:electron` and `:app`.
+# Then, run the following command in a different shell.
+# If you have opened desktop logseq, you should close it. Otherwise, this command will fail.
 yarn dev-electron-app
 yarn dev-electron-app
 ```
 ```
 
 
 Alternatively, run `bb dev:electron-start` to do this step with one command. To
 Alternatively, run `bb dev:electron-start` to do this step with one command. To
 download bb, see https://github.com/babashka/babashka#installation.
 download bb, see https://github.com/babashka/babashka#installation.
 
 
-### Production
+3. (Optional) Update dependencies if your are updating from an old branch
+
+```bash
+# pull new changes
+git pull
+
+cd static && yarn install && cd ..
+```
+
+Here `static/` is generated by `yarn watch` command.
+
+### Production Build
+
 Build a release:
 Build a release:
 
 
 ```bash
 ```bash
 yarn release-electron
 yarn release-electron
 ```
 ```
+
+The final released binaries or installers will be at `static/out/`.

+ 2 - 0
e2e-tests/basic.spec.ts

@@ -203,6 +203,8 @@ test('auto completion and auto pair', async ({ page, block }) => {
   await block.mustType('type (', { toBe: 'type ()' })
   await block.mustType('type (', { toBe: 'type ()' })
   await block.mustType('(', { toBe: 'type (())' })
   await block.mustType('(', { toBe: 'type (())' })
 
 
+  await block.escapeEditing() // escape any popup from `(())`
+
   // [[  #3251
   // [[  #3251
   await block.clickNext()
   await block.clickNext()
 
 

+ 1 - 1
e2e-tests/page-search.spec.ts

@@ -157,6 +157,6 @@ async function alias_test(page: Page, page_name: string, search_kws: string[]) {
   // TODO: search clicking (alias property)
   // TODO: search clicking (alias property)
 }
 }
 
 
-test('page diacritic alias', async ({ page }) => {
+test.skip('page diacritic alias', async ({ page }) => {
   await alias_test(page, "ü", ["ü", "ü", "Ü"])
   await alias_test(page, "ü", ["ü", "ü", "Ü"])
 })
 })

+ 4 - 4
ios/App/App.xcodeproj/project.pbxproj

@@ -542,7 +542,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.7.5;
+				MARKETING_VERSION = 0.7.6;
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -568,7 +568,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.7.5;
+				MARKETING_VERSION = 0.7.6;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -593,7 +593,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.7.5;
+				MARKETING_VERSION = 0.7.6;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
@@ -620,7 +620,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.7.5;
+				MARKETING_VERSION = 0.7.6;
 				MTL_FAST_MATH = YES;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 7 - 2
ios/App/App/FileContainer.swift

@@ -21,8 +21,13 @@ public class FileContainer: CAPPlugin, UIDocumentPickerDelegate {
 
 
     @objc func ensureDocuments(_ call: CAPPluginCall) {
     @objc func ensureDocuments(_ call: CAPPluginCall) {
         
         
-        validateDocuments(at: self.iCloudContainerUrl!)
-        validateDocuments(at: self.localContainerUrl!)
+        if self.iCloudContainerUrl != nil {
+            validateDocuments(at: self.iCloudContainerUrl!)
+        }
+        
+        if self.localContainerUrl != nil {
+            validateDocuments(at: self.localContainerUrl!)
+        }
         
         
         call.resolve(["path": [self.iCloudContainerUrl?.path as Any,
         call.resolve(["path": [self.iCloudContainerUrl?.path as Any,
                                self.localContainerUrl?.path as Any]])
                                self.localContainerUrl?.path as Any]])

+ 2 - 1
libs/src/LSPlugin.ts

@@ -196,6 +196,7 @@ export interface PageEntity {
   children?: Array<PageEntity>
   children?: Array<PageEntity>
   format?: 'markdown' | 'org'
   format?: 'markdown' | 'org'
   journalDay?: number
   journalDay?: number
+  updatedAt?: number
 }
 }
 
 
 export type BlockIdentity = BlockUUID | Pick<BlockEntity, 'uuid'>
 export type BlockIdentity = BlockUUID | Pick<BlockEntity, 'uuid'>
@@ -622,7 +623,7 @@ export interface IEditorProxy extends Record<string, any> {
 
 
   renamePage: (oldName: string, newName: string) => Promise<void>
   renamePage: (oldName: string, newName: string) => Promise<void>
 
 
-  getAllPages: (repo?: string) => Promise<any>
+  getAllPages: (repo?: string) => Promise<PageEntity[] | null>
 
 
   prependBlockInPage: (
   prependBlockInPage: (
     page: PageIdentity,
     page: PageIdentity,

+ 1 - 1
package.json

@@ -115,11 +115,11 @@
         "react-grid-layout": "0.16.6",
         "react-grid-layout": "0.16.6",
         "react-icon-base": "^2.1.2",
         "react-icon-base": "^2.1.2",
         "react-icons": "2.2.7",
         "react-icons": "2.2.7",
+        "react-intersection-observer": "^9.3.5",
         "react-resize-context": "3.0.0",
         "react-resize-context": "3.0.0",
         "react-textarea-autosize": "8.3.3",
         "react-textarea-autosize": "8.3.3",
         "react-tippy": "1.4.0",
         "react-tippy": "1.4.0",
         "react-transition-group": "4.3.0",
         "react-transition-group": "4.3.0",
-        "react-visibility-sensor": "^5.1.1",
         "reakit": "0.11.1",
         "reakit": "0.11.1",
         "remove-accents": "0.4.2",
         "remove-accents": "0.4.2",
         "send-intent": "3.0.11",
         "send-intent": "3.0.11",

+ 1 - 1
resources/package.json

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

+ 46 - 41
src/main/frontend/commands.cljs

@@ -22,11 +22,8 @@
 
 
 ;; TODO: move to frontend.handler.editor.commands
 ;; TODO: move to frontend.handler.editor.commands
 
 
-(defonce *show-commands (atom false))
-(defonce *slash-caret-pos (atom nil))
-(defonce *show-block-commands (atom false))
 (defonce angle-bracket "<")
 (defonce angle-bracket "<")
-(defonce *angle-bracket-caret-pos (atom nil))
+(defonce colon ":")
 (defonce *current-command (atom nil))
 (defonce *current-command (atom nil))
 
 
 (def query-doc
 (def query-doc
@@ -302,16 +299,20 @@
 
 
 (defonce *matched-block-commands (atom (block-commands-map)))
 (defonce *matched-block-commands (atom (block-commands-map)))
 
 
-(defn restore-state
-  [restore-slash-caret-pos?]
-  (when restore-slash-caret-pos?
-    (reset! *slash-caret-pos nil))
-  (reset! *show-commands false)
-  (reset! *angle-bracket-caret-pos nil)
-  (reset! *show-block-commands false)
-  (reset! *matched-commands @*initial-commands)
+(defn reinit-matched-commands!
+  []
+  (reset! *matched-commands @*initial-commands))
+
+(defn reinit-matched-block-commands!
+  []
   (reset! *matched-block-commands (block-commands-map)))
   (reset! *matched-block-commands (block-commands-map)))
 
 
+(defn restore-state
+  []
+  (state/clear-editor-action!)
+  (reinit-matched-commands!)
+  (reinit-matched-block-commands!))
+
 (defn insert!
 (defn insert!
   [id value
   [id value
    {:keys [last-pattern postfix-fn backward-pos forward-pos end-pattern backward-truncate-number]
    {:keys [last-pattern postfix-fn backward-pos forward-pos end-pattern backward-truncate-number]
@@ -327,19 +328,22 @@
                            (+ current-pos i)))
                            (+ current-pos i)))
                        current-pos)
                        current-pos)
           orig-prefix (subs edit-content 0 current-pos)
           orig-prefix (subs edit-content 0 current-pos)
-          space? (when (and last-pattern orig-prefix)
-                   (let [s (when-let [last-index (string/last-index-of orig-prefix last-pattern)]
-                             (gp-util/safe-subs orig-prefix 0 last-index))]
-                     (not
-                      (or
-                       (and s
-                            (string/ends-with? s "(")
-                            (or (string/starts-with? last-pattern "((")
-                                (string/starts-with? last-pattern "[[")))
-                       (and s (string/starts-with? s "{{embed"))))))
-          space? (if (and space? (string/starts-with? last-pattern "#[["))
-                   false
-                   space?)
+          space? (let [space? (when (and last-pattern orig-prefix)
+                                (let [s (when-let [last-index (string/last-index-of orig-prefix last-pattern)]
+                                          (gp-util/safe-subs orig-prefix 0 last-index))]
+                                  (not
+                                   (or
+                                    (and s
+                                         (string/ends-with? s "(")
+                                         (or (string/starts-with? last-pattern "((")
+                                             (string/starts-with? last-pattern "[[")))
+                                    (and s (string/starts-with? s "{{embed"))
+                                    (and last-pattern
+                                         (or (string/ends-with? last-pattern "::")
+                                             (string/starts-with? last-pattern "::")))))))]
+                   (if (and space? (string/starts-with? last-pattern "#[["))
+                     false
+                     space?))
           prefix (cond
           prefix (cond
                    (and backward-truncate-number (integer? backward-truncate-number))
                    (and backward-truncate-number (integer? backward-truncate-number))
                    (str (gp-util/safe-subs orig-prefix 0 (- (count orig-prefix) backward-truncate-number))
                    (str (gp-util/safe-subs orig-prefix 0 (- (count orig-prefix) backward-truncate-number))
@@ -366,12 +370,13 @@
                       (str prefix postfix))
                       (str prefix postfix))
           new-pos (- (count prefix)
           new-pos (- (count prefix)
                      (or backward-pos 0))]
                      (or backward-pos 0))]
-      (state/set-block-content-and-last-pos! id new-value new-pos)
-      (cursor/move-cursor-to input
-                             (if (and (or backward-pos forward-pos)
-                                      (not= end-pattern "]]"))
-                               new-pos
-                               (inc new-pos))))))
+      (when-not (string/blank? new-value)
+        (state/set-block-content-and-last-pos! id new-value new-pos)
+        (cursor/move-cursor-to input
+                               (if (and (or backward-pos forward-pos)
+                                        (not= end-pattern "]]"))
+                                 new-pos
+                                 (inc new-pos)))))))
 
 
 (defn simple-insert!
 (defn simple-insert!
   [id value
   [id value
@@ -464,13 +469,13 @@
     (let [type (:type option)
     (let [type (:type option)
           input (gdom/getElement input-id)
           input (gdom/getElement input-id)
           beginning-of-line? (or (cursor/beginning-of-line? input)
           beginning-of-line? (or (cursor/beginning-of-line? input)
-                                 (= 1 (:pos @*angle-bracket-caret-pos)))
+                                 (= 1 (:pos (:pos (state/get-editor-action-data)))))
           value (if (and (contains? #{"block" "properties"} type)
           value (if (and (contains? #{"block" "properties"} type)
                          (not beginning-of-line?))
                          (not beginning-of-line?))
                   (str "\n" value)
                   (str "\n" value)
                   value)]
                   value)]
       (insert! input-id value option)
       (insert! input-id value option)
-      (reset! *show-commands false))))
+      (state/clear-editor-action!))))
 
 
 (defmethod handle-step :editor/cursor-back [[_ n]]
 (defmethod handle-step :editor/cursor-back [[_ n]]
   (when-let [input-id (state/get-edit-input-id)]
   (when-let [input-id (state/get-edit-input-id)]
@@ -537,7 +542,7 @@
   (when-let [input-id (state/get-edit-input-id)]
   (when-let [input-id (state/get-edit-input-id)]
     (when-let [current-input (gdom/getElement input-id)]
     (when-let [current-input (gdom/getElement input-id)]
       (let [edit-content (gobj/get current-input "value")
       (let [edit-content (gobj/get current-input "value")
-            slash-pos (:pos @*slash-caret-pos)
+            slash-pos (:pos (:pos (state/get-editor-action-data)))
             [re-pattern new-line-re-pattern] (if (= :org format)
             [re-pattern new-line-re-pattern] (if (= :org format)
                                                [#"\*+\s" #"\n\*+\s"]
                                                [#"\*+\s" #"\n\*+\s"]
                                                [#"#+\s" #"\n#+\s"])
                                                [#"#+\s" #"\n#+\s"])
@@ -595,22 +600,22 @@
         (state/set-edit-content! input-id new-value)))))
         (state/set-edit-content! input-id new-value)))))
 
 
 (defmethod handle-step :editor/search-page [[_]]
 (defmethod handle-step :editor/search-page [[_]]
-  (state/set-editor-show-page-search! true))
+  (state/set-editor-action! :page-search))
 
 
 (defmethod handle-step :editor/search-page-hashtag [[_]]
 (defmethod handle-step :editor/search-page-hashtag [[_]]
-  (state/set-editor-show-page-search-hashtag! true))
+  (state/set-editor-action! :page-search-hashtag))
 
 
 (defmethod handle-step :editor/search-block [[_ _type]]
 (defmethod handle-step :editor/search-block [[_ _type]]
-  (state/set-editor-show-block-search! true))
+  (state/set-editor-action! :block-search))
 
 
 (defmethod handle-step :editor/search-template [[_]]
 (defmethod handle-step :editor/search-template [[_]]
-  (state/set-editor-show-template-search! true))
+  (state/set-editor-action! :template-search))
 
 
 (defmethod handle-step :editor/show-input [[_ option]]
 (defmethod handle-step :editor/show-input [[_ option]]
   (state/set-editor-show-input! option))
   (state/set-editor-show-input! option))
 
 
 (defmethod handle-step :editor/show-zotero [[_]]
 (defmethod handle-step :editor/show-zotero [[_]]
-  (state/set-editor-show-zotero! true))
+  (state/set-editor-action! :zotero))
 
 
 (defn insert-youtube-timestamp
 (defn insert-youtube-timestamp
   []
   []
@@ -632,8 +637,8 @@
          (string/blank? value)))
          (string/blank? value)))
     (do
     (do
       (notification/show! [:div "Please add some content first."] :warning)
       (notification/show! [:div "Please add some content first."] :warning)
-      (restore-state false))
-    (state/set-editor-show-date-picker! true)))
+      (restore-state))
+    (state/set-editor-action! :datepicker)))
 
 
 (defmethod handle-step :editor/click-hidden-file-input [[_ _input-id]]
 (defmethod handle-step :editor/click-hidden-file-input [[_ _input-id]]
   (when-let [input-file (gdom/getElement "upload-file")]
   (when-let [input-file (gdom/getElement "upload-file")]

+ 16 - 15
src/main/frontend/components/block.cljs

@@ -421,7 +421,9 @@
   [config page-name-in-block page-name redirect-page-name page-entity contents-page? children html-export? label]
   [config page-name-in-block page-name redirect-page-name page-entity contents-page? children html-export? label]
   (let [tag? (:tag? config)]
   (let [tag? (:tag? config)]
     [:a
     [:a
-     {:class (if tag? "tag" "page-ref")
+     {:class (cond-> (if tag? "tag" "page-ref")
+               (:property? config)
+               (str " page-property-key"))
       :data-ref page-name
       :data-ref page-name
       :on-mouse-down
       :on-mouse-down
       (fn [e]
       (fn [e]
@@ -1065,7 +1067,7 @@
 ;;;; Macro component render functions
 ;;;; Macro component render functions
 (defn- macro-query-cp
 (defn- macro-query-cp
   [config arguments]
   [config arguments]
-  [:div.dsl-query
+  [:div.dsl-query.overflow-x-hidden.pr-3.sm:pr-0
    (let [query (->> (string/join ", " arguments)
    (let [query (->> (string/join ", " arguments)
                     (string/trim))]
                     (string/trim))]
      (when-not (string/blank? query)
      (when-not (string/blank? query)
@@ -1805,7 +1807,7 @@
   [config block k v]
   [config block k v]
   (let [date (and (= k :date) (date/get-locale-string (str v)))]
   (let [date (and (= k :date) (date/get-locale-string (str v)))]
     [:div
     [:div
-     [:span.page-property-key.font-medium (name k)]
+     (page-cp (assoc config :property? true) {:block/name (subs (str k) 1)})
      [:span.mr-1 ":"]
      [:span.mr-1 ":"]
      (cond
      (cond
        (int? v)
        (int? v)
@@ -1894,12 +1896,12 @@
                            (do
                            (do
                              (reset! show? false)
                              (reset! show? false)
                              (reset! commands/*current-command nil)
                              (reset! commands/*current-command nil)
-                             (state/set-editor-show-date-picker! false)
+                             (state/clear-editor-action!)
                              (state/set-timestamp-block! nil))
                              (state/set-timestamp-block! nil))
                            (do
                            (do
                              (reset! show? true)
                              (reset! show? true)
                              (reset! commands/*current-command typ)
                              (reset! commands/*current-command typ)
-                             (state/set-editor-show-date-picker! true)
+                             (state/set-editor-action! :datepicker)
                              (state/set-timestamp-block! {:block block
                              (state/set-timestamp-block! {:block block
                                                           :typ typ
                                                           :typ typ
                                                           :show? show?}))))}
                                                           :show? show?}))))}
@@ -1923,7 +1925,8 @@
    (util/details-or-summary? target)
    (util/details-or-summary? target)
    (and (util/sup? target)
    (and (util/sup? target)
         (dom/has-class? target "fn"))
         (dom/has-class? target "fn"))
-   (dom/has-class? target "image-resize")))
+   (dom/has-class? target "image-resize")
+   (dom/closest target "a")))
 
 
 (defn- block-content-on-mouse-down
 (defn- block-content-on-mouse-down
   [e block block-id _content edit-input-id]
   [e block block-id _content edit-input-id]
@@ -2057,7 +2060,7 @@
 
 
      [:<>
      [:<>
       [:div.flex.flex-row.justify-between.block-content-inner
       [:div.flex.flex-row.justify-between.block-content-inner
-       [:div.flex-1
+       [:div.flex-1.w-full
         (cond
         (cond
           (seq title)
           (seq title)
           (build-block-title config block)
           (build-block-title config block)
@@ -2168,7 +2171,10 @@
             (ui/block-error "Block Render Error:"
             (ui/block-error "Block Render Error:"
                             {:content (:block/content block)
                             {:content (:block/content block)
                              :section-attrs
                              :section-attrs
-                             {:on-click #(state/set-editing! edit-input-id (:block/content block) block "")}})
+                             {:on-click #(do
+                                           (editor-handler/clear-selection!)
+                                           (editor-handler/unhighlight-blocks!)
+                                           (state/set-editing! edit-input-id (:block/content block) block ""))}})
             (block-content config block edit-input-id block-id slide?))]
             (block-content config block edit-input-id block-id slide?))]
           [:div.flex.flex-row.items-center
           [:div.flex.flex-row.items-center
            (when (and (:embed? config)
            (when (and (:embed? config)
@@ -2551,10 +2557,7 @@
         custom-query? (boolean (:custom-query? config))]
         custom-query? (boolean (:custom-query? config))]
     (if (and ref? (not custom-query?) (not (:ref-query-child? config)))
     (if (and ref? (not custom-query?) (not (:ref-query-child? config)))
       (ui/lazy-visible
       (ui/lazy-visible
-       (fn []
-         (block-container-inner state repo config block))
-       nil
-       {})
+       (fn [] (block-container-inner state repo config block)))
       (block-container-inner state repo config block))))
       (block-container-inner state repo config block))))
 
 
 (defn divide-lists
 (defn divide-lists
@@ -2892,9 +2895,7 @@
   (ui/catch-error
   (ui/catch-error
    (ui/block-error "Query Error:" {:content (:query q)})
    (ui/block-error "Query Error:" {:content (:query q)})
    (ui/lazy-visible
    (ui/lazy-visible
-    (fn [] (custom-query* config q))
-    nil
-    {})))
+    (fn [] (custom-query* config q)))))
 (defn admonition
 (defn admonition
   [config type result]
   [config type result]
   (when-let [icon (case (string/lower-case (name type))
   (when-let [icon (case (string/lower-case (name type))

+ 6 - 1
src/main/frontend/components/block.css

@@ -188,7 +188,7 @@
 .block-left-menu {
 .block-left-menu {
     background-color: var(--ls-secondary-background-color);
     background-color: var(--ls-secondary-background-color);
     background: linear-gradient(90deg, var(--ls-primary-background-color) 0%, var(--ls-secondary-background-color) 100%);
     background: linear-gradient(90deg, var(--ls-primary-background-color) 0%, var(--ls-secondary-background-color) 100%);
-    
+
     .commands-button {
     .commands-button {
         overflow: hidden;
         overflow: hidden;
         max-width: 40px;
         max-width: 40px;
@@ -613,9 +613,14 @@ a.cloze-revealed {
 }
 }
 
 
 .page-property-key {
 .page-property-key {
+  @apply font-medium;
   color: var(--ls-secondary-text-color);
   color: var(--ls-secondary-text-color);
 }
 }
 
 
+.page-property-key:hover {
+    background-color: var(--ls-selection-background-color);
+}
+
 .block-parents a {
 .block-parents a {
   color: var(--ls-primary-text-color);
   color: var(--ls-primary-text-color);
 }
 }

+ 3 - 4
src/main/frontend/components/datetime.cljs

@@ -105,8 +105,7 @@
     (when show?
     (when show?
       (reset! show? false)))
       (reset! show? false)))
   (clear-timestamp!)
   (clear-timestamp!)
-  (state/set-editor-show-date-picker! false)
-  (commands/restore-state false))
+  (commands/restore-state))
 
 
 (rum/defc time-repeater < rum/reactive
 (rum/defc time-repeater < rum/reactive
   (mixins/event-mixin
   (mixins/event-mixin
@@ -147,7 +146,7 @@
                                    (contains? #{"deadline" "scheduled"}
                                    (contains? #{"deadline" "scheduled"}
                                               (string/lower-case current-command)))
                                               (string/lower-case current-command)))
         date (state/sub :date-picker/date)]
         date (state/sub :date-picker/date)]
-    (when (state/sub :editor/show-date-picker?)
+    (when (= :datepicker (state/sub :editor/action))
       [:div#date-time-picker.flex.flex-row {:on-click (fn [e] (util/stop e))
       [:div#date-time-picker.flex.flex-row {:on-click (fn [e] (util/stop e))
                                             :on-mouse-down (fn [e] (.stopPropagation e))}
                                             :on-mouse-down (fn [e] (.stopPropagation e))}
        (ui/datepicker
        (ui/datepicker
@@ -164,7 +163,7 @@
                                                (util/format "[[%s]]" journal)
                                                (util/format "[[%s]]" journal)
                                                format
                                                format
                                                nil)
                                                nil)
-               (state/set-editor-show-date-picker! false)
+               (state/clear-editor-action!)
                (reset! commands/*current-command nil))))})
                (reset! commands/*current-command nil))))})
        (when deadline-or-schedule?
        (when deadline-or-schedule?
          (time-repeater))])))
          (time-repeater))])))

+ 243 - 192
src/main/frontend/components/editor.cljs

@@ -2,11 +2,12 @@
   (:require [clojure.string :as string]
   (:require [clojure.string :as string]
             [goog.string :as gstring]
             [goog.string :as gstring]
             [frontend.commands :as commands
             [frontend.commands :as commands
-             :refer [*angle-bracket-caret-pos *first-command-group *matched-block-commands *matched-commands *show-block-commands *show-commands *slash-caret-pos]]
+             :refer [*first-command-group *matched-block-commands *matched-commands]]
             [frontend.components.block :as block]
             [frontend.components.block :as block]
             [frontend.components.datetime :as datetime-comp]
             [frontend.components.datetime :as datetime-comp]
             [frontend.components.search :as search]
             [frontend.components.search :as search]
             [frontend.components.svg :as svg]
             [frontend.components.svg :as svg]
+            [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.db.model :as db-model]
             [frontend.db.model :as db-model]
             [frontend.extensions.zotero :as zotero]
             [frontend.extensions.zotero :as zotero]
@@ -29,8 +30,8 @@
 
 
 (rum/defc commands < rum/reactive
 (rum/defc commands < rum/reactive
   [id format]
   [id format]
-  (let [matched (util/react *matched-commands)]
-    (when (util/react *show-commands)
+  (when (= :commands (state/sub :editor/action))
+    (let [matched (util/react *matched-commands)]
       (ui/auto-complete
       (ui/auto-complete
        matched
        matched
        {:get-group-name
        {:get-group-name
@@ -82,7 +83,7 @@
 
 
 (rum/defc block-commands < rum/reactive
 (rum/defc block-commands < rum/reactive
   [id format]
   [id format]
-  (when (util/react *show-block-commands)
+  (when (= :block-commands (state/get-editor-action))
     (let [matched (util/react *matched-block-commands)]
     (let [matched (util/react *matched-block-commands)]
       (ui/auto-complete
       (ui/auto-complete
        (map first matched)
        (map first matched)
@@ -99,59 +100,63 @@
   {:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)}
   {:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)}
   "Embedded page searching popup"
   "Embedded page searching popup"
   [id format]
   [id format]
-  (when (state/sub :editor/show-page-search?)
-    (let [pos (state/get-editor-last-pos)
-          input (gdom/getElement id)]
-      (when input
-        (let [current-pos (cursor/pos input)
-              edit-content (or (state/sub [:editor/content id]) "")
-              sidebar? (in-sidebar? input)
-              q (or
-                 @editor-handler/*selected-text
-                 (when (state/sub :editor/show-page-search-hashtag?)
-                   (gp-util/safe-subs edit-content pos current-pos))
-                 (when (> (count edit-content) current-pos)
-                   (gp-util/safe-subs edit-content pos current-pos))
-                 "")
-              matched-pages (when-not (string/blank? q)
-                              (editor-handler/get-matched-pages q))
-              matched-pages (cond
-                              (contains? (set (map util/page-name-sanity-lc matched-pages)) (util/page-name-sanity-lc (string/trim q)))  ;; if there's a page name fully matched
-                              matched-pages
-
-                              (string/blank? q)
-                              nil
-
-                              (empty? matched-pages)
-                              (cons (str "New page: " q) matched-pages)
-
-                              ;; reorder, shortest and starts-with first.
-                              :else
-                              (let [matched-pages (remove nil? matched-pages)
-                                    matched-pages (sort-by
-                                                   (fn [m]
-                                                     [(not (gstring/caseInsensitiveStartsWith m q)) (count m)])
-                                                   matched-pages)]
-                                (if (gstring/caseInsensitiveStartsWith (first matched-pages) q)
-                                  (cons (first matched-pages)
-                                        (cons  (str "New page: " q) (rest matched-pages)))
-                                  (cons (str "New page: " q) matched-pages))))]
-          (ui/auto-complete
-           matched-pages
-           {:on-chosen   (page-handler/on-chosen-handler input id q pos format)
-            :on-enter    #(page-handler/page-not-exists-handler input id q current-pos)
-            :item-render (fn [page-name chosen?]
-                           [:div.preview-trigger-wrapper
-                            (block/page-preview-trigger
-                             {:children        [:div (search/highlight-exact-query page-name q)]
-                              :open?           chosen?
-                              :manual?         true
-                              :fixed-position? true
-                              :tippy-distance  24
-                              :tippy-position  (if sidebar? "left" "right")}
-                             page-name)])
-            :empty-placeholder [:div.text-gray-500.text-sm.px-4.py-2 "Search for a page"]
-            :class       "black"}))))))
+  (let [action (state/sub :editor/action)]
+    (when (contains? #{:page-search :page-search-hashtag} action)
+      (let [pos (state/get-editor-last-pos)
+            input (gdom/getElement id)]
+        (when input
+          (let [current-pos (cursor/pos input)
+                edit-content (or (state/sub [:editor/content id]) "")
+                sidebar? (in-sidebar? input)
+                q (or
+                   @editor-handler/*selected-text
+                   (when (= action :page-search-hashtag)
+                     (gp-util/safe-subs edit-content pos current-pos))
+                   (when (> (count edit-content) current-pos)
+                     (gp-util/safe-subs edit-content pos current-pos))
+                   "")
+                matched-pages (when-not (string/blank? q)
+                                (editor-handler/get-matched-pages q))
+                matched-pages (cond
+                                (contains? (set (map util/page-name-sanity-lc matched-pages))
+                                           (util/page-name-sanity-lc (string/trim q)))  ;; if there's a page name fully matched
+                                (sort-by (fn [m]
+                                           [(count m) m])
+                                         matched-pages)
+
+                                (string/blank? q)
+                                nil
+
+                                (empty? matched-pages)
+                                (cons (str (t :new-page) ": " q) matched-pages)
+
+                               ;; reorder, shortest and starts-with first.
+                                :else
+                                (let [matched-pages (remove nil? matched-pages)
+                                      matched-pages (sort-by
+                                                     (fn [m]
+                                                       [(not (gstring/caseInsensitiveStartsWith m q)) (count m) m])
+                                                     matched-pages)]
+                                  (if (gstring/caseInsensitiveStartsWith (first matched-pages) q)
+                                    (cons (first matched-pages)
+                                          (cons  (str (t :new-page) ": " q) (rest matched-pages)))
+                                    (cons (str (t :new-page) ": " q) matched-pages))))]
+            (ui/auto-complete
+             matched-pages
+             {:on-chosen   (page-handler/on-chosen-handler input id q pos format)
+              :on-enter    #(page-handler/page-not-exists-handler input id q current-pos)
+              :item-render (fn [page-name chosen?]
+                             [:div.preview-trigger-wrapper
+                              (block/page-preview-trigger
+                               {:children        [:div (search/highlight-exact-query page-name q)]
+                                :open?           chosen?
+                                :manual?         true
+                                :fixed-position? true
+                                :tippy-distance  24
+                                :tippy-position  (if sidebar? "left" "right")}
+                               page-name)])
+              :empty-placeholder [:div.text-gray-500.text-sm.px-4.py-2 "Search for a page"]
+              :class       "black"})))))))
 
 
 (rum/defcs block-search-auto-complete < rum/reactive
 (rum/defcs block-search-auto-complete < rum/reactive
   {:init (fn [state]
   {:init (fn [state]
@@ -164,24 +169,25 @@
                      (reset! result matched-blocks)))
                      (reset! result matched-blocks)))
                  state)}
                  state)}
   [state _edit-block input id q format]
   [state _edit-block input id q format]
-  (let [result (rum/react (get state ::result))
+  (let [result (->> (rum/react (get state ::result))
+                    (remove (fn [b] (string/blank? (:block/content (db-model/query-block-by-uuid (:block/uuid b)))))))
         chosen-handler (editor-handler/block-on-chosen-handler input id q format)
         chosen-handler (editor-handler/block-on-chosen-handler input id q format)
         non-exist-block-handler (editor-handler/block-non-exist-handler input)]
         non-exist-block-handler (editor-handler/block-non-exist-handler input)]
-    (when result
-      (ui/auto-complete
-       result
-       {:on-chosen   chosen-handler
-        :on-enter    non-exist-block-handler
-        :empty-placeholder   [:div.text-gray-500.pl-4.pr-4 "Search for a block"]
-        :item-render (fn [{:block/keys [page uuid]}]  ;; content returned from search engine is normalized
-                       (let [page (or (:block/original-name page)
-                                      (:block/name page))
-                             repo (state/sub :git/current-repo)
-                             format (db/get-page-format page)
-                             block (db-model/query-block-by-uuid uuid)
-                             content (:block/content block)]
-                         [:.py-2 (search/block-search-result-item repo uuid format content q :block)]))
-        :class       "black"}))))
+    (ui/auto-complete
+     result
+     {:on-chosen   chosen-handler
+      :on-enter    non-exist-block-handler
+      :empty-placeholder   [:div.text-gray-500.pl-4.pr-4 "Search for a block"]
+      :item-render (fn [{:block/keys [page uuid]}]  ;; content returned from search engine is normalized
+                     (let [page (or (:block/original-name page)
+                                    (:block/name page))
+                           repo (state/sub :git/current-repo)
+                           format (db/get-page-format page)
+                           block (db-model/query-block-by-uuid uuid)
+                           content (:block/content block)]
+                       (when-not (string/blank? content)
+                         [:.py-2 (search/block-search-result-item repo uuid format content q :block)])))
+      :class       "black"})))
 
 
 (rum/defcs block-search < rum/reactive
 (rum/defcs block-search < rum/reactive
   {:will-unmount (fn [state]
   {:will-unmount (fn [state]
@@ -189,7 +195,7 @@
                    (state/clear-search-result!)
                    (state/clear-search-result!)
                    state)}
                    state)}
   [state id _format]
   [state id _format]
-  (when (state/sub :editor/show-block-search?)
+  (when (= :block-search (state/sub :editor/action))
     (let [pos (state/get-editor-last-pos)
     (let [pos (state/get-editor-last-pos)
           input (gdom/getElement id)
           input (gdom/getElement id)
           [id format] (:rum/args state)
           [id format] (:rum/args state)
@@ -206,27 +212,73 @@
 (rum/defc template-search < rum/reactive
 (rum/defc template-search < rum/reactive
   {:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)}
   {:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)}
   [id _format]
   [id _format]
-  (when (state/sub :editor/show-template-search?)
-    (let [pos (state/get-editor-last-pos)
-          input (gdom/getElement id)]
-      (when input
-        (let [current-pos (cursor/pos input)
-              edit-content (state/sub [:editor/content id])
-              q (or
-                 (when (>= (count edit-content) current-pos)
-                   (subs edit-content pos current-pos))
-                 "")
-              matched-templates (editor-handler/get-matched-templates q)
-              non-exist-handler (fn [_state]
-                                  (state/set-editor-show-template-search! false))]
-          (ui/auto-complete
-           matched-templates
-           {:on-chosen   (editor-handler/template-on-chosen-handler id)
-            :on-enter    non-exist-handler
-            :empty-placeholder [:div.text-gray-500.px-4.py-2.text-sm "Search for a template"]
-            :item-render (fn [[template _block-db-id]]
-                           template)
-            :class       "black"}))))))
+  (let [pos (state/get-editor-last-pos)
+        input (gdom/getElement id)]
+    (when input
+      (let [current-pos (cursor/pos input)
+            edit-content (state/sub [:editor/content id])
+            q (or
+               (when (>= (count edit-content) current-pos)
+                 (subs edit-content pos current-pos))
+               "")
+            matched-templates (editor-handler/get-matched-templates q)
+            non-exist-handler (fn [_state]
+                                (state/clear-editor-action!))]
+        (ui/auto-complete
+         matched-templates
+         {:on-chosen   (editor-handler/template-on-chosen-handler id)
+          :on-enter    non-exist-handler
+          :empty-placeholder [:div.text-gray-500.px-4.py-2.text-sm "Search for a template"]
+          :item-render (fn [[template _block-db-id]]
+                         template)
+          :class       "black"})))))
+
+(rum/defc property-search < rum/reactive
+  {:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)}
+  [id]
+  (let [input (gdom/getElement id)]
+    (when input
+      (let [q (or (:searching-property (editor-handler/get-searching-property input))
+                  "")
+            matched-properties (editor-handler/get-matched-properties q)
+            q-property (string/replace (string/lower-case q) #"\s+" "-")
+            non-exist-handler (fn [_state]
+                                ((editor-handler/property-on-chosen-handler id q-property) nil))]
+        (ui/auto-complete
+         matched-properties
+         {:on-chosen (editor-handler/property-on-chosen-handler id q-property)
+          :on-enter non-exist-handler
+          :empty-placeholder [:div.px-4.py-2.text-sm (str "Create a new property: " q-property)]
+          :header [:div.px-4.py-2.text-sm.font-medium "Matched properties: "]
+          :item-render (fn [property] property)
+          :class       "black"})))))
+
+(rum/defc property-value-search < rum/reactive
+  {:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)}
+  [id]
+  (let [property (:property (state/get-editor-action-data))
+        input (gdom/getElement id)]
+    (when (and input
+               (not (string/blank? property)))
+      (let [current-pos (cursor/pos input)
+            edit-content (state/sub [:editor/content id])
+            start-idx (string/last-index-of (subs edit-content 0 current-pos) "::")
+            q (or
+               (when (>= current-pos (+ start-idx 2))
+                 (subs edit-content (+ start-idx 2) current-pos))
+               "")
+            q (string/triml q)
+            matched-values (editor-handler/get-matched-property-values property q)
+            non-exist-handler (fn [_state]
+                                ((editor-handler/property-value-on-chosen-handler id q) nil))]
+        (ui/auto-complete
+         matched-values
+         {:on-chosen (editor-handler/property-value-on-chosen-handler id q)
+          :on-enter non-exist-handler
+          :empty-placeholder [:div.px-4.py-2.text-sm (str "Create a new property value: " q)]
+          :header [:div.px-4.py-2.text-sm.font-medium "Matched property values: "]
+          :item-render (fn [property-value] property-value)
+          :class       "black"})))))
 
 
 (rum/defcs input < rum/reactive
 (rum/defcs input < rum/reactive
   (rum/local {} ::input-value)
   (rum/local {} ::input-value)
@@ -237,44 +289,44 @@
       {;; enter
       {;; enter
        13 (fn [state e]
        13 (fn [state e]
             (let [input-value (get state ::input-value)
             (let [input-value (get state ::input-value)
-                  input-option (get @state/state :editor/show-input)]
+                  input-option (:options (state/get-editor-show-input))]
               (when (seq @input-value)
               (when (seq @input-value)
                 ;; no new line input
                 ;; no new line input
                 (util/stop e)
                 (util/stop e)
                 (let [[_id on-submit] (:rum/args state)
                 (let [[_id on-submit] (:rum/args state)
-                      {:keys [pos]} @*slash-caret-pos
                       command (:command (first input-option))]
                       command (:command (first input-option))]
-                  (on-submit command @input-value pos))
+                  (on-submit command @input-value))
                 (reset! input-value nil))))})))
                 (reset! input-value nil))))})))
   [state _id on-submit]
   [state _id on-submit]
-  (when-let [input-option (state/sub :editor/show-input)]
-    (let [{:keys [pos]} (util/react *slash-caret-pos)
-          input-value (get state ::input-value)]
-      (when (seq input-option)
-        (let [command (:command (first input-option))]
-          [:div.p-2.rounded-md.shadow-lg
-           (for [{:keys [id placeholder type autoFocus] :as input-item} input-option]
-             [:div.my-3 {:key id}
-              [:input.form-input.block.w-full.pl-2.sm:text-sm.sm:leading-5
-               (merge
-                (cond->
-                 {:key           (str "modal-input-" (name id))
-                  :id            (str "modal-input-" (name id))
-                  :type          (or type "text")
-                  :on-change     (fn [e]
-                                   (swap! input-value assoc id (util/evalue e)))
-                  :auto-complete (if (util/chrome?) "chrome-off" "off")}
-                  placeholder
-                  (assoc :placeholder placeholder)
-                  autoFocus
-                  (assoc :auto-focus true))
-                (dissoc input-item :id))]])
-           (ui/button
-            "Submit"
-            :on-click
-            (fn [e]
-              (util/stop e)
-              (on-submit command @input-value pos)))])))))
+  (when (= :input (state/sub :editor/action))
+    (when-let [action-data (state/sub :editor/action-data)]
+      (let [{:keys [pos options]} action-data
+            input-value (get state ::input-value)]
+        (when (seq options)
+          (let [command (:command (first options))]
+            [:div.p-2.rounded-md.shadow-lg
+             (for [{:keys [id placeholder type autoFocus] :as input-item} options]
+               [:div.my-3 {:key id}
+                [:input.form-input.block.w-full.pl-2.sm:text-sm.sm:leading-5
+                 (merge
+                  (cond->
+                    {:key           (str "modal-input-" (name id))
+                     :id            (str "modal-input-" (name id))
+                     :type          (or type "text")
+                     :on-change     (fn [e]
+                                      (swap! input-value assoc id (util/evalue e)))
+                     :auto-complete (if (util/chrome?) "chrome-off" "off")}
+                    placeholder
+                    (assoc :placeholder placeholder)
+                    autoFocus
+                    (assoc :auto-focus true))
+                  (dissoc input-item :id))]])
+             (ui/button
+               "Submit"
+               :on-click
+               (fn [e]
+                 (util/stop e)
+                 (on-submit command @input-value pos)))]))))))
 
 
 (rum/defc absolute-modal < rum/static
 (rum/defc absolute-modal < rum/static
   [cp set-default-width? {:keys [top left rect]}]
   [cp set-default-width? {:keys [top left rect]}]
@@ -305,37 +357,37 @@
         to-max-height (if y-overflow-vh? max-height to-max-height)
         to-max-height (if y-overflow-vh? max-height to-max-height)
         pos-rect (when (and (seq rect) editing-key)
         pos-rect (when (and (seq rect) editing-key)
                    (:rect (cursor/get-caret-pos (state/get-input))))
                    (:rect (cursor/get-caret-pos (state/get-input))))
-        y-diff (when pos-rect (- (:height pos-rect) (:height rect)))]
+        y-diff (when pos-rect (- (:height pos-rect) (:height rect)))
+        style (merge
+               {:top        (+ top offset-top (if (int? y-diff) y-diff 0))
+                :max-height to-max-height
+                :max-width 700
+                ;; TODO: auto responsive fixed size
+                :width "fit-content"
+                :z-index    11}
+               (when set-default-width?
+                 {:width max-width})
+               (let [^js/HTMLElement editor
+                     (js/document.querySelector ".editor-wrapper")]
+                 (if (<= (.-clientWidth editor) (+ left (if set-default-width? max-width 500)))
+                   {:right 0}
+                   {:left (if (or (nil? y-diff) (and y-diff (= y-diff 0))) left 0)})))]
     [:div.absolute.rounded-md.shadow-lg.absolute-modal
     [:div.absolute.rounded-md.shadow-lg.absolute-modal
      {:ref *el
      {:ref *el
       :class (if y-overflow-vh? "is-overflow-vh-y" "")
       :class (if y-overflow-vh? "is-overflow-vh-y" "")
       :on-mouse-down (fn [e]
       :on-mouse-down (fn [e]
                        (.stopPropagation e))
                        (.stopPropagation e))
-      :style (merge
-              {:top        (+ top offset-top (if (int? y-diff) y-diff 0))
-               :max-height to-max-height
-               :max-width 700
-               ;; TODO: auto responsive fixed size
-               :width "fit-content"
-               :z-index    11}
-              (when set-default-width?
-                {:width max-width})
-              (let [^js/HTMLElement editor
-                    (js/document.querySelector ".editor-wrapper")]
-                (if (<= (.-clientWidth editor) (+ left (if set-default-width? max-width 500)))
-                  {:right 0}
-                  {:left (if (and y-diff (= y-diff 0)) left 0)})))}
+      :style style}
      cp]))
      cp]))
 
 
 (rum/defc transition-cp < rum/reactive
 (rum/defc transition-cp < rum/reactive
-  [cp set-default-width? pos]
-  (when pos
-    (when-let [pos (rum/react pos)]
-      (ui/css-transition
-       {:class-names "fade"
-        :timeout     {:enter 500
-                      :exit  300}}
-       (absolute-modal cp set-default-width? pos)))))
+  [cp set-default-width?]
+  (when-let [pos (:pos (state/sub :editor/action-data))]
+    (ui/css-transition
+     {:class-names "fade"
+      :timeout     {:enter 500
+                    :exit  300}}
+     (absolute-modal cp set-default-width? pos))))
 
 
 (rum/defc image-uploader < rum/reactive
 (rum/defc image-uploader < rum/reactive
   [id format]
   [id format]
@@ -354,8 +406,7 @@
         [:div.flex.flex-row.align-center.rounded-md.shadow-sm.bg-base-2.px-1.py-1
         [:div.flex.flex-row.align-center.rounded-md.shadow-sm.bg-base-2.px-1.py-1
          (ui/loading
          (ui/loading
           (util/format "Uploading %s%" (util/format "%2d" processing)))]
           (util/format "Uploading %s%" (util/format "%2d" processing)))]
-        false
-        *slash-caret-pos)))])
+        false)))])
 
 
 (defn- set-up-key-down!
 (defn- set-up-key-down!
   [state format]
   [state format]
@@ -452,9 +503,9 @@
   (let [content (state/sub-edit-content)]
   (let [content (state/sub-edit-content)]
     (mock-textarea content)))
     (mock-textarea content)))
 
 
-(defn animated-modal
-  [key component set-default-width? *pos]
-  (when *pos
+(rum/defc animated-modal < rum/reactive
+  [key component set-default-width?]
+  (when-let [pos (:pos (state/get-editor-action-data))]
     (ui/css-transition
     (ui/css-transition
      {:key key
      {:key key
       :class-names {:enter "origin-top-left opacity-0 transform scale-95"
       :class-names {:enter "origin-top-left opacity-0 transform scale-95"
@@ -466,48 +517,48 @@
        (absolute-modal
        (absolute-modal
         component
         component
         set-default-width?
         set-default-width?
-        *pos)))))
+        pos)))))
 
 
 (rum/defc modals < rum/reactive
 (rum/defc modals < rum/reactive
   "React to atom changes, find and render the correct modal"
   "React to atom changes, find and render the correct modal"
   [id format]
   [id format]
-  (ui/transition-group
-   (cond
-     (and (util/react *show-commands)
-          (not (state/sub :editor/show-page-search?))
-          (not (state/sub :editor/show-block-search?))
-          (not (state/sub :editor/show-template-search?))
-          (not (state/sub :editor/show-input))
-          (not (state/sub :editor/show-zotero))
-          (not (state/sub :editor/show-date-picker?)))
-     (animated-modal "commands" (commands id format) true (util/react *slash-caret-pos))
-
-     (and (util/react *show-block-commands) @*angle-bracket-caret-pos)
-     (animated-modal "block-commands" (block-commands id format) true (util/react *angle-bracket-caret-pos))
-
-     (state/sub :editor/show-page-search?)
-     (animated-modal "page-search" (page-search id format) true (util/react *slash-caret-pos))
-
-     (state/sub :editor/show-block-search?)
-     (animated-modal "block-search" (block-search id format) false (util/react *slash-caret-pos))
-
-     (state/sub :editor/show-template-search?)
-     (animated-modal "template-search" (template-search id format) true (util/react *slash-caret-pos))
-
-     (state/sub :editor/show-date-picker?)
-     (animated-modal "date-picker" (datetime-comp/date-picker id format nil) false (util/react *slash-caret-pos))
-
-     (state/sub :editor/show-input)
-     (animated-modal "input" (input id
-                                    (fn [command m _pos]
-                                      (editor-handler/handle-command-input command id format m)))
-                     true (util/react *slash-caret-pos))
-
-     (state/sub :editor/show-zotero)
-     (animated-modal "zotero-search" (zotero/zotero-search id) false (util/react *slash-caret-pos))
-
-     :else
-     nil)))
+  (let [action (state/sub :editor/action)]
+    (cond
+      (= action :commands)
+      (animated-modal "commands" (commands id format) true)
+
+      (= action :block-commands)
+      (animated-modal "block-commands" (block-commands id format) true)
+
+      (contains? #{:page-search :page-search-hashtag} action)
+      (animated-modal "page-search" (page-search id format) true)
+
+      (= :block-search action)
+      (animated-modal "block-search" (block-search id format) true)
+
+      (= :template-search action)
+      (animated-modal "template-search" (template-search id format) true)
+
+      (= :property-search action)
+      (animated-modal "property-search" (property-search id) true)
+
+      (= :property-value-search action)
+      (animated-modal "property-value-search" (property-value-search id) true)
+
+      (= :datepicker action)
+      (animated-modal "date-picker" (datetime-comp/date-picker id format nil) false)
+
+      (= :input action)
+      (animated-modal "input" (input id
+                                     (fn [command m]
+                                       (editor-handler/handle-command-input command id format m)))
+                      true)
+
+      (= :zotero action)
+      (animated-modal "zotero-search" (zotero/zotero-search id) false)
+
+      :else
+      nil)))
 
 
 (rum/defcs box < rum/reactive
 (rum/defcs box < rum/reactive
   {:init (fn [state]
   {:init (fn [state]

+ 3 - 3
src/main/frontend/components/header.cljs

@@ -172,10 +172,10 @@
 
 
        {:title [:div.flex-row.flex.justify-between.items-center
        {:title [:div.flex-row.flex.justify-between.items-center
                 [:span (t :join-community)]]
                 [:span (t :join-community)]]
-        :options {:href "https://discord.gg/KpN4eHY"
-                  :title (t :discord-title)
+        :options {:href "https://discuss.logseq.com"
+                  :title (t :discourse-title)
                   :target "_blank"}
                   :target "_blank"}
-        :icon (ui/icon "brand-discord")}]
+        :icon (ui/icon "message-circle")}]
       (concat page-menu-and-hr)
       (concat page-menu-and-hr)
       (remove nil?))
       (remove nil?))
      {})))
      {})))

+ 1 - 4
src/main/frontend/components/journal.cljs

@@ -56,10 +56,7 @@
 
 
       (if today?
       (if today?
         (blocks-cp repo page format)
         (blocks-cp repo page format)
-        (ui/lazy-visible (fn []
-                           (blocks-cp repo page format))
-                         nil
-                         {}))
+        (ui/lazy-visible (fn [] (blocks-cp repo page format))))
 
 
       {})
       {})
 
 

+ 4 - 4
src/main/frontend/components/onboarding.cljs

@@ -12,9 +12,9 @@
 (defn help
 (defn help
   []
   []
   [:div.help.cp__sidebar-help-docs
   [:div.help.cp__sidebar-help-docs
-   (let [discord-with-icon [:div.flex-row.inline-flex.items-center
-                            [:span.mr-1 (t :help/community)]
-                            (ui/icon "brand-discord" {:style {:font-size 20}})]
+   (let [discourse-with-icon [:div.flex-row.inline-flex.items-center
+                            [:span.mr-1 (t :help/forum-community)]
+                            (ui/icon "message-circle" {:style {:font-size 20}})]
          list
          list
          [{:title "Usage"
          [{:title "Usage"
            :children [[[:a
            :children [[[:a
@@ -29,7 +29,7 @@
           {:title "Community"
           {:title "Community"
            :children [[(t :help/awesome-logseq) "https://github.com/logseq/awesome-logseq"]
            :children [[(t :help/awesome-logseq) "https://github.com/logseq/awesome-logseq"]
                       [(t :help/blog) "https://blog.logseq.com"]
                       [(t :help/blog) "https://blog.logseq.com"]
-                      [discord-with-icon "https://discord.gg/KpN4eHY"]]}
+                      [discourse-with-icon "https://discuss.logseq.com"]]}
 
 
           {:title "Development"
           {:title "Development"
            :children [[(t :help/roadmap) "https://trello.com/b/8txSM12G/roadmap"]
            :children [[(t :help/roadmap) "https://trello.com/b/8txSM12G/roadmap"]

+ 13 - 8
src/main/frontend/components/plugins.cljs

@@ -855,26 +855,31 @@
   "type of :toolbar, :pagebar"
   "type of :toolbar, :pagebar"
   [_state type]
   [_state type]
   (when (state/sub [:plugin/installed-ui-items])
   (when (state/sub [:plugin/installed-ui-items])
-    (let [pinned-items (state/sub [:plugin/preferences :pinnedToolbarItems])
+    (let [toolbar?     (= :toolbar type)
+          pinned-items (state/sub [:plugin/preferences :pinnedToolbarItems])
           pinned-items (and (sequential? pinned-items) (into #{} pinned-items))
           pinned-items (and (sequential? pinned-items) (into #{} pinned-items))
           items        (state/get-plugins-ui-items-with-type type)
           items        (state/get-plugins-ui-items-with-type type)
           items        (sort-by #(:key (second %)) items)]
           items        (sort-by #(:key (second %)) items)]
 
 
       (when-let [items (and (seq items)
       (when-let [items (and (seq items)
-                            (map #(assoc-in % [1 :pinned?]
-                                            (let [[_ {:keys [key]} pid] %
-                                                  pkey (str (name pid) ":" key)]
-                                              (contains? pinned-items pkey)))
-                                 items))]
+                            (if toolbar?
+                              (map #(assoc-in % [1 :pinned?]
+                                              (let [[_ {:keys [key]} pid] %
+                                                    pkey (str (name pid) ":" key)]
+                                                (contains? pinned-items pkey)))
+                                   items)
+                              items))]
 
 
         [:div {:class     (str "ui-items-container")
         [:div {:class     (str "ui-items-container")
                :data-type (name type)}
                :data-type (name type)}
          (conj (for [[_ {:keys [key pinned?] :as opts} pid] items]
          (conj (for [[_ {:keys [key pinned?] :as opts} pid] items]
-                 (when (or (not (set? pinned-items)) pinned?)
+                 (when (or (not toolbar?)
+                           (not (set? pinned-items)) pinned?)
                    (rum/with-key (ui-item-renderer pid type opts) key))))
                    (rum/with-key (ui-item-renderer pid type opts) key))))
 
 
          ;; manage plugin buttons
          ;; manage plugin buttons
-         (toolbar-plugins-manager-list items)]))))
+         (when toolbar?
+           (toolbar-plugins-manager-list items))]))))
 
 
 (rum/defcs hook-ui-fenced-code < rum/reactive
 (rum/defcs hook-ui-fenced-code < rum/reactive
   [_state content {:keys [render edit] :as _opts}]
   [_state content {:keys [render edit] :as _opts}]

+ 1 - 3
src/main/frontend/components/reference.cljs

@@ -169,9 +169,7 @@
    (ui/component-error "Linked References: Unexpected error")
    (ui/component-error "Linked References: Unexpected error")
    (ui/lazy-visible
    (ui/lazy-visible
     (fn []
     (fn []
-      (references* page-name))
-    nil
-    {})))
+      (references* page-name)))))
 
 
 (rum/defcs unlinked-references-aux
 (rum/defcs unlinked-references-aux
   < rum/reactive db-mixins/query
   < rum/reactive db-mixins/query

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

@@ -216,7 +216,7 @@
 
 
        :new-page
        :new-page
        [:div.text.font-bold (str (t :new-page) ": ")
        [:div.text.font-bold (str (t :new-page) ": ")
-        [:span.ml-1 (str "\"" search-q "\"")]]
+        [:span.ml-1 (str "\"" (string/trim search-q) "\"")]]
 
 
        :go-to-whiteboard
        :go-to-whiteboard
        [:div.text.font-bold (str (t :go-to-whiteboard) ": ")
        [:div.text.font-bold (str (t :go-to-whiteboard) ": ")

+ 2 - 2
src/main/frontend/components/widgets.cljs

@@ -39,9 +39,9 @@
 
 
          [:li.mt-8
          [:li.mt-8
           [:div.font-bold.mb-2 "I need some help"]
           [:div.font-bold.mb-2 "I need some help"]
-          [:p "👋 Join our discord group to chat with the makers and our helpful community members."]
+          [:p "👋 Join our Forum to chat with the makers and our helpful community members."]
           (ui/button "Join the community"
           (ui/button "Join the community"
-            :href "https://discord.gg/KpN4eHY"
+            :href "https://discuss.logseq.com"
             :target "_blank")]]]
             :target "_blank")]]]
        [:div.cp__widgets-open-local-directory
        [:div.cp__widgets-open-local-directory
         [:div.select-file-wrap.cursor
         [:div.select-file-wrap.cursor

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

@@ -31,7 +31,7 @@
    "
    "
     Welcome to Logseq!
     Welcome to Logseq!
     If you encounter any problem, feel free to file an issue on GitHub (https://github.com/logseq/logseq)
     If you encounter any problem, feel free to file an issue on GitHub (https://github.com/logseq/logseq)
-    or join our Discord server (https://discord.gg/KpN4eHY).
+    or join our forum (https://discuss.logseq.com).
     .____
     .____
     |    |    ____   ____  ______ ____  ______
     |    |    ____   ____  ______ ____  ______
     |    |   /  _ \\ / ___\\/  ___// __ \\/ ____/
     |    |   /  _ \\ / ___\\/  ___// __ \\/ ____/

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

@@ -1341,6 +1341,40 @@
                 [(get m :template) e]))
                 [(get m :template) e]))
          (into {}))))
          (into {}))))
 
 
+(defn get-all-properties
+  []
+  (let [properties (d/q
+                     '[:find [?p ...]
+                       :where
+                       [_ :block/properties ?p]]
+                     (conn/get-db))
+        properties (remove (fn [m] (empty? m)) properties)]
+    (->> (map keys properties)
+         (apply concat)
+         distinct
+         (remove #{:id})
+         sort)))
+
+(defn get-property-values
+  [property]
+  (let [pred (fn [_db properties]
+               (get properties property))]
+    (->>
+     (d/q
+       '[:find [?property-val ...]
+         :in $ ?pred
+         :where
+         [_ :block/properties ?p]
+         [(?pred $ ?p) ?property-val]]
+       (conn/get-db)
+       pred)
+     (map (fn [x] (if (coll? x) x [x])))
+     (apply concat)
+     (map str)
+     (remove string/blank?)
+     (distinct)
+     (sort))))
+
 (defn get-template-by-name
 (defn get-template-by-name
   [name]
   [name]
   (when (string? name)
   (when (string? name)

+ 14 - 12
src/main/frontend/dicts.cljc

@@ -26,6 +26,7 @@
         :help/privacy "Privacy policy"
         :help/privacy "Privacy policy"
         :help/terms "Terms"
         :help/terms "Terms"
         :help/community "Discord community"
         :help/community "Discord community"
+        :help/forum-community "Forum community"
         :help/awesome-logseq "Awesome Logseq"
         :help/awesome-logseq "Awesome Logseq"
         :help/shortcuts "Keyboard shortcuts"
         :help/shortcuts "Keyboard shortcuts"
         :help/shortcuts-triggers "Triggers"
         :help/shortcuts-triggers "Triggers"
@@ -253,7 +254,8 @@
         :import "Import"
         :import "Import"
         :join-community "Join the community"
         :join-community "Join the community"
         :sponsor-us "Sponsor Us"
         :sponsor-us "Sponsor Us"
-        :discord-title "Our discord group!"
+        :discourse-title "Our forum!"
+        :discord-title "Our discord group!" ;; unused
         :help-shortcut-title "Click to check shortcuts and other tips"
         :help-shortcut-title "Click to check shortcuts and other tips"
         :loading "Loading"
         :loading "Loading"
         :cloning "Cloning"
         :cloning "Cloning"
@@ -809,11 +811,11 @@
            :right-side-bar/favorites "收藏"
            :right-side-bar/favorites "收藏"
            :right-side-bar/page-graph "页面图谱:"
            :right-side-bar/page-graph "页面图谱:"
            :right-side-bar/block-ref "块引用"
            :right-side-bar/block-ref "块引用"
-           :right-side-bar/graph-view "Graph view"
-           :right-side-bar/all-pages "All pages"
-           :right-side-bar/flashcards "Flashcards"
-           :right-side-bar/new-page "New page"
-           :left-side-bar/journals "Journals"
+           :right-side-bar/graph-view "图谱视角"
+           :right-side-bar/all-pages "全部页面"
+           :right-side-bar/flashcards "记忆卡片"
+           :right-side-bar/new-page "新页面"
+           :left-side-bar/journals "日志"
            :left-side-bar/new-page "新页面"
            :left-side-bar/new-page "新页面"
            :left-side-bar/nav-favorites "收藏页面"
            :left-side-bar/nav-favorites "收藏页面"
            :left-side-bar/nav-shortcuts "快捷导航"
            :left-side-bar/nav-shortcuts "快捷导航"
@@ -964,7 +966,7 @@
            :all-journals "日记"
            :all-journals "日记"
            :publishing "发布"
            :publishing "发布"
            :export "导出"
            :export "导出"
-           :all-graphs "所有"
+           :all-graphs "所有图谱"
            :all-pages "所有页面"
            :all-pages "所有页面"
            :all-files "所有文件"
            :all-files "所有文件"
            :remove-orphaned-pages "删除空页面"
            :remove-orphaned-pages "删除空页面"
@@ -1104,11 +1106,11 @@
              :right-side-bar/recent "最近"
              :right-side-bar/recent "最近"
              :right-side-bar/contents "目錄"
              :right-side-bar/contents "目錄"
              :right-side-bar/block-ref "塊引用"
              :right-side-bar/block-ref "塊引用"
-             :right-side-bar/graph-view "Graph view"
-             :right-side-bar/all-pages "All pages"
-             :right-side-bar/flashcards "Flashcards"
-             :right-side-bar/new-page "New page"
-             :left-side-bar/journals "Journals"
+             :right-side-bar/graph-view "圖譜視角"
+             :right-side-bar/all-pages "全部頁面"
+             :right-side-bar/flashcards "記憶卡片"
+             :right-side-bar/new-page "新頁面"
+             :left-side-bar/journals "日誌"
              :format/preferred-mode "請選擇偏好格式"
              :format/preferred-mode "請選擇偏好格式"
              :format/markdown "Markdown"
              :format/markdown "Markdown"
              :format/org-mode "Org Mode"
              :format/org-mode "Org Mode"

+ 30 - 13
src/main/frontend/extensions/html_parser.cljs

@@ -11,7 +11,11 @@
   [hiccup]
   [hiccup]
   (walk/postwalk (fn [f]
   (walk/postwalk (fn [f]
                    (if (map? f)
                    (if (map? f)
-                     (dissoc f :style)
+                      (apply dissoc f (conj (filter (fn [key]
+                                                      (string/starts-with? (str key) ":data-"))
+                                                    (keys f))
+                                            :style
+                                            :class))
                      f)) hiccup))
                      f)) hiccup))
 
 
 (defn- export-hiccup
 (defn- export-hiccup
@@ -73,7 +77,7 @@
                                              :else
                                              :else
                                              nil)
                                              nil)
                                    children' (map-join children)]
                                    children' (map-join children)]
-                               (when-not (string/blank? children')
+                               (when (not-empty children')
                                  (str (if (string? pattern) pattern (apply str pattern))
                                  (str (if (string? pattern) pattern (apply str pattern))
                                       children'
                                       children'
                                       (if (string? pattern) pattern (apply str (reverse pattern)))))))
                                       (if (string? pattern) pattern (apply str (reverse pattern)))))))
@@ -122,11 +126,15 @@
                                       :org (util/format "[[%s][%s]]" href label)
                                       :org (util/format "[[%s][%s]]" href label)
                                       nil))))
                                       nil))))
                            :img (let [src (:src attrs)
                            :img (let [src (:src attrs)
-                                      alt (or (:alt attrs) "")]
-                                  (case format
-                                    :markdown (util/format "![%s](%s)" alt src)
-                                    :org (util/format "[[%s][%s]]" src alt)
-                                    nil))
+                                      alt (or (:alt attrs) "")
+                                      ;; reject url-encoded and utf8-encoded(svg)
+                                      unsafe-data-url? (and (string/starts-with? src "data:")
+                                                            (not (re-find #"^data:.*?;base64," src)))]
+                                  (when-not unsafe-data-url?
+                                    (case format
+                                      :markdown (util/format "![%s](%s)" alt src)
+                                      :org (util/format "[[%s][%s]]" src alt)
+                                      nil)))
                            :p (util/format "%s"
                            :p (util/format "%s"
                                            (map-join children))
                                            (map-join children))
 
 
@@ -140,12 +148,19 @@
                                                    :span} %))
                                                    :span} %))
                            (emphasis-transform tag attrs children)
                            (emphasis-transform tag attrs children)
 
 
-                           :code (if @*inside-pre?
+                           :code (cond
+                                   @*inside-pre?
                                    (map-join children)
                                    (map-join children)
+
+                                   (string? (first children))
                                    (let [pattern (config/get-code format)]
                                    (let [pattern (config/get-code format)]
                                      (str " "
                                      (str " "
                                           (str pattern (first children) pattern)
                                           (str pattern (first children) pattern)
-                                          " ")))
+                                          " "))
+
+                                   ;; skip monospace style, since it has more complex children
+                                   :else
+                                   (map-join children))
 
 
                            :pre
                            :pre
                            (do
                            (do
@@ -170,6 +185,9 @@
                            :li
                            :li
                            (str "- " (map-join children))
                            (str "- " (map-join children))
 
 
+                           :br
+                           "\n"
+
                            :dt
                            :dt
                            (case format
                            (case format
                              :org (str "- " (map-join children) " ")
                              :org (str "- " (map-join children) " ")
@@ -218,7 +236,7 @@
                  (for [x hiccup]
                  (for [x hiccup]
                    (single-hiccup-transform x))
                    (single-hiccup-transform x))
                  (single-hiccup-transform hiccup))]
                  (single-hiccup-transform hiccup))]
-    (string/replace (apply str result) #"\n\n+" "\n\n")))
+    (apply str result)))
 
 
 (defn hiccup->doc
 (defn hiccup->doc
   [format hiccup]
   [format hiccup]
@@ -226,9 +244,8 @@
     (if (string/blank? s)
     (if (string/blank? s)
       ""
       ""
       (-> s
       (-> s
-          (string/trim)
-          (string/replace "\n\n\n\n" "\n\n")
-          (string/replace "\n\n\n" "\n\n")))))
+          string/trim
+          (string/replace #"\n\n+" "\n\n")))))
 
 
 (defn html-decode-hiccup
 (defn html-decode-hiccup
   [hiccup]
   [hiccup]

+ 50 - 40
src/main/frontend/extensions/srs.cljs

@@ -254,6 +254,10 @@
                        :or {use-cache? true}}]
                        :or {use-cache? true}}]
    (when (string? query-string)
    (when (string? query-string)
      (let [query-string (template/resolve-dynamic-template! query-string)
      (let [query-string (template/resolve-dynamic-template! query-string)
+           query-string (if (and (not (string/starts-with? query-string "("))
+                                 (not (string/starts-with? query-string "[")))
+                          (util/format "[[%s]]" (string/trim query-string))
+                          query-string)
            {:keys [query sort-by rules]} (query-dsl/parse query-string)
            {:keys [query sort-by rules]} (query-dsl/parse query-string)
            query* (concat [['?b :block/refs '?bp] ['?bp :block/name card-hash-tag]]
            query* (concat [['?b :block/refs '?bp] ['?bp :block/name card-hash-tag]]
                           (if (coll? (first query))
                           (if (coll? (first query))
@@ -395,14 +399,15 @@
   [:p.p-2 "Congrats, you've reviewed all the cards for this query, see you next time! 💯"])
   [:p.p-2 "Congrats, you've reviewed all the cards for this query, see you next time! 💯"])
 
 
 (defn- btn-with-shortcut [{:keys [shortcut id btn-text background on-click]}]
 (defn- btn-with-shortcut [{:keys [shortcut id btn-text background on-click]}]
-  (ui/button [:span btn-text " " (ui/render-keyboard-shortcut shortcut)]
-             :id id
-             :background background
-             :on-click (fn [e]
-                         (when-let [elem (gobj/get e "target")]
-                           (js/console.log (.-classList elem))
-                           (.add (.-classList elem) "opacity-25"))
-                         (js/setTimeout #(on-click) 10))))
+  (ui/button
+    [:span btn-text " " (ui/render-keyboard-shortcut shortcut)]
+    :id id
+    :class id
+    :background background
+    :on-click (fn [e]
+                (when-let [elem (gobj/get e "target")]
+                  (.add (.-classList elem) "opacity-25"))
+                (js/setTimeout #(on-click) 10))))
 
 
 (rum/defcs view
 (rum/defcs view
   < rum/reactive
   < rum/reactive
@@ -419,7 +424,8 @@
                  modal? :modal?
                  modal? :modal?
                  cb :callback}
                  cb :callback}
    card-index]
    card-index]
-  (let [cards (map ->card blocks)
+  (let [blocks (if (fn? blocks) (blocks) blocks)
+        cards (map ->card blocks)
         review-records (::review-records state)
         review-records (::review-records state)
         ;; TODO: needs refactor
         ;; TODO: needs refactor
         card (if preview?
         card (if preview?
@@ -433,7 +439,10 @@
             root-block-id (:block/uuid root-block)]
             root-block-id (:block/uuid root-block)]
         [:div.ls-card.content
         [:div.ls-card.content
          {:class (when (or preview? modal?)
          {:class (when (or preview? modal?)
-                   (util/hiccup->class ".flex.flex-col.resize.overflow-y-auto"))}
+                   (str (util/hiccup->class ".flex.flex-col.resize.overflow-y-auto")
+                        (when modal? " modal-cards")))
+          :on-mouse-down (fn [e]
+                           (util/stop e))}
          (let [repo (state/get-current-repo)]
          (let [repo (state/get-current-repo)]
            [:div {:style {:margin-top 20}}
            [:div {:style {:margin-top 20}}
             (component-block/breadcrumb {} repo root-block-id {})])
             (component-block/breadcrumb {} repo root-block-id {})])
@@ -446,20 +455,20 @@
          (if (or preview? modal?)
          (if (or preview? modal?)
            [:div.flex.my-4.justify-between
            [:div.flex.my-4.justify-between
             (when-not (and (not preview?) (= next-phase 1))
             (when-not (and (not preview?) (= next-phase 1))
-              (ui/button [:span (case next-phase
-                                  1 "Hide answers"
-                                  2 "Show answers"
-                                  3 "Show clozes")
-                          (ui/render-keyboard-shortcut [:s])]
-                         :id "card-answers"
-                         :class "mr-2"
-                         :on-click #(reset! phase next-phase)))
-
+              (ui/button
+                [:span (case next-phase
+                         1 "Hide answers"
+                         2 "Show answers"
+                         3 "Show clozes")
+                 (ui/render-keyboard-shortcut [:s])]
+                :class "mr-2 card-answers"
+                :on-click #(reset! phase next-phase)))
             (when (and (> (count cards) 1) preview?)
             (when (and (> (count cards) 1) preview?)
               (ui/button [:span "Next " (ui/render-keyboard-shortcut [:n])]
               (ui/button [:span "Next " (ui/render-keyboard-shortcut [:n])]
-                         :id "card-next"
-                         :class "mr-2"
-                         :on-click #(skip-card card card-index cards phase review-records cb)))
+                :class "mr-2 card-next"
+                :on-click (fn [e]
+                            (util/stop e)
+                            (skip-card card card-index cards phase review-records cb))))
 
 
             (when (and (not preview?) (= 1 next-phase))
             (when (and (not preview?) (= 1 next-phase))
               [:<>
               [:<>
@@ -489,9 +498,11 @@
                          :class "tippy-hover"
                          :class "tippy-hover"
                          :interactive true}
                          :interactive true}
                         (ui/button [:span "Reset"]
                         (ui/button [:span "Reset"]
-                                   :id "card-reset"
-                                   :class (util/hiccup->class "opacity-60.hover:opacity-100")
-                                   :on-click #(operation-reset! card))))]
+                          :id "card-reset"
+                          :class (util/hiccup->class "opacity-60.hover:opacity-100.card-reset")
+                          :on-click (fn [e]
+                                      (util/stop e)
+                                      (operation-reset! card)))))]
            [:div.my-3 (ui/button "Review cards" :small? true)])]))))
            [:div.my-3 (ui/button "Review cards" :small? true)])]))))
 
 
 (rum/defc view-modal <
 (rum/defc view-modal <
@@ -504,9 +515,10 @@
         blocks (if (:random-mode? option)
         blocks (if (:random-mode? option)
                  (shuffle blocks)
                  (shuffle blocks)
                  blocks)]
                  blocks)]
-    (if (seq blocks)
-      (view blocks option card-index)
-      review-finished)))
+    [:div#cards-modal
+     (if (seq blocks)
+       (view blocks option card-index)
+       review-finished)]))
 
 
 (rum/defc preview-cp
 (rum/defc preview-cp
   [block-id]
   [block-id]
@@ -639,28 +651,26 @@
                                                  :font-weight 600}
                                                  :font-weight 600}
                                                  @*random-mode?
                                                  @*random-mode?
                                                  (assoc :color "orange"))})])]]
                                                  (assoc :color "orange"))})])]]
-         (if (seq review-cards)
+         (if (or @*preview-mode? (seq review-cards))
            [:div.px-1
            [:div.px-1
-            (when-not modal?
+            (when (and (not modal?) (not @*preview-mode?))
               {:on-click (fn []
               {:on-click (fn []
-                           (let [blocks-f (if @*preview-mode?
-                                            (fn [] (query repo query-string))
-                                            (fn []
-                                              (let [query-result (query repo query-string)]
-                                                (:result (query-scheduled repo query-result (tl/local-now))))))]
+                           (let [blocks-f (fn []
+                                            (let [query-result (query repo query-string)]
+                                              (:result (query-scheduled repo query-result (tl/local-now)))))]
                              (state/set-modal! #(view-modal
                              (state/set-modal! #(view-modal
                                                  blocks-f
                                                  blocks-f
                                                  {:modal? true
                                                  {:modal? true
                                                   :random-mode? *random-mode?
                                                   :random-mode? *random-mode?
-                                                  :preview? @*preview-mode?
+                                                  :preview? false
                                                   :callback callback-fn}
                                                   :callback callback-fn}
                                                  *card-index)
                                                  *card-index)
                                                {:id :srs})))})
                                                {:id :srs})))})
             (let [view-fn (if modal? view-modal view)
             (let [view-fn (if modal? view-modal view)
-                  blocks-fn (if @*preview-mode?
-                              (fn [] (query repo query-string))
-                              review-cards)]
-              (view-fn blocks-fn
+                  blocks (if @*preview-mode?
+                           (query repo query-string)
+                           review-cards)]
+              (view-fn blocks
                (merge config
                (merge config
                       {:global? global?
                       {:global? global?
                        :random-mode? @*random-mode?
                        :random-mode? @*random-mode?

+ 5 - 3
src/main/frontend/extensions/srs/handler.cljs

@@ -1,9 +1,11 @@
-(ns frontend.extensions.srs.handler)
+(ns frontend.extensions.srs.handler
+  (:require [dommy.core :refer-macros [sel]]))
 
 
 (defn click
 (defn click
   [id]
   [id]
-  (when-let [node (js/document.getElementById id)]
-    (.click node)))
+  (let [nodes (sel [:#cards-modal (str "." id)])]
+    (doseq [node nodes]
+      (.click node))))
 
 
 (defn toggle-answers []
 (defn toggle-answers []
   (click "card-answers"))
   (click "card-answers"))

+ 1 - 1
src/main/frontend/extensions/zotero/handler.cljs

@@ -41,7 +41,7 @@
 
 
 (defn handle-command-zotero
 (defn handle-command-zotero
   [id page-name]
   [id page-name]
-  (state/set-editor-show-zotero! false)
+  (state/clear-editor-action!)
   (editor-handler/insert-command! id (str "[[" page-name "]]") nil {}))
   (editor-handler/insert-command! id (str "[[" page-name "]]") nil {}))
 
 
 (defn- create-abstract-note!
 (defn- create-abstract-note!

+ 1 - 1
src/main/frontend/fs/watcher_handler.cljs

@@ -90,7 +90,7 @@
             (when dir-exists?
             (when dir-exists?
               (when-let [page-name (db/get-file-page path)]
               (when-let [page-name (db/get-file-page path)]
                 (println "Delete page: " page-name ", file path: " path ".")
                 (println "Delete page: " page-name ", file path: " path ".")
-                (page-handler/delete! page-name #() :unlink-file? true))))
+                (page-handler/delete! page-name #() :delete-file? false))))
 
 
           (and (contains? #{"add" "change" "unlink"} type)
           (and (contains? #{"add" "change" "unlink"} type)
                (string/ends-with? path "logseq/custom.css"))
                (string/ends-with? path "logseq/custom.css"))

+ 201 - 156
src/main/frontend/handler/editor.cljs

@@ -4,10 +4,7 @@
             [clojure.string :as string]
             [clojure.string :as string]
             [clojure.walk :as w]
             [clojure.walk :as w]
             [dommy.core :as dom]
             [dommy.core :as dom]
-            [frontend.commands :as commands
-             :refer [*angle-bracket-caret-pos
-                     *show-block-commands *show-commands
-                     *slash-caret-pos]]
+            [frontend.commands :as commands]
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.date :as date]
             [frontend.date :as date]
             [frontend.db :as db]
             [frontend.db :as db]
@@ -525,8 +522,7 @@
 
 
 (defn clear-when-saved!
 (defn clear-when-saved!
   []
   []
-  (state/clear-editor-show-state!)
-  (commands/restore-state true))
+  (commands/restore-state))
 
 
 (defn get-state
 (defn get-state
   []
   []
@@ -664,7 +660,7 @@
 (defn properties-block
 (defn properties-block
   [properties format page]
   [properties format page]
   (let [content (property/insert-properties format "" properties)
   (let [content (property/insert-properties format "" properties)
-        refs (gp-block/get-page-refs-from-properties properties
+        refs (gp-block/get-page-refs-from-properties format properties
                                                      (db/get-db (state/get-current-repo))
                                                      (db/get-db (state/get-current-repo))
                                                      (state/get-date-formatter))]
                                                      (state/get-date-formatter))]
     {:block/pre-block? true
     {:block/pre-block? true
@@ -1292,14 +1288,7 @@
    ;; non English input method
    ;; non English input method
    (when-not (state/editor-in-composition?)
    (when-not (state/editor-in-composition?)
      (when (state/get-current-repo)
      (when (state/get-current-repo)
-       (when (and (not @commands/*show-commands)
-                  (not @commands/*show-block-commands)
-                  (not (state/get-editor-show-page-search?))
-                  (not (state/get-editor-show-page-search-hashtag?))
-                  (not (state/get-editor-show-block-search?))
-                  (not (state/get-editor-show-date-picker?))
-                  (not (state/get-editor-show-template-search?))
-                  (not (state/get-editor-show-input)))
+       (when-not (state/get-editor-action)
          (try
          (try
            (let [input-id (state/get-edit-input-id)
            (let [input-id (state/get-edit-input-id)
                  block (state/get-edit-block)
                  block (state/get-edit-block)
@@ -1357,13 +1346,7 @@
     nil)
     nil)
 
 
   (when restore?
   (when restore?
-    (let [restore-slash-caret-pos? (if (and
-                                        (seq? command-output)
-                                        (= :editor/click-hidden-file-input
-                                           (ffirst command-output)))
-                                     false
-                                     true)]
-      (commands/restore-state restore-slash-caret-pos?))))
+    (commands/restore-state)))
 
 
 (defn get-asset-file-link
 (defn get-asset-file-link
   [format url file-name image?]
   [format url file-name image?]
@@ -1570,12 +1553,12 @@
           "[["
           "[["
           (do
           (do
             (commands/handle-step [:editor/search-page])
             (commands/handle-step [:editor/search-page])
-            (reset! commands/*slash-caret-pos (cursor/get-caret-pos input)))
+            (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}))
 
 
           "(("
           "(("
           (do
           (do
             (commands/handle-step [:editor/search-block :reference])
             (commands/handle-step [:editor/search-block :reference])
-            (reset! commands/*slash-caret-pos (cursor/get-caret-pos input)))
+            (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}))
 
 
           nil)))))
           nil)))))
 
 
@@ -1626,12 +1609,20 @@
   [q]
   [q]
   (search/template-search q))
   (search/template-search q))
 
 
+(defn get-matched-properties
+  [q]
+  (search/property-search q))
+
+(defn get-matched-property-values
+  [property q]
+  (search/property-value-search property q))
+
 (defn get-matched-commands
 (defn get-matched-commands
   [input]
   [input]
   (try
   (try
     (let [edit-content (or (gobj/get input "value") "")
     (let [edit-content (or (gobj/get input "value") "")
           pos (cursor/pos input)
           pos (cursor/pos input)
-          last-slash-caret-pos (:pos @*slash-caret-pos)
+          last-slash-caret-pos (:pos (:pos (state/get-editor-action-data)))
           last-command (and last-slash-caret-pos (subs edit-content last-slash-caret-pos pos))]
           last-command (and last-slash-caret-pos (subs edit-content last-slash-caret-pos pos))]
       (when (> pos 0)
       (when (> pos 0)
         (or
         (or
@@ -1649,7 +1640,7 @@
     (let [edit-content (gobj/get input "value")
     (let [edit-content (gobj/get input "value")
           pos (cursor/pos input)
           pos (cursor/pos input)
           last-command (subs edit-content
           last-command (subs edit-content
-                             (:pos @*angle-bracket-caret-pos)
+                             (:pos (:pos (state/get-editor-action-data)))
                              pos)]
                              pos)]
       (when (> pos 0)
       (when (> pos 0)
         (or
         (or
@@ -1664,14 +1655,8 @@
 
 
 (defn auto-complete?
 (defn auto-complete?
   []
   []
-  (or @*show-commands
-      @*show-block-commands
-      @*asset-uploading?
-      (state/get-editor-show-input)
-      (state/get-editor-show-page-search?)
-      (state/get-editor-show-block-search?)
-      (state/get-editor-show-template-search?)
-      (state/get-editor-show-date-picker?)))
+  (or @*asset-uploading?
+      (state/get-editor-action)))
 
 
 (defn get-current-input-char
 (defn get-current-input-char
   [input]
   [input]
@@ -1785,9 +1770,7 @@
 (defn close-autocomplete-if-outside
 (defn close-autocomplete-if-outside
   [input]
   [input]
   (when (and input
   (when (and input
-             (or (state/get-editor-show-page-search?)
-                 (state/get-editor-show-page-search-hashtag?)
-                 (state/get-editor-show-block-search?))
+             (state/get-editor-action)
              (not (wrapped-by? input "[[" "]]")))
              (not (wrapped-by? input "[[" "]]")))
     (when (get-search-q)
     (when (get-search-q)
       (let [value (gobj/get input "value")
       (let [value (gobj/get input "value")
@@ -1800,9 +1783,7 @@
                     (string/includes? between "]")
                     (string/includes? between "]")
                     (string/includes? between "(")
                     (string/includes? between "(")
                     (string/includes? between ")")))
                     (string/includes? between ")")))
-          (state/set-editor-show-block-search! false)
-          (state/set-editor-show-page-search! false)
-          (state/set-editor-show-page-search-hashtag! false))))))
+          (state/clear-editor-action!))))))
 
 
 (defn resize-image!
 (defn resize-image!
   [block-id metadata full_text size]
   [block-id metadata full_text size]
@@ -1842,25 +1823,51 @@
 (defn handle-last-input []
 (defn handle-last-input []
   (let [input           (state/get-input)
   (let [input           (state/get-input)
         pos             (cursor/pos input)
         pos             (cursor/pos input)
-        last-input-char (util/nth-safe (.-value input) (dec pos))]
+        last-input-char (util/nth-safe (.-value input) (dec pos))
+        last-prev-input-char (util/nth-safe (.-value input) (dec (dec pos)))
+        prev-prev-input-char (util/nth-safe (.-value input) (- pos 3))]
 
 
     ;; TODO: is it cross-browser compatible?
     ;; TODO: is it cross-browser compatible?
     ;; (not= (gobj/get native-e "inputType") "insertFromPaste")
     ;; (not= (gobj/get native-e "inputType") "insertFromPaste")
-    (when (= last-input-char (state/get-editor-command-trigger))
-      (when (seq (get-matched-commands input))
-        (reset! commands/*slash-caret-pos (cursor/get-caret-pos input))
-        (reset! commands/*show-commands true)))
-
-    (if (= last-input-char commands/angle-bracket)
-      (when (seq (get-matched-block-commands input))
-        (reset! commands/*angle-bracket-caret-pos (cursor/get-caret-pos input))
-        (reset! commands/*show-block-commands true))
+    (cond
+      (= last-input-char (state/get-editor-command-trigger))
+      (do
+        (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
+        (commands/reinit-matched-commands!)
+        (state/set-editor-show-commands!))
+
+      (= last-input-char commands/angle-bracket)
+      (do
+        (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
+        (commands/reinit-matched-block-commands!)
+        (state/set-editor-show-block-commands!))
+
+      (and (= last-input-char last-prev-input-char commands/colon)
+           (or (nil? prev-prev-input-char)
+               (= prev-prev-input-char "\n")))
+      (do
+        (cursor/move-cursor-backward input 2)
+        (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
+        (state/set-editor-action! :property-search))
+
+      (and
+       (not= :property-search (state/get-editor-action))
+       (or (wrapped-by? input "" "::")
+           (wrapped-by? input "\n" "::")))
+      (do
+        (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
+        (state/set-editor-action! :property-search))
+
+      (and (= last-input-char commands/colon) (= :property-search (state/get-editor-action)))
+      (state/clear-editor-action!)
+
+      :else
       nil)))
       nil)))
 
 
 (defn block-on-chosen-handler
 (defn block-on-chosen-handler
   [_input id q format]
   [_input id q format]
   (fn [chosen _click?]
   (fn [chosen _click?]
-    (state/set-editor-show-block-search! false)
+    (state/clear-editor-action!)
     (let [uuid-string (str (:block/uuid chosen))]
     (let [uuid-string (str (:block/uuid chosen))]
 
 
       ;; block reference
       ;; block reference
@@ -1883,7 +1890,7 @@
 (defn block-non-exist-handler
 (defn block-non-exist-handler
   [input]
   [input]
   (fn []
   (fn []
-    (state/set-editor-show-block-search! false)
+    (state/clear-editor-action!)
     (cursor/move-cursor-forward input 2)))
     (cursor/move-cursor-forward input 2)))
 
 
 (defn- paste-block-cleanup
 (defn- paste-block-cleanup
@@ -2053,6 +2060,44 @@
     (insert-template! element-id db-id
     (insert-template! element-id db-id
                       {:replace-empty-target? true})))
                       {:replace-empty-target? true})))
 
 
+(defn get-searching-property
+  [input]
+  (let [value (.-value input)
+        pos (util/get-selection-start input)
+        postfix (subs value pos)
+        end-index (when-let [idx (string/index-of postfix "::")]
+                    (+ (max 0 (count (subs value 0 pos))) idx))
+        start-index (or (when-let [p (string/last-index-of (subs value 0 pos) "\n")]
+                          (inc p))
+                        0)]
+    {:end-index end-index
+     :searching-property (when (and start-index end-index (>= end-index start-index))
+                           (subs value start-index end-index))}))
+
+(defn property-on-chosen-handler
+  [element-id q]
+  (fn [property]
+    (when-let [input (gdom/getElement element-id)]
+      (let [{:keys [end-index searching-property]} (get-searching-property input)]
+        (cursor/move-cursor-to input (+ end-index 2))
+        (commands/insert! element-id (str (or property q) ":: ")
+                          {:last-pattern (str searching-property "::")})
+        (state/clear-editor-action!)
+        (js/setTimeout (fn []
+                         (let [pos (let [input (gdom/getElement element-id)]
+                                     (cursor/get-caret-pos input))]
+                           (state/set-editor-action-data! {:property (or property q)
+                                                           :pos pos})
+                           (state/set-editor-action! :property-value-search)))
+                       50)))))
+
+(defn property-value-on-chosen-handler
+  [element-id q]
+  (fn [property-value]
+    (commands/insert! element-id (str ":: " (or property-value q))
+                      {:last-pattern (str ":: " q)})
+    (state/clear-editor-action!)))
+
 (defn parent-is-page?
 (defn parent-is-page?
   [{{:block/keys [parent page]} :data :as node}]
   [{{:block/keys [parent page]} :data :as node}]
   {:pre [(tree/satisfied-inode? node)]}
   {:pre [(tree/satisfied-inode? node)]}
@@ -2252,9 +2297,9 @@
                            parent-id bounds
                            parent-id bounds
                            {:backward-pos backward-pos
                            {:backward-pos backward-pos
                             :check-fn (fn [_ _ _]
                             :check-fn (fn [_ _ _]
-                                        (reset! commands/*slash-caret-pos new-pos)
+                                        (state/set-editor-action-data! {:pos new-pos})
                                         (commands/handle-step [:editor/search-page]))}))]
                                         (commands/handle-step [:editor/search-page]))}))]
-        (state/set-editor-show-page-search! false)
+        (state/clear-editor-action!)
         (let [selection (get-selection-and-format)
         (let [selection (get-selection-and-format)
               {:keys [selection-start selection-end selection]} selection]
               {:keys [selection-start selection-end selection]} selection]
           (if selection
           (if selection
@@ -2285,9 +2330,9 @@
                             parent-id bounds
                             parent-id bounds
                             {:backward-pos backward-pos
                             {:backward-pos backward-pos
                              :check-fn     (fn [_ _ _]
                              :check-fn     (fn [_ _ _]
-                                             (reset! commands/*slash-caret-pos new-pos)
+                                             (state/set-editor-action-data! {:pos new-pos})
                                              (commands/handle-step [:editor/search-block]))}))]
                                              (commands/handle-step [:editor/search-block]))}))]
-        (state/set-editor-show-block-search! false)
+        (state/clear-editor-action!)
         (if-let [embed-ref (thingatpt/embed-macro-at-point input)]
         (if-let [embed-ref (thingatpt/embed-macro-at-point input)]
           (let [{:keys [raw-content start end]} embed-ref]
           (let [{:keys [raw-content start end]} embed-ref]
             (delete-and-update input start end)
             (delete-and-update input start end)
@@ -2568,16 +2613,14 @@
            (= (util/nth-safe value (dec current-pos)) (state/get-editor-command-trigger)))
            (= (util/nth-safe value (dec current-pos)) (state/get-editor-command-trigger)))
       (do
       (do
         (util/stop e)
         (util/stop e)
-        (reset! *slash-caret-pos nil)
-        (reset! *show-commands false)
+        (commands/restore-state)
         (delete-and-update input (dec current-pos) current-pos))
         (delete-and-update input (dec current-pos) current-pos))
 
 
       (and (> current-pos 1)
       (and (> current-pos 1)
            (= (util/nth-safe value (dec current-pos)) commands/angle-bracket))
            (= (util/nth-safe value (dec current-pos)) commands/angle-bracket))
       (do
       (do
         (util/stop e)
         (util/stop e)
-        (reset! *angle-bracket-caret-pos nil)
-        (reset! *show-block-commands false)
+        (commands/restore-state)
         (delete-and-update input (dec current-pos) current-pos))
         (delete-and-update input (dec current-pos) current-pos))
 
 
       ;; pair
       ;; pair
@@ -2595,10 +2638,10 @@
         (commands/delete-pair! id)
         (commands/delete-pair! id)
         (cond
         (cond
           (and (= deleted "[") (state/get-editor-show-page-search?))
           (and (= deleted "[") (state/get-editor-show-page-search?))
-          (state/set-editor-show-page-search! false)
+          (state/clear-editor-action!)
 
 
           (and (= deleted "(") (state/get-editor-show-block-search?))
           (and (= deleted "(") (state/get-editor-show-block-search?))
-          (state/set-editor-show-block-search! false)
+          (state/clear-editor-action!)
 
 
           :else
           :else
           nil))
           nil))
@@ -2606,7 +2649,7 @@
       ;; deleting hashtag
       ;; deleting hashtag
       (and (= deleted "#") (state/get-editor-show-page-search-hashtag?))
       (and (= deleted "#") (state/get-editor-show-page-search-hashtag?))
       (do
       (do
-        (state/set-editor-show-page-search-hashtag! false)
+        (state/clear-editor-action!)
         (delete-and-update input (dec current-pos) current-pos))
         (delete-and-update input (dec current-pos) current-pos))
 
 
       ;; just delete
       ;; just delete
@@ -2635,9 +2678,7 @@
   (fn [e]
   (fn [e]
     (cond
     (cond
       (state/editing?)
       (state/editing?)
-      (when (and (not (state/get-editor-show-input))
-                 (not (state/get-editor-show-date-picker?))
-                 (not (state/get-editor-show-template-search?)))
+      (when-not (state/get-editor-action)
         (util/stop e)
         (util/stop e)
         (indent-outdent (not (= :left direction))))
         (indent-outdent (not (= :left direction))))
 
 
@@ -2688,7 +2729,7 @@
         (and (= key "#")
         (and (= key "#")
              (and (> pos 0)
              (and (> pos 0)
                   (= "#" (util/nth-safe value (dec pos)))))
                   (= "#" (util/nth-safe value (dec pos)))))
-        (state/set-editor-show-page-search-hashtag! false)
+        (state/clear-editor-action!)
 
 
         (and (contains? (set/difference (set (keys reversed-autopair-map))
         (and (contains? (set/difference (set (keys reversed-autopair-map))
                                         #{"`"})
                                         #{"`"})
@@ -2720,7 +2761,7 @@
           (if (= key "#")
           (if (= key "#")
             (state/set-editor-last-pos! (inc (cursor/pos input))) ;; In keydown handler, the `#` is not inserted yet.
             (state/set-editor-last-pos! (inc (cursor/pos input))) ;; In keydown handler, the `#` is not inserted yet.
             (state/set-editor-last-pos! (cursor/pos input)))
             (state/set-editor-last-pos! (cursor/pos input)))
-          (reset! commands/*slash-caret-pos (cursor/get-caret-pos input)))
+          (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}))
 
 
         (let [sym "$"]
         (let [sym "$"]
           (and (= key sym)
           (and (= key sym)
@@ -2759,92 +2800,92 @@
             blank-selected? (string/blank? (util/get-selected-text))
             blank-selected? (string/blank? (util/get-selected-text))
             is-processed? (util/event-is-composing? e true) ;; #3440
             is-processed? (util/event-is-composing? e true) ;; #3440
             non-enter-processed? (and is-processed? ;; #3251
             non-enter-processed? (and is-processed? ;; #3251
-                                      (not= code keycode/enter-code))] ;; #3459
-        (when-not (or (state/get-editor-show-input) non-enter-processed?)
-          (cond
-            (and (not (contains? #{"ArrowDown" "ArrowLeft" "ArrowRight" "ArrowUp"} k))
-                 (not (:editor/show-page-search? @state/state))
-                 (not (:editor/show-page-search-hashtag? @state/state))
-                 (wrapped-by? input "[[" "]]"))
-            (let [orig-pos (cursor/get-caret-pos input)
-                  value (gobj/get input "value")
-                  square-pos (string/last-index-of (subs value 0 (:pos orig-pos)) "[[")
-                  pos (+ square-pos 2)
-                  _ (state/set-editor-last-pos! pos)
-                  pos (assoc orig-pos :pos pos)
-                  command-step (if (= \# (util/nth-safe value (dec square-pos)))
-                                 :editor/search-page-hashtag
-                                 :editor/search-page)]
-              (commands/handle-step [command-step])
-              (reset! commands/*slash-caret-pos pos))
-
-            (and blank-selected?
-                 (contains? keycode/left-square-brackets-keys k)
-                 (= (:key last-key-code) k)
-                 (> current-pos 0)
-                 (not (wrapped-by? input "[[" "]]")))
-            (do
-              (commands/handle-step [:editor/input "[[]]" {:backward-truncate-number 2
-                                                           :backward-pos 2}])
-              (commands/handle-step [:editor/search-page])
-              (reset! commands/*slash-caret-pos (cursor/get-caret-pos input)))
-
-            (and blank-selected?
-                 (contains? keycode/left-paren-keys k)
-                 (= (:key last-key-code) k)
-                 (> current-pos 0)
-                 (not (wrapped-by? input "((" "))")))
-            (do
-              (commands/handle-step [:editor/input "(())" {:backward-truncate-number 2
-                                                           :backward-pos 2}])
-              (commands/handle-step [:editor/search-block :reference])
-              (reset! commands/*slash-caret-pos (cursor/get-caret-pos input)))
-
-            (and (= "〈" c)
-                 (= "《" (util/nth-safe value (dec (dec current-pos))))
-                 (> current-pos 0))
-            (do
-              (commands/handle-step [:editor/input commands/angle-bracket {:last-pattern "《〈"
-                                                                           :backward-pos 0}])
-              (reset! commands/*angle-bracket-caret-pos (cursor/get-caret-pos input))
-              (reset! commands/*show-block-commands true))
-
-            (and (= c " ")
-                 (or (= (util/nth-safe value (dec (dec current-pos))) "#")
-                     (not (state/get-editor-show-page-search?))
-                     (and (state/get-editor-show-page-search?)
-                          (not= (util/nth-safe value current-pos) "]"))))
-            (state/set-editor-show-page-search-hashtag! false)
-
-            (and @*show-commands (not= k (state/get-editor-command-trigger)))
-            (let [matched-commands (get-matched-commands input)]
-              (if (seq matched-commands)
+                                      (not= code keycode/enter-code)) ;; #3459
+            editor-action (state/get-editor-action)]
+        (cond
+          (and (= :commands (state/get-editor-action)) (not= k (state/get-editor-command-trigger)))
+          (let [matched-commands (get-matched-commands input)]
+            (if (seq matched-commands)
+              (reset! commands/*matched-commands matched-commands)
+              (state/clear-editor-action!)))
+
+          (and (= :block-commands editor-action) (not= key-code 188)) ; not <
+          (let [matched-block-commands (get-matched-block-commands input)]
+            (if (seq matched-block-commands)
+              (cond
+                (= key-code 9)       ;tab
                 (do
                 (do
-                  (reset! *show-commands true)
-                  (reset! commands/*matched-commands matched-commands))
-                (reset! *show-commands false)))
-
-            (and @*show-block-commands (not= key-code 188)) ; not <
-            (let [matched-block-commands (get-matched-block-commands input)]
-              (if (seq matched-block-commands)
-                (cond
-                  (= key-code 9)       ;tab
-                  (when @*show-block-commands
-                    (util/stop e)
-                    (insert-command! input-id
-                                     (last (first matched-block-commands))
-                                     format
-                                     {:last-pattern commands/angle-bracket}))
-
-                  :else
-                  (reset! commands/*matched-block-commands matched-block-commands))
-                (reset! *show-block-commands false)))
-
-            (nil? @search-timeout)
-            (close-autocomplete-if-outside input)
+                  (util/stop e)
+                  (insert-command! input-id
+                                   (last (first matched-block-commands))
+                                   format
+                                   {:last-pattern commands/angle-bracket}))
 
 
-            :else
-            nil))
+                :else
+                (reset! commands/*matched-block-commands matched-block-commands))
+              (state/clear-editor-action!)))
+
+          (and (contains? #{:commands :block-commands} (state/get-editor-action))
+               (= c (util/nth-safe value (dec (dec current-pos))) " "))
+          (state/clear-editor-action!)
+
+          (and (state/get-editor-show-page-search-hashtag?)
+               (= c " "))
+          (state/clear-editor-action!)
+
+          :else
+          (when (and (not editor-action) (not non-enter-processed?))
+            (cond
+              (and (not (contains? #{"ArrowDown" "ArrowLeft" "ArrowRight" "ArrowUp"} k))
+                   (wrapped-by? input "[[" "]]"))
+              (let [orig-pos (cursor/get-caret-pos input)
+                    value (gobj/get input "value")
+                    square-pos (string/last-index-of (subs value 0 (:pos orig-pos)) "[[")
+                    pos (+ square-pos 2)
+                    _ (state/set-editor-last-pos! pos)
+                    pos (assoc orig-pos :pos pos)
+                    command-step (if (= \# (util/nth-safe value (dec square-pos)))
+                                   :editor/search-page-hashtag
+                                   :editor/search-page)]
+                (commands/handle-step [command-step])
+                (state/set-editor-action-data! {:pos pos}))
+
+              (and blank-selected?
+                   (contains? keycode/left-square-brackets-keys k)
+                   (= (:key last-key-code) k)
+                   (> current-pos 0)
+                   (not (wrapped-by? input "[[" "]]")))
+              (do
+                (commands/handle-step [:editor/input "[[]]" {:backward-truncate-number 2
+                                                             :backward-pos 2}])
+                (commands/handle-step [:editor/search-page])
+                (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}))
+
+              (and blank-selected?
+                   (contains? keycode/left-paren-keys k)
+                   (= (:key last-key-code) k)
+                   (> current-pos 0)
+                   (not (wrapped-by? input "((" "))")))
+              (do
+                (commands/handle-step [:editor/input "(())" {:backward-truncate-number 2
+                                                             :backward-pos 2}])
+                (commands/handle-step [:editor/search-block :reference])
+                (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}))
+
+              (and (= "〈" c)
+                   (= "《" (util/nth-safe value (dec (dec current-pos))))
+                   (> current-pos 0))
+              (do
+                (commands/handle-step [:editor/input commands/angle-bracket {:last-pattern "《〈"
+                                                                             :backward-pos 0}])
+                (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
+                (state/set-editor-show-block-commands!))
+
+              (nil? @search-timeout)
+              (close-autocomplete-if-outside input)
+
+              :else
+              nil)))
         (when-not (or (= k "Shift") is-processed?)
         (when-not (or (= k "Shift") is-processed?)
           (state/set-last-key-code! {:key-code key-code
           (state/set-last-key-code! {:key-code key-code
                                      :code code
                                      :code code
@@ -2861,7 +2902,7 @@
 (defn editor-on-change!
 (defn editor-on-change!
   [block id search-timeout]
   [block id search-timeout]
   (fn [e]
   (fn [e]
-    (if (state/sub :editor/show-block-search?)
+    (if (= :block-search (state/sub :editor/action))
       (let [timeout 300]
       (let [timeout 300]
         (when @search-timeout
         (when @search-timeout
           (js/clearTimeout @search-timeout))
           (js/clearTimeout @search-timeout))
@@ -2952,14 +2993,18 @@
   "shortcut cut action:
   "shortcut cut action:
   * when in selection mode, cut selected blocks
   * when in selection mode, cut selected blocks
   * when in edit mode with text selected, cut selected text
   * when in edit mode with text selected, cut selected text
-  * otherwise same as delete shortcut"
+  * otherwise nothing need to be handled."
   [e]
   [e]
   (cond
   (cond
     (state/selection?)
     (state/selection?)
     (shortcut-cut-selection e)
     (shortcut-cut-selection e)
 
 
-    (state/editing?)
-    (keydown-backspace-handler true e)))
+    (and (state/editing?) (util/input-text-selected?
+                           (gdom/getElement (state/get-edit-input-id))))
+    (keydown-backspace-handler true e)
+
+    :else
+    nil))
 
 
 (defn delete-selection
 (defn delete-selection
   [e]
   [e]

+ 2 - 1
src/main/frontend/handler/events.cljs

@@ -207,7 +207,8 @@
                            (set block-properties)
                            (set block-properties)
                            (set all-properties))
                            (set all-properties))
         shown-properties (set/intersection (set all-properties) shown-properties)]
         shown-properties (set/intersection (set all-properties) shown-properties)]
-    (state/set-modal! (query-properties-settings block shown-properties all-properties))))
+    (state/set-modal! (query-properties-settings block shown-properties all-properties)
+                      {:center? true})))
 
 
 (defmethod handle :modal/show-cards [_]
 (defmethod handle :modal/show-cards [_]
   (state/set-modal! srs/global-cards {:id :srs
   (state/set-modal! srs/global-cards {:id :srs

+ 29 - 9
src/main/frontend/handler/page.cljs

@@ -74,7 +74,7 @@
    (let [p (common-handler/get-page-default-properties title)
    (let [p (common-handler/get-page-default-properties title)
          ps (merge p properties)
          ps (merge p properties)
          content (page-property/insert-properties format "" ps)
          content (page-property/insert-properties format "" ps)
-         refs (gp-block/get-page-refs-from-properties properties
+         refs (gp-block/get-page-refs-from-properties format properties
                                                       (db/get-db (state/get-current-repo))
                                                       (db/get-db (state/get-current-repo))
                                                       (state/get-date-formatter))]
                                                       (state/get-date-formatter))]
      {:block/uuid (db/new-block-id)
      {:block/uuid (db/new-block-id)
@@ -243,13 +243,21 @@
         (util/replace-ignore-case (str " " old-tag " ") (str " " new-tag " "))
         (util/replace-ignore-case (str " " old-tag " ") (str " " new-tag " "))
         (util/replace-ignore-case (str " " old-tag "$") (str " " new-tag)))))
         (util/replace-ignore-case (str " " old-tag "$") (str " " new-tag)))))
 
 
+(defn- replace-property-ref!
+  [content old-name new-name]
+  (let [new-name (keyword (string/replace (string/lower-case new-name) #"\s+" "-"))
+        old-property (str old-name "::")
+        new-property (str (name new-name) "::")]
+    (util/replace-ignore-case content old-property new-property)))
+
 (defn- replace-old-page!
 (defn- replace-old-page!
   "Unsanitized names"
   "Unsanitized names"
   [content old-name new-name]
   [content old-name new-name]
   (when (and (string? content) (string? old-name) (string? new-name))
   (when (and (string? content) (string? old-name) (string? new-name))
     (-> content
     (-> content
         (replace-page-ref! old-name new-name)
         (replace-page-ref! old-name new-name)
-        (replace-tag-ref! old-name new-name))))
+        (replace-tag-ref! old-name new-name)
+        (replace-property-ref! old-name new-name))))
 
 
 (defn- walk-replace-old-page!
 (defn- walk-replace-old-page!
   "Unsanitized names"
   "Unsanitized names"
@@ -268,6 +276,9 @@
                        new-name
                        new-name
                        (replace-old-page! f old-name new-name))
                        (replace-old-page! f old-name new-name))
 
 
+                     (and (keyword f) (= (name f) old-name))
+                     (keyword (string/replace (string/lower-case new-name) #"\s+" "-"))
+
                      :else
                      :else
                      f))
                      f))
                  form))
                  form))
@@ -312,6 +323,7 @@
 (defn delete!
 (defn delete!
   [page-name ok-handler & {:keys [delete-file?]
   [page-name ok-handler & {:keys [delete-file?]
                            :or {delete-file? true}}]
                            :or {delete-file? true}}]
+  (route-handler/redirect-to-home!)
   (when page-name
   (when page-name
     (when-let [repo (state/get-current-repo)]
     (when-let [repo (state/get-current-repo)]
       (let [page-name (util/page-name-sanity-lc page-name)
       (let [page-name (util/page-name-sanity-lc page-name)
@@ -371,6 +383,7 @@
                                   {: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))
                                    :block/refs (rename-update-block-refs! (:block/refs block) (:db/id page) (:db/id to-page))
                                    :block/path-refs (rename-update-block-refs! (:block/path-refs block) (:db/id page) (:db/id to-page))})))) blocks)
                                    :block/path-refs (rename-update-block-refs! (:block/path-refs block) (:db/id page) (:db/id to-page))})))) blocks)
                       (remove nil?))]
                       (remove nil?))]
@@ -478,7 +491,11 @@
         pages (cons page pages)]
         pages (cons page pages)]
     (doseq [{:block/keys [name original-name]} pages]
     (doseq [{:block/keys [name original-name]} pages]
       (let [old-page-title (or original-name name)
       (let [old-page-title (or original-name name)
-            new-page-title (string/replace old-page-title old-name new-name)
+            ;; only replace one time, for the case that the namespace is a sub-string of the sub-namespace page name
+            ;; Example: has pages [[work]] [[work/worklog]],
+            ;; we want to rename [[work/worklog]] to [[work1/worklog]] when rename [[work]] to [[work1]],
+            ;; but don't rename [[work/worklog]] to [[work1/work1log]]
+            new-page-title (string/replace-first old-page-title old-name new-name)
             redirect? (= name (:block/name page))]
             redirect? (= name (:block/name page))]
         (when (and old-page-title new-page-title)
         (when (and old-page-title new-page-title)
           (p/let [_ (rename-page-aux old-page-title new-page-title redirect?)]
           (p/let [_ (rename-page-aux old-page-title new-page-title redirect?)]
@@ -555,7 +572,8 @@
           (rename-namespace-pages! repo old-name new-name))
           (rename-namespace-pages! repo old-name new-name))
         (rename-nested-pages old-name new-name))
         (rename-nested-pages old-name new-name))
       (when (string/blank? new-name)
       (when (string/blank? new-name)
-        (notification/show! "Please use a valid name, empty name is not allowed!" :error)))))
+        (notification/show! "Please use a valid name, empty name is not allowed!" :error)))
+    (ui-handler/re-render-root!)))
 
 
 (defn- split-col-by-element
 (defn- split-col-by-element
   [col element]
   [col element]
@@ -668,7 +686,7 @@
 ;; Editor
 ;; Editor
 (defn page-not-exists-handler
 (defn page-not-exists-handler
   [input id q current-pos]
   [input id q current-pos]
-  (state/set-editor-show-page-search! false)
+  (state/clear-editor-action!)
   (if (state/org-mode-file-link? (state/get-current-repo))
   (if (state/org-mode-file-link? (state/get-current-repo))
     (let [page-ref-text (get-page-ref-text q)
     (let [page-ref-text (get-page-ref-text q)
           value (gobj/get input "value")
           value (gobj/get input "value")
@@ -689,15 +707,17 @@
   [input id _q pos format]
   [input id _q pos format]
   (let [current-pos (cursor/pos input)
   (let [current-pos (cursor/pos input)
         edit-content (state/sub [:editor/content id])
         edit-content (state/sub [:editor/content id])
+        action (state/get-editor-action)
+        hashtag? (= action :page-search-hashtag)
         q (or
         q (or
            @editor-handler/*selected-text
            @editor-handler/*selected-text
-           (when (state/sub :editor/show-page-search-hashtag?)
+           (when hashtag?
              (gp-util/safe-subs edit-content pos current-pos))
              (gp-util/safe-subs edit-content pos current-pos))
            (when (> (count edit-content) current-pos)
            (when (> (count edit-content) current-pos)
              (gp-util/safe-subs edit-content pos current-pos)))]
              (gp-util/safe-subs edit-content pos current-pos)))]
-    (if (state/sub :editor/show-page-search-hashtag?)
+    (if hashtag?
       (fn [chosen _click?]
       (fn [chosen _click?]
-        (state/set-editor-show-page-search! false)
+        (state/clear-editor-action!)
         (let [wrapped? (= "[[" (gp-util/safe-subs edit-content (- pos 2) pos))
         (let [wrapped? (= "[[" (gp-util/safe-subs edit-content (- pos 2) pos))
               chosen (if (string/starts-with? chosen "New page: ") ;; FIXME: What if a page named "New page: XXX"?
               chosen (if (string/starts-with? chosen "New page: ") ;; FIXME: What if a page named "New page: XXX"?
                        (subs chosen 10)
                        (subs chosen 10)
@@ -719,7 +739,7 @@
                                            :end-pattern (when wrapped? "]]")
                                            :end-pattern (when wrapped? "]]")
                                            :forward-pos forward-pos})))
                                            :forward-pos forward-pos})))
       (fn [chosen _click?]
       (fn [chosen _click?]
-        (state/set-editor-show-page-search! false)
+        (state/clear-editor-action!)
         (let [chosen (if (string/starts-with? chosen "New page: ")
         (let [chosen (if (string/starts-with? chosen "New page: ")
                        (subs chosen 10)
                        (subs chosen 10)
                        chosen)
                        chosen)

+ 33 - 5
src/main/frontend/search.cljs

@@ -3,6 +3,7 @@
             [clojure.string :as string]
             [clojure.string :as string]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.config :as gp-config]
             [frontend.db :as db]
             [frontend.db :as db]
+            [frontend.db.model :as db-model]
             [frontend.regex :as regex]
             [frontend.regex :as regex]
             [frontend.search.browser :as search-browser]
             [frontend.search.browser :as search-browser]
             [frontend.search.db :as search-db :refer [indices]]
             [frontend.search.db :as search-db :refer [indices]]
@@ -155,11 +156,38 @@
   ([q]
   ([q]
    (template-search q 10))
    (template-search q 10))
   ([q limit]
   ([q limit]
-   (let [q (clean-str q)
-         templates (db/get-all-templates)]
-     (when (seq templates)
-       (let [result (fuzzy-search (keys templates) q :limit limit)]
-         (vec (select-keys templates result)))))))
+   (when q
+     (let [q (clean-str q)
+           templates (db/get-all-templates)]
+       (when (seq templates)
+         (let [result (fuzzy-search (keys templates) q :limit limit)]
+           (vec (select-keys templates result))))))))
+
+(defn property-search
+  ([q]
+   (property-search q 10))
+  ([q limit]
+   (when q
+     (let [q (clean-str q)
+           properties (map name (db-model/get-all-properties))]
+       (when (seq properties)
+         (if (string/blank? q)
+           properties
+           (let [result (fuzzy-search properties q :limit limit)]
+             (vec result))))))))
+
+(defn property-value-search
+  ([property q]
+   (property-value-search property q 10))
+  ([property q limit]
+   (when q
+     (let [q (clean-str q)
+           result (db-model/get-property-values (keyword property))]
+       (when (seq result)
+         (if (string/blank? q)
+           result
+           (let [result (fuzzy-search result q :limit limit)]
+             (vec result))))))))
 
 
 (defn sync-search-indice!
 (defn sync-search-indice!
   [repo tx-report]
   [repo tx-report]

+ 38 - 46
src/main/frontend/state.cljs

@@ -97,12 +97,9 @@
      :config                                {}
      :config                                {}
      :block/component-editing-mode?         false
      :block/component-editing-mode?         false
      :editor/draw-mode?                     false
      :editor/draw-mode?                     false
-     :editor/show-page-search?              false
-     :editor/show-page-search-hashtag?      false
-     :editor/show-date-picker?              false
+     :editor/action                         nil
+     :editor/action-data                    nil
      ;; With label or other data
      ;; With label or other data
-     :editor/show-input                     nil
-     :editor/show-zotero                    false
      :editor/last-saved-cursor              nil
      :editor/last-saved-cursor              nil
      :editor/editing?                       nil
      :editor/editing?                       nil
      :editor/in-composition?                false
      :editor/in-composition?                false
@@ -598,63 +595,58 @@
   [value]
   [value]
   (set-state! :search/mode value))
   (set-state! :search/mode value))
 
 
-(defn set-editor-show-page-search!
+(defn set-editor-action!
   [value]
   [value]
-  (set-state! :editor/show-page-search? value))
+  (set-state! :editor/action value))
+
+(defn set-editor-action-data!
+  [value]
+  (set-state! :editor/action-data value))
+
+(defn get-editor-action
+  []
+  (:editor/action @state))
+
+(defn get-editor-action-data
+  []
+  (:editor/action-data @state))
 
 
 (defn get-editor-show-page-search?
 (defn get-editor-show-page-search?
   []
   []
-  (get @state :editor/show-page-search?))
+  (= (get-editor-action) :page-search))
 
 
-(defn set-editor-show-page-search-hashtag!
-  [value]
-  (set-state! :editor/show-page-search? value)
-  (set-state! :editor/show-page-search-hashtag? value))
 (defn get-editor-show-page-search-hashtag?
 (defn get-editor-show-page-search-hashtag?
   []
   []
-  (get @state :editor/show-page-search-hashtag?))
-(defn set-editor-show-block-search!
-  [value]
-  (set-state! :editor/show-block-search? value))
+  (= (get-editor-action) :page-search-hashtag))
+
 (defn get-editor-show-block-search?
 (defn get-editor-show-block-search?
   []
   []
-  (get @state :editor/show-block-search?))
-(defn set-editor-show-template-search!
-  [value]
-  (set-state! :editor/show-template-search? value))
-(defn get-editor-show-template-search?
-  []
-  (get @state :editor/show-template-search?))
-(defn set-editor-show-date-picker!
-  [value]
-  (set-state! :editor/show-date-picker? value))
-(defn get-editor-show-date-picker?
-  []
-  (get @state :editor/show-date-picker?))
+  (= (get-editor-action) :block-search))
+
 (defn set-editor-show-input!
 (defn set-editor-show-input!
   [value]
   [value]
-  (set-state! :editor/show-input value))
+  (if value
+    (do
+      (set-editor-action-data! (assoc (get-editor-action-data) :options value))
+      (set-editor-action! :input))
+    (do
+      (set-editor-action! nil)
+      (set-editor-action-data! nil))))
 (defn get-editor-show-input
 (defn get-editor-show-input
   []
   []
-  (get @state :editor/show-input))
-
-
-(defn set-editor-show-zotero!
-  [value]
-  (set-state! :editor/show-zotero value))
+  (when (= (get-editor-action) :input)
+    (get @state :editor/action-data)))
+(defn set-editor-show-commands!
+  []
+  (when-not (get-editor-action) (set-editor-action! :commands)))
+(defn set-editor-show-block-commands!
+  []
+  (when-not (get-editor-action) (set-editor-action! :block-commands)))
 
 
-;; TODO: refactor, use one state
-(defn clear-editor-show-state!
+(defn clear-editor-action!
   []
   []
   (swap! state (fn [state]
   (swap! state (fn [state]
-                 (assoc state
-                        :editor/show-input nil
-                        :editor/show-zotero false
-                        :editor/show-date-picker? false
-                        :editor/show-block-search? false
-                        :editor/show-template-search? false
-                        :editor/show-page-search? false
-                        :editor/show-page-search-hashtag? false))))
+                 (assoc state :editor/action nil))))
 
 
 (defn set-edit-input-id!
 (defn set-edit-input-id!
   [input-id]
   [input-id]

+ 19 - 35
src/main/frontend/ui.cljs

@@ -25,7 +25,7 @@
             ["react-tippy" :as react-tippy]
             ["react-tippy" :as react-tippy]
             ["react-transition-group" :refer [CSSTransition TransitionGroup]]
             ["react-transition-group" :refer [CSSTransition TransitionGroup]]
             ["@logseq/react-tweet-embed" :as react-tweet-embed]
             ["@logseq/react-tweet-embed" :as react-tweet-embed]
-            ["react-visibility-sensor" :as rvs]
+            ["react-intersection-observer" :as react-intersection-observer]
             [rum.core :as rum]
             [rum.core :as rum]
             [frontend.db-mixins :as db-mixins]
             [frontend.db-mixins :as db-mixins]
             [frontend.mobile.util :as mobile-util]
             [frontend.mobile.util :as mobile-util]
@@ -38,7 +38,7 @@
 (def resize-consumer (r/adapt-class (gobj/get Resize "ResizeConsumer")))
 (def resize-consumer (r/adapt-class (gobj/get Resize "ResizeConsumer")))
 (def Tippy (r/adapt-class (gobj/get react-tippy "Tooltip")))
 (def Tippy (r/adapt-class (gobj/get react-tippy "Tooltip")))
 (def ReactTweetEmbed (r/adapt-class react-tweet-embed))
 (def ReactTweetEmbed (r/adapt-class react-tweet-embed))
-(def visibility-sensor (r/adapt-class (gobj/get rvs "default")))
+(def useInView (gobj/get react-intersection-observer "useInView"))
 
 
 (defn reset-ios-whole-page-offset!
 (defn reset-ios-whole-page-offset!
   []
   []
@@ -65,10 +65,7 @@
                             (plugin-handler/hook-plugin-editor :input-selection-end (bean/->js e)))))))
                             (plugin-handler/hook-plugin-editor :input-selection-end (bean/->js e)))))))
                 state)}
                 state)}
   [{:keys [on-change] :as props}]
   [{:keys [on-change] :as props}]
-  (let [skip-composition? (or
-                           (state/sub :editor/show-page-search?)
-                           (state/sub :editor/show-block-search?)
-                           (state/sub :editor/show-template-search?))
+  (let [skip-composition? (state/sub :editor/action)
         on-composition (fn [e]
         on-composition (fn [e]
                          (if skip-composition?
                          (if skip-composition?
                            (on-change e)
                            (on-change e)
@@ -400,11 +397,13 @@
            get-group-name
            get-group-name
            empty-placeholder
            empty-placeholder
            item-render
            item-render
-           class]}]
+           class
+           header]}]
   (let [current-idx (get state ::current-idx)]
   (let [current-idx (get state ::current-idx)]
     [:div#ui__ac {:class class}
     [:div#ui__ac {:class class}
      (if (seq matched)
      (if (seq matched)
        [:div#ui__ac-inner.hide-scrollbar
        [:div#ui__ac-inner.hide-scrollbar
+        (when header header)
         (for [[idx item] (medley/indexed matched)]
         (for [[idx item] (medley/indexed matched)]
           [:<>
           [:<>
            {:key idx}
            {:key idx}
@@ -901,13 +900,10 @@
      label-right]]
      label-right]]
    (progress-bar width)])
    (progress-bar width)])
 
 
-(rum/defcs lazy-visible-inner < rum/reactive
-  {:init (fn [state]
-           (assoc state
-                  ::ref (atom nil)))}
-  [state visible? content-fn]
+(rum/defcs lazy-visible-inner
+  [state visible? content-fn ref]
   [:div.lazy-visibility
   [:div.lazy-visibility
-   {:ref #(reset! (::ref state) %)
+   {:ref ref
     :style {:min-height 24}}
     :style {:min-height 24}}
    (if visible?
    (if visible?
      (when (fn? content-fn)
      (when (fn? content-fn)
@@ -925,25 +921,13 @@
           [:div.h-2.bg-base-4.rounded.col-span-1]]
           [:div.h-2.bg-base-4.rounded.col-span-1]]
          [:div.h-2.bg-base-4.rounded]]]]])])
          [:div.h-2.bg-base-4.rounded]]]]])])
 
 
-(rum/defcs lazy-visible <
-  (rum/local false ::visible?)
-  (rum/local true ::active?)
-  [state content-fn sensor-opts {:keys [once?]}]
-  (let [*active? (::active? state)]
-    (if (or (util/mobile?) (mobile-util/native-platform?))
-      (content-fn)
-      (let [*visible? (::visible? state)]
-        (visibility-sensor
-         (merge
-          {:on-change (fn [v]
-                        (reset! *visible? v)
-                        (when (and once? v)
-                          (reset! *active? false)))
-           :partialVisibility true
-           :offset {:top -300
-                    :bottom -300}
-           :scrollCheck true
-           :scrollThrottle 500
-           :active @*active?}
-          sensor-opts)
-         (lazy-visible-inner @*visible? content-fn))))))
+(rum/defc lazy-visible
+  [content-fn]
+  (let [[hasBeenSeen setHasBeenSeen] (rum/use-state false)
+        inViewState (useInView #js {:rootMargin "100px"
+                                    :onChange (fn [v entry]
+                                                (let [self-top (.-top (.-boundingClientRect entry))
+                                                      v (if v v (if (> self-top 0) false true))]
+                                                  (setHasBeenSeen v)))})
+        ref (.-ref inViewState)]
+    (lazy-visible-inner hasBeenSeen content-fn ref)))

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

@@ -312,6 +312,11 @@
   (when input
   (when input
     (.-selectionEnd input)))
     (.-selectionEnd input)))
 
 
+(defn input-text-selected?
+  [input]
+  (not= (get-selection-start input)
+        (get-selection-end input)))
+
 (defn get-selection-direction
 (defn get-selection-direction
   [input]
   [input]
   (when input
   (when input

+ 4 - 3
src/main/frontend/util/cursor.cljs

@@ -111,9 +111,10 @@
 (defn beginning-of-line?
 (defn beginning-of-line?
   [input]
   [input]
   (let [[content pos] (get-input-content&pos input)]
   (let [[content pos] (get-input-content&pos input)]
-    (or (zero? pos)
-        (when-let [pre-char (subs content (dec pos) pos)]
-          (= pre-char \newline)))))
+    (when content
+      (or (zero? pos)
+         (when-let [pre-char (subs content (dec pos) pos)]
+           (= pre-char \newline))))))
 
 
 (defn move-cursor-to-line-end
 (defn move-cursor-to-line-end
   [input]
   [input]

+ 10 - 8
src/main/frontend/util/text.cljs

@@ -86,19 +86,21 @@
 
 
 (defn get-string-all-indexes
 (defn get-string-all-indexes
   "Get all indexes of `value` in the string `s`."
   "Get all indexes of `value` in the string `s`."
-  [s value]
-  (loop [acc []
-         i 0]
-    (if-let [i (string/index-of s value i)]
-      (recur (conj acc i) (+ i (count value)))
-      acc)))
+  [s value {:keys [before?] :or {before? true}}]
+  (if (= value "")
+    (if before? [0] [(dec (count s))])
+    (loop [acc []
+          i 0]
+     (if-let [i (string/index-of s value i)]
+       (recur (conj acc i) (+ i (count value)))
+       acc))))
 
 
 (defn wrapped-by?
 (defn wrapped-by?
   "`pos` must be wrapped by `before` and `and` in string `value`, e.g. ((a|b))"
   "`pos` must be wrapped by `before` and `and` in string `value`, e.g. ((a|b))"
   [value pos before end]
   [value pos before end]
-  (let [before-matches (->> (get-string-all-indexes value before)
+  (let [before-matches (->> (get-string-all-indexes value before {:before? true})
                             (map (fn [i] [i :before])))
                             (map (fn [i] [i :before])))
-        end-matches (->> (get-string-all-indexes value end)
+        end-matches (->> (get-string-all-indexes value end {:before? false})
                          (map (fn [i] [i :end])))
                          (map (fn [i] [i :end])))
         indexes (sort-by first (concat before-matches end-matches [[pos :between]]))
         indexes (sort-by first (concat before-matches end-matches [[pos :between]]))
         ks (map second indexes)
         ks (map second indexes)

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

@@ -1,3 +1,3 @@
 (ns frontend.version)
 (ns frontend.version)
 
 
-(defonce version "0.7.5")
+(defonce version "0.7.6")

+ 1 - 1
src/test/frontend/db/query_dsl_test.cljs

@@ -394,7 +394,7 @@ tags: other
                 (dsl-query "(or [[tag2]] [[page 3]])")))
                 (dsl-query "(or [[tag2]] [[page 3]])")))
         "OR query with nonexistent page should return meaningful results")
         "OR query with nonexistent page should return meaningful results")
 
 
-    (is (= ["foo:: bar\n" "b1 [[page 1]] #tag2" "b3"]
+    (is (= ["b1 [[page 1]] #tag2" "foo:: bar\n" "b3"]
            (->> (dsl-query "(not [[page 2]])")
            (->> (dsl-query "(not [[page 2]])")
                 ;; Only filter to page1 to get meaningful results
                 ;; Only filter to page1 to get meaningful results
                 (filter #(= "page1" (get-in % [:block/page :block/name])))
                 (filter #(= "page1" (get-in % [:block/page :block/name])))

+ 8 - 1
src/test/frontend/extensions/calc_test.cljc

@@ -101,7 +101,14 @@
       0.0  "acos(cos(0))"
       0.0  "acos(cos(0))"
       5.0  "2 * log(10) + 3"
       5.0  "2 * log(10) + 3"
       1.0  "-2 * log(10) + 3"
       1.0  "-2 * log(10) + 3"
-      10.0 "ln(1) + 10")))
+      10.0 "ln(1) + 10"))
+  (testing "avoiding rounding errors"
+    (are [value expr] (= value (run expr))
+      3.3 "1.1 + 2.2"
+      2.2 "3.3 - 1.1"
+      0.0001 "1/10000"
+      1e-7 "1/10000000"
+      )))
 
 
 (deftest variables
 (deftest variables
   (testing "variables can be remembered"
   (testing "variables can be remembered"

+ 3 - 3
src/test/frontend/util/text_test.cljs

@@ -23,11 +23,11 @@
 (deftest get-string-all-indexes
 (deftest get-string-all-indexes
   []
   []
   (are [x y] (= x y)
   (are [x y] (= x y)
-    (text-util/get-string-all-indexes "[[hello]] [[world]]" "[[")
+    (text-util/get-string-all-indexes "[[hello]] [[world]]" "[[" {})
     [0 10]
     [0 10]
 
 
-    (text-util/get-string-all-indexes "abc abc ab" "ab")
+    (text-util/get-string-all-indexes "abc abc ab" "ab" {})
     [0 4 8]
     [0 4 8]
 
 
-    (text-util/get-string-all-indexes "a.c a.c ab" "a.")
+    (text-util/get-string-all-indexes "a.c a.c ab" "a." {})
     [0 4]))
     [0 4]))

+ 13 - 8
yarn.lock

@@ -6623,7 +6623,7 @@ prompts@^2.3.2:
     kleur "^3.0.3"
     kleur "^3.0.3"
     sisteransi "^1.0.5"
     sisteransi "^1.0.5"
 
 
[email protected], prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
[email protected], prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.2:
   version "15.8.1"
   version "15.8.1"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
   integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
   integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -6811,6 +6811,11 @@ [email protected]:
     react-draggable "3.x"
     react-draggable "3.x"
     react-resizable "1.x"
     react-resizable "1.x"
 
 
[email protected]:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.0.tgz#a196e33fdf1e7aaa1fda3aefbb68bdad9e82a79d"
+  integrity sha512-9wwKJa2LB8ujtJB5MAXYYEM7JfYThZTj0YnfGxzLLWkifaLIGc7iTde2EpJ7ka5MjneRHnlxbIn5VV9k2WjUVA==
+
 react-icon-base@^2.1.2:
 react-icon-base@^2.1.2:
   version "2.1.2"
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.2.tgz#a17101dad9c1192652356096860a9ab43a0766c7"
   resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.2.tgz#a17101dad9c1192652356096860a9ab43a0766c7"
@@ -6820,6 +6825,13 @@ [email protected]:
   version "2.2.7"
   version "2.2.7"
   resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-2.2.7.tgz#d7860826b258557510dac10680abea5ca23cf650"
   resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-2.2.7.tgz#d7860826b258557510dac10680abea5ca23cf650"
   integrity sha512-0n4lcGqzJFcIQLoQytLdJCE0DKSA9dkwEZRYoGrIDJZFvIT6Hbajx5mv9geqhqFiNjUgtxg8kPyDfjlhymbGFg==
   integrity sha512-0n4lcGqzJFcIQLoQytLdJCE0DKSA9dkwEZRYoGrIDJZFvIT6Hbajx5mv9geqhqFiNjUgtxg8kPyDfjlhymbGFg==
+  dependencies:
+    react-icon-base "2.1.0"
+
+react-intersection-observer@^9.3.5:
+  version "9.3.5"
+  resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.3.5.tgz#df97584c1ef1549a47d4af6380db2fb4b76d7bba"
+  integrity sha512-TiJXVUapzAaIrZCAMBLjyWvwGYNGm0Xpkcwm3NY23b9PsJEBavul0hRFmrwc/LOmBUA/8TlkjCj7lCvjM0q1Hg==
 
 
 react-is@^16.13.1, react-is@^16.3.1, react-is@^16.7.0:
 react-is@^16.13.1, react-is@^16.3.1, react-is@^16.7.0:
   version "16.13.1"
   version "16.13.1"
@@ -6880,13 +6892,6 @@ [email protected]:
     loose-envify "^1.4.0"
     loose-envify "^1.4.0"
     prop-types "^15.6.2"
     prop-types "^15.6.2"
 
 
-react-visibility-sensor@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/react-visibility-sensor/-/react-visibility-sensor-5.1.1.tgz#5238380960d3a0b2be0b7faddff38541e337f5a9"
-  integrity sha512-cTUHqIK+zDYpeK19rzW6zF9YfT4486TIgizZW53wEZ+/GPBbK7cNS0EHyJVyHYacwFEvvHLEKfgJndbemWhB/w==
-  dependencies:
-    prop-types "^15.7.2"
-
 [email protected]:
 [email protected]:
   version "17.0.2"
   version "17.0.2"
   resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
   resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"