瀏覽代碼

Merge branch 'master' into feat/tweet-shape

Konstantinos Kaloutas 2 年之前
父節點
當前提交
7f471e865f
共有 46 個文件被更改,包括 650 次插入187 次删除
  1. 3 1
      .clj-kondo/config.edn
  2. 6 1
      README.md
  3. 2 2
      android/app/build.gradle
  4. 1 1
      deps.edn
  5. 4 0
      deps/graph-parser/.carve/ignore
  6. 1 1
      deps/graph-parser/src/logseq/graph_parser.cljs
  7. 1 1
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  8. 1 1
      deps/graph-parser/src/logseq/graph_parser/extract.cljc
  9. 8 2
      deps/graph-parser/src/logseq/graph_parser/text.cljs
  10. 12 1
      deps/graph-parser/src/logseq/graph_parser/util.cljs
  11. 26 8
      docs/dev-practices.md
  12. 87 0
      e2e-tests/editor.spec.ts
  13. 1 1
      e2e-tests/whiteboards.spec.ts
  14. 4 4
      ios/App/App.xcodeproj/project.pbxproj
  15. 1 1
      libs/src/LSPlugin.ts
  16. 1 1
      resources/package.json
  17. 57 32
      src/electron/electron/handler.cljs
  18. 4 2
      src/electron/electron/search.cljs
  19. 12 3
      src/main/frontend/components/block.cljs
  20. 118 0
      src/main/frontend/components/bug_report.cljs
  21. 7 0
      src/main/frontend/components/bug_report.css
  22. 2 3
      src/main/frontend/components/header.cljs
  23. 3 1
      src/main/frontend/components/query_table.cljs
  24. 2 0
      src/main/frontend/components/search.cljs
  25. 5 1
      src/main/frontend/components/sidebar.css
  26. 9 3
      src/main/frontend/db/query_custom.cljs
  27. 1 2
      src/main/frontend/db/utils.cljs
  28. 33 33
      src/main/frontend/extensions/pdf/assets.cljs
  29. 62 44
      src/main/frontend/extensions/pdf/highlights.cljs
  30. 1 0
      src/main/frontend/extensions/pdf/utils.cljs
  31. 3 2
      src/main/frontend/fs/sync.cljs
  32. 4 4
      src/main/frontend/handler/editor.cljs
  33. 5 1
      src/main/frontend/handler/editor/keyboards.cljs
  34. 16 17
      src/main/frontend/handler/file_sync.cljs
  35. 33 1
      src/main/frontend/handler/page.cljs
  36. 1 2
      src/main/frontend/handler/repo.cljs
  37. 1 0
      src/main/frontend/handler/search.cljs
  38. 1 1
      src/main/frontend/handler/user.cljs
  39. 3 0
      src/main/frontend/publishing.cljs
  40. 75 0
      src/main/frontend/pubsub.cljc
  41. 11 2
      src/main/frontend/routes.cljs
  42. 3 0
      src/main/frontend/search.cljs
  43. 4 5
      src/main/frontend/util.cljc
  44. 1 1
      src/main/frontend/version.cljs
  45. 12 0
      src/test/frontend/db/query_custom_test.cljs
  46. 2 1
      src/test/frontend/extensions/pdf/assets_test.cljs

+ 3 - 1
.clj-kondo/config.edn

@@ -22,7 +22,9 @@
                              frontend.util/node-path.dirname
                              frontend.util/node-path.dirname
                              frontend.util/node-path.join
                              frontend.util/node-path.join
                              frontend.util/node-path.extname
                              frontend.util/node-path.extname
-                             frontend.util/node-path.name]}
+                             frontend.util/node-path.name
+                             ;; frontend.pubsub/def-mult-or-pub generate vars clj-kondo cannot resolve
+                             frontend.pubsub]}
 
 
   :consistent-alias
   :consistent-alias
   {:aliases {cljs.reader reader
   {:aliases {cljs.reader reader

+ 6 - 1
README.md

@@ -90,7 +90,12 @@ There are more guides in [docs/](docs/), e.g. the [Guide for contributing to tra
 ## How to contribute with a PR
 ## How to contribute with a PR
 If you would like to contribute by solving an open issue, please fork this repository and then create a branch for the fix.
 If you would like to contribute by solving an open issue, please fork this repository and then create a branch for the fix.
 
 
-Once you push your code to your fork, you'll be able to open a PR into Logseq repository. For more info you can follow this guide from [GitHub docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork)
+Once you push your code to your fork, you'll be able to open a PR into Logseq repository. For more info you can follow this guide from [GitHub docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). 
+
+Enabling "allow edits from maintainers" for PR is highly appreciated!
+
+There's a nice [project board](https://github.com/orgs/logseq/projects/5/views/1?pane=info
+) listing items that easy for contributors to catch-up
 
 
 And here a list of some [good first issues](https://github.com/logseq/logseq/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)!
 And here a list of some [good first issues](https://github.com/logseq/logseq/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)!
 
 

+ 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 49
-        versionName "0.8.15"
+        versionCode 50
+        versionName "0.8.16"
         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.

+ 1 - 1
deps.edn

@@ -33,7 +33,7 @@
  :aliases {:cljs {:extra-paths ["src/dev-cljs/" "src/test/" "src/electron/"]
  :aliases {:cljs {:extra-paths ["src/dev-cljs/" "src/test/" "src/electron/"]
                   :extra-deps  {org.clojure/clojurescript        {:mvn/version "1.11.54"}
                   :extra-deps  {org.clojure/clojurescript        {:mvn/version "1.11.54"}
                                 org.clojure/tools.namespace      {:mvn/version "0.2.11"}
                                 org.clojure/tools.namespace      {:mvn/version "0.2.11"}
-                                cider/cider-nrepl                {:mvn/version "0.28.4"}
+                                cider/cider-nrepl                {:mvn/version "0.29.0"}
                                 org.clojars.knubie/cljs-run-test {:mvn/version "1.0.1"}}
                                 org.clojars.knubie/cljs-run-test {:mvn/version "1.0.1"}}
                   :main-opts   ["-m" "shadow.cljs.devtools.cli"]}
                   :main-opts   ["-m" "shadow.cljs.devtools.cli"]}
 
 

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

@@ -38,3 +38,7 @@ logseq.graph-parser/get-blocks-to-delete
 logseq.graph-parser.property/colons-org
 logseq.graph-parser.property/colons-org
 ;; API
 ;; API
 logseq.graph-parser.util.db/resolve-input
 logseq.graph-parser.util.db/resolve-input
+;; TODO: use fast-remove-nils instead
+logseq.graph-parser.util/remove-nils
+;; API
+logseq.graph-parser.text/get-file-basename

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

@@ -122,7 +122,7 @@ Options available:
                          new?
                          new?
                          ;; TODO: use file system timestamp?
                          ;; TODO: use file system timestamp?
                          (assoc :file/created-at (date-time-util/time-ms)))])
                          (assoc :file/created-at (date-time-util/time-ms)))])
-        tx' (gp-util/remove-nils tx)
+        tx' (gp-util/fast-remove-nils tx)
         result (if skip-db-transact?
         result (if skip-db-transact?
                  tx'
                  tx'
                  (d/transact! conn tx' (select-keys options [:new-graph? :from-disk?])))]
                  (d/transact! conn tx' (select-keys options [:new-graph? :from-disk?])))]

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

@@ -376,7 +376,7 @@
   [blocks]
   [blocks]
   (map (fn [block]
   (map (fn [block]
          (if (map? block)
          (if (map? block)
-           (block-keywordize (gp-util/remove-nils block))
+           (block-keywordize (gp-util/remove-nils-non-nested block))
            block))
            block))
        blocks))
        blocks))
 
 

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

@@ -109,7 +109,7 @@
         invalid-properties (set (->> (map (comp name first) *invalid-properties)
         invalid-properties (set (->> (map (comp name first) *invalid-properties)
                                      (concat invalid-properties)))
                                      (concat invalid-properties)))
         page-m (->
         page-m (->
-                (gp-util/remove-nils
+                (gp-util/remove-nils-non-nested
                  (assoc
                  (assoc
                   (gp-block/page-name->map page false db true date-formatter
                   (gp-block/page-name->map page false db true date-formatter
                                            :from-page from-page)
                                            :from-page from-page)

+ 8 - 2
deps/graph-parser/src/logseq/graph_parser/text.cljs

@@ -10,9 +10,15 @@
             [logseq.graph-parser.util.page-ref :as page-ref]))
             [logseq.graph-parser.util.page-ref :as page-ref]))
 
 
 (defn get-file-basename
 (defn get-file-basename
+  "Returns the basename of a file path. e.g. /a/b/c.md -> c.md"
+  [path]
+  (when-not (string/blank? path)
+    (.-base (path/parse (string/replace path "+" "/")))))
+
+(defn get-file-rootname
+  "Returns the rootname of a file path. e.g. /a/b/c.md -> c"
   [path]
   [path]
   (when-not (string/blank? path)
   (when-not (string/blank? path)
-    ;; Same as util/node-path.name
     (.-name (path/parse (string/replace path "+" "/")))))
     (.-name (path/parse (string/replace path "+" "/")))))
 
 
 (def page-ref-re-0 #"\[\[(.*)\]\]")
 (def page-ref-re-0 #"\[\[(.*)\]\]")
@@ -28,7 +34,7 @@
        (or (when-let [[_ label _path] (re-matches markdown-page-ref-re s)]
        (or (when-let [[_ label _path] (re-matches markdown-page-ref-re s)]
              (string/trim label))
              (string/trim label))
            (when-let [[_ path _label] (re-matches org-page-ref-re s)]
            (when-let [[_ path _label] (re-matches org-page-ref-re s)]
-             (some-> (get-file-basename path)
+             (some-> (get-file-rootname path)
                      (string/replace "." "/")))
                      (string/replace "." "/")))
            (-> (re-matches page-ref-re-0 s)
            (-> (re-matches page-ref-re-0 s)
                second))))
                second))))

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

@@ -28,7 +28,8 @@
   (.normalize s "NFC"))
   (.normalize s "NFC"))
 
 
 (defn remove-nils
 (defn remove-nils
-  "remove pairs of key-value that has nil value from a (possibly nested) map."
+  "remove pairs of key-value that has nil value from a (possibly nested) map or
+  coll of maps."
   [nm]
   [nm]
   (walk/postwalk
   (walk/postwalk
    (fn [el]
    (fn [el]
@@ -37,6 +38,16 @@
        el))
        el))
    nm))
    nm))
 
 
+(defn remove-nils-non-nested
+  "remove pairs of key-value that has nil value from a map (nested not supported)."
+  [nm]
+  (into {} (remove (comp nil? second)) nm))
+
+(defn fast-remove-nils
+  "remove pairs of key-value that has nil value from a coll of maps."
+  [nm]
+  (keep (fn [m] (if (map? m) (remove-nils-non-nested m) m)) nm))
+
 (defn split-first [pattern s]
 (defn split-first [pattern s]
   (when-let [first-index (string/index-of s pattern)]
   (when-let [first-index (string/index-of s pattern)]
     [(subs s 0 first-index)
     [(subs s 0 first-index)

+ 26 - 8
docs/dev-practices.md

@@ -11,8 +11,8 @@ this section, run `bb dev:lint`.
 ### Clojure code
 ### Clojure code
 
 
 To lint:
 To lint:
-```
-clojure -M:clj-kondo --lint src
+```sh
+clojure -M:clj-kondo --parallel --lint src --cache false
 ```
 ```
 
 
 We lint our Clojure(Script) code with https://github.com/clj-kondo/clj-kondo/. If you need to configure specific linters, see [this documentation](https://github.com/clj-kondo/clj-kondo/blob/master/doc/linters.md). Where possible, a global linting configuration is used and namespace specific configuration is avoided.
 We lint our Clojure(Script) code with https://github.com/clj-kondo/clj-kondo/. If you need to configure specific linters, see [this documentation](https://github.com/clj-kondo/clj-kondo/blob/master/doc/linters.md). Where possible, a global linting configuration is used and namespace specific configuration is avoided.
@@ -27,7 +27,7 @@ There are outstanding linting items that are currently ignored to allow linting
 We use https://github.com/borkdude/carve to detect unused vars in our codebase.
 We use https://github.com/borkdude/carve to detect unused vars in our codebase.
 
 
 To run this linter:
 To run this linter:
-```
+```sh
 bb lint:carve
 bb lint:carve
 ```
 ```
 
 
@@ -35,7 +35,7 @@ By default, the script runs in CI mode which prints unused vars if they are
 found. The script can be run in an interactive mode which prompts for keeping
 found. The script can be run in an interactive mode which prompts for keeping
 (ignoring) an unused var or removing it. Run this mode with:
 (ignoring) an unused var or removing it. Run this mode with:
 
 
-```
+```sh
 bb lint:carve '{:interactive true}'
 bb lint:carve '{:interactive true}'
 ```
 ```
 
 
@@ -46,7 +46,7 @@ why a var is ignored to help others understand why it's unused.
 
 
 Large vars have a lot of complexity and make it hard for the team to maintain
 Large vars have a lot of complexity and make it hard for the team to maintain
 and understand them. To run this linter:
 and understand them. To run this linter:
-```
+```sh
 bb lint:large-vars
 bb lint:large-vars
 ```
 ```
 
 
@@ -55,7 +55,7 @@ To configure the linter, see the `[:tasks/config :large-vars]` path of bb.edn.
 ### Document namespaces
 ### Document namespaces
 
 
 Documentation helps teams share their knowledge and enables more individuals to contribute to the codebase. Documenting our namespaces is a good first step to improving our documentation. To run this linter:
 Documentation helps teams share their knowledge and enables more individuals to contribute to the codebase. Documenting our namespaces is a good first step to improving our documentation. To run this linter:
-```
+```sh
 bb lint:ns-docstrings
 bb lint:ns-docstrings
 ```
 ```
 
 
@@ -83,7 +83,7 @@ We have unit and end to end tests.
 
 
 To run end to end tests
 To run end to end tests
 
 
-``` bash
+```sh
 yarn electron-watch
 yarn electron-watch
 # in another shell
 # in another shell
 yarn e2e-test # or npx playwright test
 yarn e2e-test # or npx playwright test
@@ -91,8 +91,9 @@ yarn e2e-test # or npx playwright test
 
 
 If e2e failed after first running:
 If e2e failed after first running:
 - `rm -rdf ~/.logseq`
 - `rm -rdf ~/.logseq`
+- `rm -rdf ~/.config/Logseq`
 - `rm -rdf <repo dir>/tmp/`  
 - `rm -rdf <repo dir>/tmp/`  
-- `rm -rdf <appData dir>/Electron`  (Reference: https://www.electronjs.org/de/docs/latest/api/app#appgetpathname)
+- Windows: `rmdir /s %APPDATA%/Electron`  (Reference: https://www.electronjs.org/de/docs/latest/api/app#appgetpathname)
 
 
 If e2e tests fail, they can be debugged by examining a trace dump with [the
 If e2e tests fail, they can be debugged by examining a trace dump with [the
 playwright trace
 playwright trace
@@ -212,3 +213,20 @@ Currently the codebase is not formatted/indented consistently. We loosely follow
 There are some babashka tasks under `nbb:` which are useful for inspecting
 There are some babashka tasks under `nbb:` which are useful for inspecting
 database changes in realtime. See [these
 database changes in realtime. See [these
 docs](https://github.com/logseq/bb-tasks#logseqbb-tasksnbbwatch) for more info.
 docs](https://github.com/logseq/bb-tasks#logseqbb-tasksnbbwatch) for more info.
+
+## FAQ
+
+If dev app launch failed after electron upgrade:
+```sh
+yarn
+yarn watch
+```
+In another window:
+```sh
+cd static
+yarn
+cd ..
+yarn dev-electron-app
+``` 
+and kill all electron process
+Then a normal start happens via `yarn dev-electron-app`

+ 87 - 0
e2e-tests/editor.spec.ts

@@ -596,3 +596,90 @@ test('should not erase typed text when expanding block quickly after typing #389
     ''
     ''
   )
   )
 })
 })
+
+test('should keep correct undo and redo seq after indenting or outdenting the block #7615',async({page,block}) => {
+  await createRandomPage(page)
+
+  await block.mustFill("foo")
+  
+  await page.keyboard.press("Enter")
+  await expect(page.locator('textarea >> nth=0')).toHaveText("")
+  await block.indent()
+  await block.mustFill("bar")
+  await expect(page.locator('textarea >> nth=0')).toHaveText("bar")
+
+  if (IsMac) {
+    await page.keyboard.press('Meta+z')
+  } else {
+    await page.keyboard.press('Control+z')
+  }
+  // should undo "bar" input
+  await expect(page.locator('textarea >> nth=0')).toHaveText("")
+  if (IsMac) {
+    await page.keyboard.press('Shift+Meta+z')
+  } else {
+    await page.keyboard.press('Shift+Control+z')
+  }
+  // should redo "bar" input
+  await expect(page.locator('textarea >> nth=0')).toHaveText("bar")
+  await page.keyboard.press("Shift+Tab")
+  
+  await page.keyboard.press("Enter")
+  await expect(page.locator('textarea >> nth=0')).toHaveText("")
+  // swap input seq
+  await block.mustFill("baz")
+  await block.indent()
+
+  if (IsMac) {
+    await page.keyboard.press('Meta+z')
+  } else {
+    await page.keyboard.press('Control+z')
+  }
+  // should undo indention
+  await expect(page.locator('textarea >> nth=0')).toHaveText("baz")
+  await page.keyboard.press("Shift+Tab")
+
+  await page.keyboard.press("Enter")
+  await expect(page.locator('textarea >> nth=0')).toHaveText("")
+  // #7615
+  await page.keyboard.type("aaa")
+  await block.indent()
+  await page.keyboard.type(" bbb")
+  await expect(page.locator('textarea >> nth=0')).toHaveText("aaa bbb")
+  if (IsMac) {
+    await page.keyboard.press('Meta+z')
+  } else {
+    await page.keyboard.press('Control+z')
+  }
+  await expect(page.locator('textarea >> nth=0')).toHaveText("aaa")
+  if (IsMac) {
+    await page.keyboard.press('Meta+z')
+  } else {
+    await page.keyboard.press('Control+z')
+  }
+  await expect(page.locator('textarea >> nth=0')).toHaveText("aaa")
+  if (IsMac) {
+    await page.keyboard.press('Meta+z')
+  } else {
+    await page.keyboard.press('Control+z')
+  }
+  await expect(page.locator('textarea >> nth=0')).toHaveText("")
+  if (IsMac) {
+    await page.keyboard.press('Shift+Meta+z')
+  } else {
+    await page.keyboard.press('Shift+Control+z')
+  }
+  await expect(page.locator('textarea >> nth=0')).toHaveText("aaa")
+  if (IsMac) {
+    await page.keyboard.press('Shift+Meta+z')
+  } else {
+    await page.keyboard.press('Shift+Control+z')
+  }
+  await expect(page.locator('textarea >> nth=0')).toHaveText("aaa")
+  if (IsMac) {
+    await page.keyboard.press('Shift+Meta+z')
+  } else {
+    await page.keyboard.press('Shift+Control+z')
+  }
+  await expect(page.locator('textarea >> nth=0')).toHaveText("aaa bbb")
+})

+ 1 - 1
e2e-tests/whiteboards.spec.ts

@@ -105,7 +105,7 @@ test('zoom out', async ({ page }) => {
   await page.keyboard.press('Shift+0')
   await page.keyboard.press('Shift+0')
   await page.waitForTimeout(1500)
   await page.waitForTimeout(1500)
   await page.click('#tl-zoom-out')
   await page.click('#tl-zoom-out')
-  await expect(page.locator('#tl-zoom')).toContainText('100%')
+  await expect(page.locator('#tl-zoom')).toContainText('80%')
 })
 })
 
 
 test('open context menu', async ({ page }) => {
 test('open context menu', async ({ page }) => {

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

@@ -515,7 +515,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.8.15;
+				MARKETING_VERSION = 0.8.16;
 				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)";
@@ -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.8.15;
+				MARKETING_VERSION = 0.8.16;
 				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 = "";
@@ -567,7 +567,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.8.15;
+				MARKETING_VERSION = 0.8.16;
 				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;
@@ -594,7 +594,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.8.15;
+				MARKETING_VERSION = 0.8.16;
 				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)";

+ 1 - 1
libs/src/LSPlugin.ts

@@ -317,7 +317,7 @@ export interface IPluginSearchServiceHooks {
 
 
   onIndiceInit: (graph: string) => Promise<SearchIndiceInitStatus>
   onIndiceInit: (graph: string) => Promise<SearchIndiceInitStatus>
   onIndiceReset: (graph: string) => Promise<void>
   onIndiceReset: (graph: string) => Promise<void>
-  onBlocksChanged: (graph: string, changes: { added: Array<SearchBlockItem>, removed: Array<BlockEntity> }) => Promise<void>
+  onBlocksChanged: (graph: string, changes: { added: Array<SearchBlockItem>, removed: Array<EntityID> }) => Promise<void>
   onGraphRemoved: (graph: string, opts?: {}) => Promise<any>
   onGraphRemoved: (graph: string, opts?: {}) => Promise<any>
 }
 }
 
 

+ 1 - 1
resources/package.json

@@ -1,7 +1,7 @@
 {
 {
   "name": "Logseq",
   "name": "Logseq",
   "productName": "Logseq",
   "productName": "Logseq",
-  "version": "0.8.15",
+  "version": "0.8.16",
   "main": "electron.js",
   "main": "electron.js",
   "author": "Logseq",
   "author": "Logseq",
   "license": "AGPL-3.0",
   "license": "AGPL-3.0",

+ 57 - 32
src/electron/electron/handler.cljs

@@ -1,34 +1,34 @@
 (ns electron.handler
 (ns electron.handler
   "This ns starts the event handling for the electron main process and defines
   "This ns starts the event handling for the electron main process and defines
   all the application-specific event types"
   all the application-specific event types"
-  (:require ["electron" :refer [ipcMain dialog app autoUpdater shell]]
-            [cljs-bean.core :as bean]
-            ["fs" :as fs]
+  (:require ["/electron/utils" :as js-utils]
+            ["abort-controller" :as AbortController]
             ["buffer" :as buffer]
             ["buffer" :as buffer]
+            ["diff-match-patch" :as google-diff]
+            ["electron" :refer [app autoUpdater dialog ipcMain shell]]
+            ["fs" :as fs]
             ["fs-extra" :as fs-extra]
             ["fs-extra" :as fs-extra]
-            ["path" :as path]
             ["os" :as os]
             ["os" :as os]
-            ["diff-match-patch" :as google-diff]
-            ["/electron/utils" :as js-utils]
-            ["abort-controller" :as AbortController]
-            [electron.shell :as shell]
-            [electron.fs-watcher :as watcher]
-            [electron.configs :as cfgs]
-            [promesa.core :as p]
-            [clojure.string :as string]
-            [electron.utils :as utils]
-            [electron.logger :as logger]
-            [electron.state :as state]
+            ["path" :as path]
+            [cljs-bean.core :as bean]
+            [cljs.reader :as reader]
             [clojure.core.async :as async]
             [clojure.core.async :as async]
-            [electron.search :as search]
+            [clojure.string :as string]
+            [electron.backup-file :as backup-file]
+            [electron.configs :as cfgs]
+            [electron.file-sync-rsapi :as rsapi]
+            [electron.find-in-page :as find]
+            [electron.fs-watcher :as watcher]
             [electron.git :as git]
             [electron.git :as git]
+            [electron.logger :as logger]
             [electron.plugin :as plugin]
             [electron.plugin :as plugin]
-            [electron.window :as win]
-            [electron.file-sync-rsapi :as rsapi]
-            [electron.backup-file :as backup-file]
-            [cljs.reader :as reader]
+            [electron.search :as search]
             [electron.server :as server]
             [electron.server :as server]
-            [electron.find-in-page :as find]))
+            [electron.shell :as shell]
+            [electron.state :as state]
+            [electron.utils :as utils]
+            [electron.window :as win]
+            [promesa.core :as p]))
 
 
 (defmulti handle (fn [_window args] (keyword (first args))))
 (defmulti handle (fn [_window args] (keyword (first args))))
 
 
@@ -38,21 +38,24 @@
 (defmethod handle :mkdir-recur [_window [_ dir]]
 (defmethod handle :mkdir-recur [_window [_ dir]]
   (fs/mkdirSync dir #js {:recursive true}))
   (fs/mkdirSync dir #js {:recursive true}))
 
 
-;; {encoding: 'utf8', withFileTypes: true}
 (defn- readdir
 (defn- readdir
-  [dir]
+  "Read directory recursively, return all filenames"
+  [root-dir]
   (->> (tree-seq
   (->> (tree-seq
-        (fn [^js fpath]
-          (.isDirectory (fs/statSync fpath)))
-        (fn [dir]
-          (let [files (fs/readdirSync dir (clj->js {:withFileTypes true}))]
+        (fn [[is-dir _fpath]]
+          is-dir)
+        (fn [[_is-dir dir]]
+          (let [files (fs/readdirSync dir #js {:withFileTypes true})]
             (->> files
             (->> files
                  (remove #(.isSymbolicLink ^js %))
                  (remove #(.isSymbolicLink ^js %))
                  (remove #(string/starts-with? (.-name ^js %) "."))
                  (remove #(string/starts-with? (.-name ^js %) "."))
-                 (map #(.join path dir (.-name %))))))
-        dir)
+                 (map #(do
+                         [(.isDirectory %)
+                          (.join path dir (.-name %))])))))
+        [true root-dir])
+       (filter (complement first))
+       (map second)
        (map utils/fix-win-path!)
        (map utils/fix-win-path!)
-       (doall)
        (vec)))
        (vec)))
 
 
 (defmethod handle :readdir [_window [_ dir]]
 (defmethod handle :readdir [_window [_ dir]]
@@ -174,12 +177,34 @@
           result (get (js->clj result) "filePaths")]
           result (get (js->clj result) "filePaths")]
     (p/resolved (first result))))
     (p/resolved (first result))))
 
 
-(defmethod handle :openDir [^js _window _messages]
+(defn- pretty-print-js-error
+  "Converts file related JS Error messages to a human readable format.
+   Ex.:
+   Error: EACCES: permission denied, scandir '/tmp/test'
+   Permission denied for path: '/tmp/test' (Code: EACCES)"
+  [e]
+  (some->>
+   e
+   str
+   ;; Message parsed as "Error: $ERROR_CODE$: $REASON$, function $PATH$"
+   (re-matches #"(?:Error\: )(.+)(?:\: )(.+)(?:, \w+ )('.+')")
+   rest
+   (#(str (string/capitalize (second %)) " for path: " (nth % 2) " (Code: " (first %) ")"))))
+
+(defmethod handle :openDir [^js window _messages]
   (logger/info ::open-dir "open folder selection dialog")
   (logger/info ::open-dir "open folder selection dialog")
   (p/let [path (open-dir-dialog)]
   (p/let [path (open-dir-dialog)]
     (logger/debug ::open-dir {:path path})
     (logger/debug ::open-dir {:path path})
     (if path
     (if path
-      (p/resolved (bean/->js (get-files path)))
+      (try
+        (p/resolved (bean/->js (get-files path)))
+        (catch js/Error e 
+          (do
+            (utils/send-to-renderer window "notification" {:type "error"
+                                                           :payload (str "Opening the specified directory failed.\n"
+                                                                         (or (pretty-print-js-error e) (str "Unexpected error: " e)))})
+            (p/rejected e))))
+
       (p/rejected (js/Error "path empty")))))
       (p/rejected (js/Error "path empty")))))
 
 
 (defmethod handle :getFiles [_window [_ path]]
 (defmethod handle :getFiles [_window [_ path]]

+ 4 - 2
src/electron/electron/search.cljs

@@ -169,7 +169,8 @@
   [repo pages]
   [repo pages]
   (if-let [db (get-db repo)]
   (if-let [db (get-db repo)]
     ;; TODO: what if a CONFLICT on uuid
     ;; TODO: what if a CONFLICT on uuid
-    (let [insert (prepare db "INSERT INTO pages (id, uuid, content) VALUES (@id, @uuid, @content) ON CONFLICT (id) DO UPDATE SET content = @content")
+    ;; Should update all values on id conflict
+    (let [insert (prepare db "INSERT INTO pages (id, uuid, content) VALUES (@id, @uuid, @content) ON CONFLICT (id) DO UPDATE SET (uuid, content) = (@uuid, @content)")
           insert-many (.transaction ^object db
           insert-many (.transaction ^object db
                                     (fn [pages]
                                     (fn [pages]
                                       (doseq [page pages]
                                       (doseq [page pages]
@@ -190,7 +191,8 @@
   [repo blocks]
   [repo blocks]
   (if-let [db (get-db repo)]
   (if-let [db (get-db repo)]
     ;; TODO: what if a CONFLICT on uuid
     ;; TODO: what if a CONFLICT on uuid
-    (let [insert (prepare db "INSERT INTO blocks (id, uuid, content, page) VALUES (@id, @uuid, @content, @page) ON CONFLICT (id) DO UPDATE SET content = @content")
+    ;; Should update all values on id conflict
+    (let [insert (prepare db "INSERT INTO blocks (id, uuid, content, page) VALUES (@id, @uuid, @content, @page) ON CONFLICT (id) DO UPDATE SET (uuid, content, page) = (@uuid, @content, @page)")
           insert-many (.transaction ^object db
           insert-many (.transaction ^object db
                                     (fn [blocks]
                                     (fn [blocks]
                                       (doseq [block blocks]
                                       (doseq [block blocks]

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

@@ -2611,14 +2611,23 @@
    (editor-handler/unhighlight-blocks!)))
    (editor-handler/unhighlight-blocks!)))
 
 
 (defn- block-drop
 (defn- block-drop
-  [event uuid target-block *move-to]
+  [^js event uuid target-block *move-to]
   (util/stop event)
   (util/stop event)
   (when-not (dnd-same-block? uuid)
   (when-not (dnd-same-block? uuid)
     (let [block-uuids (state/get-selection-block-ids)
     (let [block-uuids (state/get-selection-block-ids)
           lookup-refs (map (fn [id] [:block/uuid id]) block-uuids)
           lookup-refs (map (fn [id] [:block/uuid id]) block-uuids)
           selected (db/pull-many (state/get-current-repo) '[*] lookup-refs)
           selected (db/pull-many (state/get-current-repo) '[*] lookup-refs)
-          blocks (if (seq selected) selected [@*dragging-block])]
-      (dnd/move-blocks event blocks target-block @*move-to)))
+          blocks (if (seq selected) selected [@*dragging-block])
+          blocks (remove-nils blocks)]
+      (if-not (seq blocks)
+        (when-let [text (.getData (.-dataTransfer event) "text/plain")]
+          (editor-handler/api-insert-new-block!
+           text
+           {:block-uuid  uuid
+            :edit-block? false
+            :sibling?    (= @*move-to :sibling)
+            :before?     (= @*move-to :top)}))
+        (dnd/move-blocks event blocks target-block @*move-to))))
   (block-drag-end event *move-to))
   (block-drag-end event *move-to))
 
 
 (defn- block-mouse-over
 (defn- block-mouse-over

+ 118 - 0
src/main/frontend/components/bug_report.cljs

@@ -0,0 +1,118 @@
+(ns frontend.components.bug-report
+  (:require [rum.core :as rum]
+            [frontend.ui :as ui]
+            [frontend.components.header :as header]
+            [frontend.util :as util]
+            [reitit.frontend.easy :as rfe]
+            [clojure.string :as string]
+            [frontend.handler.notification :as notification]))
+
+(defn parse-clipboard-data-transfer
+  "parse dataTransfer
+
+   input: dataTransfer
+
+   output: {:types {:type :data} :items {:kind :type} :files {:name :size :type}}"
+  [data]
+  (let [items (.-items data)
+        types (.-types data)
+        files (.-files data)]
+    (conj
+     {:items (->> items
+                  (map (fn [item] {:kind (.-kind item) :type (.-type item)}))
+                  (conj))}
+     {:types (->> types
+                  (map (fn [type] {:type type :data (.getData data type)}))
+                  (conj))}
+     {:files (->> files
+                  (map (fn [file] {:name (.-name file) :type (.-type file) :size (.-size file)}))
+                  (conj))})))
+
+(rum/defc clipboard-data-inspector
+  "bug report tool for clipboard"
+  []
+  (let [[result set-result!] (rum/use-state {})
+        [step set-step!] (rum/use-state 0)
+        paste-handler! (fn [e]
+                         (let [clipboard-data (.-clipboardData e)
+                               result (parse-clipboard-data-transfer clipboard-data)
+                               result (into {} result)]
+                           (set-result! result)
+                           (set-step! 1)))
+
+        copy-result-to-clipboard! (fn [result]
+                                    (util/copy-to-clipboard! result)
+                                    (notification/show! "Copied to clipboard!"))
+
+        reset-step! (fn []
+                      (set-step! 0)
+                      (set-result! {}))]
+
+    (rum/use-effect!
+     (fn []
+       (cond (= step 0) (js/addEventListener "paste" paste-handler!))
+       (fn [] (cond (= step 0) (js/removeEventListener "paste" paste-handler!))))
+     [step]) ;; when step === 0
+
+    [:div.flex.flex-col
+     (when (= step 0)
+       (list [:div.mx-auto "Press Ctrl+V / ⌘+V to inspect your clipboard data"]
+             [:div.mx-auto "or click here to paste if you are using the mobile version"]
+             ;; for mobile
+             [:input.form-input.is-large.transition.duration-150.ease-in-out {:type "text" :placeholder "Long press here to paste if you are on mobile"}]
+             [:div.flex.justify-between.items-center.mt-2
+              [:div "Something wrong? No problem, click to go back to the previous step."]
+              (ui/button "Go back" :on-click #(util/open-url (rfe/href :bug-report)))]))
+
+     (when (= step 1)
+       (list
+        [:div "Here is the data read from clipboard."]
+        [:div.flex.justify-between.items-center.mt-2
+         [:div "If this is okay to share, click the copy button."]
+         (ui/button "Copy the result" :on-click #(copy-result-to-clipboard! (js/JSON.stringify (clj->js result) nil 2)))]
+        [:div.flex.justify-between.items-center.mt-2
+         [:div "Now you can report the result pasted to your clipboard. Please paste the result in the 'Additional Context' section and state where you copied the original content from. Thanks!"]
+         (ui/button "Create an issue" :href header/bug-report-url)]
+        [:div.flex.justify-between.items-center.mt-2
+         [:div "Something wrong? No problem, click to go back to the previous step."]
+         (ui/button "Go back" :on-click reset-step!)]
+
+        [:pre.whitespace-pre-wrap [:code (js/JSON.stringify (clj->js result) nil 2)]]))]))
+
+(rum/defc bug-report-tool-route
+  [route-match]
+  (let [name (get-in route-match [:parameters :path :tool])]
+    [:div.flex.flex-col ;; container
+     [:h1.text-2xl.mx-auto.mb-4 (ui/icon "clipboard") " " (-> name (string/replace #"-" " ") (string/capitalize))]
+     (cond ;; TODO any fallback?
+       (= name "clipboard-data-inspector")
+       (clipboard-data-inspector))]))
+
+(rum/defc report-item-button
+  [title description icon-name {:keys [on-click]}]
+   [:a.cp__bug-report-item-button.flex.items-center.px-4.py-2.my-2.rounded-lg {:on-click on-click}
+    [(ui/icon icon-name)
+     [:div.flex.flex-col.ml-2
+      [:div title]
+      [:div.opacity-60 description]]]])
+
+(rum/defc bug-report
+  []
+  [:div.flex.flex-col
+   [:div.flex.flex-col.items-center
+    [:div.flex.items-center.mb-2
+     (ui/icon "bug")
+     [:h1.text-3xl.ml-2 "Bug report"]]
+    [:div.opacity-60 "Can you help us out by submitting a bug report? We'll get it sorted out as soon as we can."]]
+   [:div.cp__bug-report-reporter.rounded-lg.p-8.mt-8
+    [:h1.text-2xl "Is the bug you encountered related to these features?"]
+    [:div.opacity-60 "You can use these handy tools to give us additional information."]
+    (report-item-button "Clipboard helper"
+                 "Inspect and collect clipboard data"
+                 "clipboard"
+                 {:on-click #(util/open-url (rfe/href :bug-report-tools {:tool "clipboard-data-inspector"}))})
+    [:div.py-2] ;; divider
+    [:div.flex.flex-col
+     [:h1.text-2xl "Or..."]
+     [:div.opacity-60 "If there are no tools available for you to gather additional information, please report the bug directly."]
+     (report-item-button "Submit a bug report" "Help Make Logseq Better!" "message-report" {:on-click #(util/open-url header/bug-report-url)})]]])

+ 7 - 0
src/main/frontend/components/bug_report.css

@@ -0,0 +1,7 @@
+.cp__bug-report-reporter {
+  background-color: var(--ls-tertiary-background-color);
+}
+
+.cp__bug-report-item-button {
+  background-color: var(--ls-quaternary-background-color);
+}

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

@@ -70,6 +70,7 @@
     (str "https://github.com/logseq/logseq/issues/new?"
     (str "https://github.com/logseq/logseq/issues/new?"
          "title=&"
          "title=&"
          "template=bug_report.yaml&"
          "template=bug_report.yaml&"
+         "labels=from:in-app&"
          "platform="
          "platform="
          (js/encodeURIComponent platform))))
          (js/encodeURIComponent platform))))
 
 
@@ -120,9 +121,7 @@
 
 
        {:title [:div.flex-row.flex.justify-between.items-center
        {:title [:div.flex-row.flex.justify-between.items-center
                 [:span (t :help/bug)]]
                 [:span (t :help/bug)]]
-        :options {:href bug-report-url
-                  :title "Fire a bug report on Github"
-                  :target "_blank"}
+        :options {:href (rfe/href :bug-report)}
         :icon (ui/icon "bug")}
         :icon (ui/icon "bug")}
 
 
        (when (and (state/sub :auth/id-token) (user-handler/logged-in?))
        (when (and (state/sub :auth/id-token) (user-handler/logged-in?))

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

@@ -170,7 +170,9 @@
                               [:string (when-let [updated-at (:block/updated-at item)]
                               [:string (when-let [updated-at (:block/updated-at item)]
                                          (date/int->local-time-2 updated-at))]
                                          (date/int->local-time-2 updated-at))]
 
 
-                              [:string (get-in item [:block/properties column])])]
+                              [:string (or (get-in item [:block/properties-text-values column])
+                                           ;; Fallback to property relationships for page blocks
+                                           (get-in item [:block/properties column]))])]
                   [:td.whitespace-nowrap {:on-mouse-down (fn [] (reset! select? false))
                   [:td.whitespace-nowrap {:on-mouse-down (fn [] (reset! select? false))
                                           :on-mouse-move (fn [] (reset! select? true))
                                           :on-mouse-move (fn [] (reset! select? true))
                                           :on-mouse-up (fn []
                                           :on-mouse-up (fn []

+ 2 - 0
src/main/frontend/components/search.cljs

@@ -331,6 +331,8 @@
        nil)]))
        nil)]))
 
 
 (rum/defc search-auto-complete
 (rum/defc search-auto-complete
+  "has-more? - if the result is truncated
+   all? - if true, in show-more mode"
   [{:keys [engine pages files pages-content blocks has-more?] :as result} search-q all?]
   [{:keys [engine pages files pages-content blocks has-more?] :as result} search-q all?]
   (let [pages (when-not all? (map (fn [page]
   (let [pages (when-not all? (map (fn [page]
                                     (let [alias (model/get-redirect-page-name page)]
                                     (let [alias (model/get-redirect-page-name page)]

+ 5 - 1
src/main/frontend/components/sidebar.css

@@ -223,9 +223,13 @@
         padding: 0;
         padding: 0;
         margin: 0;
         margin: 0;
 
 
+        li {
+          margin: 0;
+        }
+        
         a {
         a {
           width: 100%;
           width: 100%;
-          padding: 2px 24px;
+          padding: 4px 24px;
           transition: background-color .3s;
           transition: background-color .3s;
 
 
           .page-title {
           .page-title {

+ 9 - 3
src/main/frontend/db/query_custom.cljs

@@ -28,7 +28,7 @@
   (let [{:keys [where in]} (datalog-util/query-vec->map query)
   (let [{:keys [where in]} (datalog-util/query-vec->map query)
         rules-found (datalog-util/find-rules-in-where where (-> rules/query-dsl-rules keys set))]
         rules-found (datalog-util/find-rules-in-where where (-> rules/query-dsl-rules keys set))]
     (if (seq rules-found)
     (if (seq rules-found)
-      (if (= '% (last in))
+      (if (and (= '% (last in)) (vector? (last (:inputs query-m))))
         ;; Add to existing :inputs rules
         ;; Add to existing :inputs rules
         (update query-m
         (update query-m
                 :inputs
                 :inputs
@@ -46,9 +46,15 @@
             (update :query
             (update :query
                     (fn [q]
                     (fn [q]
                       (if (contains? (set q) :in)
                       (if (contains? (set q) :in)
-                        (datalog-util/add-to-end-of-query-section q :in ['%])
+                        ;; only add '% if not already present
+                        (if (not (contains? (set q) '%))
+                          (datalog-util/add-to-end-of-query-section q :in ['%])
+                          q)
                         (into q [:in '$ '%]))))
                         (into q [:in '$ '%]))))
-            (assoc :rules (mapv rules/query-dsl-rules rules-found))))
+            (update :rules
+                    (fn [rules]
+                      (into (or rules [])
+                            (mapv rules/query-dsl-rules rules-found))))))
       query-m)))
       query-m)))
 
 
 (defn custom-query
 (defn custom-query

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

@@ -93,8 +93,7 @@
    (transact! repo-url tx-data nil))
    (transact! repo-url tx-data nil))
   ([repo-url tx-data tx-meta]
   ([repo-url tx-data tx-meta]
    (when-not config/publishing?
    (when-not config/publishing?
-     (let [tx-data (->> (gp-util/remove-nils tx-data)
-                        (remove nil?))]
+     (let [tx-data (gp-util/fast-remove-nils tx-data)]
        (when (seq tx-data)
        (when (seq tx-data)
          (when-let [conn (conn/get-db repo-url false)]
          (when-let [conn (conn/get-db repo-url false)]
            (if tx-meta
            (if tx-meta

+ 33 - 33
src/main/frontend/extensions/pdf/assets.cljs

@@ -29,21 +29,20 @@
 
 
 (defn inflate-asset
 (defn inflate-asset
   [original-path]
   [original-path]
-  (let [filename (util/node-path.basename original-path)
+  (let [filename  (util/node-path.basename original-path)
         web-link? (string/starts-with? original-path "http")
         web-link? (string/starts-with? original-path "http")
-        ext-name (util/get-file-ext filename)
-        url (assets-handler/normalize-asset-resource-url original-path)]
-    (when-let [key
-               (if web-link?
-                 (str (hash url))
-                 (and
-                   (= ext-name "pdf")
-                   (subs filename 0 (- (count filename) 4))))]
-      {:key      key
-       :identity (subs key (- (count key) 15))
-       :filename filename
-       :url      url
-       :hls-file (str "assets/" key ".edn")
+        ext-name  (util/get-file-ext filename)
+        url       (assets-handler/normalize-asset-resource-url original-path)
+        filekey   (util/safe-sanitize-file-name (subs filename 0 (- (count filename) (inc (count ext-name)))))]
+    (when-let [key (and (not (string/blank? filekey))
+                        (if web-link?
+                          (str filekey "__" (hash url)) filekey))]
+
+      {:key           key
+       :identity      (subs key (- (count key) 15))
+       :filename      filename
+       :url           url
+       :hls-file      (str "assets/" key ".edn")
        :original-path original-path})))
        :original-path original-path})))
 
 
 (defn resolve-area-image-file
 (defn resolve-area-image-file
@@ -184,25 +183,26 @@
   ([pdf hl] (ensure-ref-block! pdf hl nil))
   ([pdf hl] (ensure-ref-block! pdf hl nil))
   ([pdf-current {:keys [id content page properties]} insert-opts]
   ([pdf-current {:keys [id content page properties]} insert-opts]
    (when-let [ref-page (and pdf-current (resolve-ref-page pdf-current))]
    (when-let [ref-page (and pdf-current (resolve-ref-page pdf-current))]
-     (if-let [ref-block (db-model/query-block-by-uuid id)]
-       (do
-         (println "[existed ref block]" ref-block)
-         ref-block)
-       (let [text       (:text content)
-             wrap-props #(if-let [stamp (:image content)]
-                           (assoc % :hl-type "area" :hl-stamp stamp) %)]
-
-         (when (string? text)
-           (editor-handler/api-insert-new-block!
-            text (merge {:page        (:block/name ref-page)
-                         :custom-uuid id
-                         :properties  (wrap-props
-                                       {:ls-type  "annotation"
-                                        :hl-page  page
-                                        :hl-color (:color properties)
-                                        ;; force custom uuid
-                                        :id       (str id)})}
-                        insert-opts))))))))
+     (let [ref-block (db-model/query-block-by-uuid id)]
+       (if-not (nil? (:block/content ref-block))
+         (do
+           (println "[existed ref block]" ref-block)
+           ref-block)
+         (let [text       (:text content)
+               wrap-props #(if-let [stamp (:image content)]
+                             (assoc % :hl-type "area" :hl-stamp stamp) %)]
+
+           (when (string? text)
+             (editor-handler/api-insert-new-block!
+              text (merge {:page        (:block/name ref-page)
+                           :custom-uuid id
+                           :properties  (wrap-props
+                                         {:ls-type  "annotation"
+                                          :hl-page  page
+                                          :hl-color (:color properties)
+                                          ;; force custom uuid
+                                          :id       (str id)})}
+                          insert-opts)))))))))
 
 
 (defn del-ref-block!
 (defn del-ref-block!
   [{:keys [id]}]
   [{:keys [id]}]

+ 62 - 44
src/main/frontend/extensions/pdf/highlights.cljs

@@ -2,6 +2,7 @@
   (:require [cljs-bean.core :as bean]
   (:require [cljs-bean.core :as bean]
             [clojure.string :as string]
             [clojure.string :as string]
             [frontend.components.svg :as svg]
             [frontend.components.svg :as svg]
+            [frontend.components.block :as block]
             [frontend.context.i18n :refer [t]]
             [frontend.context.i18n :refer [t]]
             [frontend.extensions.pdf.assets :as pdf-assets]
             [frontend.extensions.pdf.assets :as pdf-assets]
             [frontend.extensions.pdf.utils :as pdf-utils]
             [frontend.extensions.pdf.utils :as pdf-utils]
@@ -93,11 +94,11 @@
   "The contextual menu which appears over a text selection and allows e.g. creating a highlight."
   "The contextual menu which appears over a text selection and allows e.g. creating a highlight."
   [^js viewer
   [^js viewer
    {:keys [highlight point ^js selection]}
    {:keys [highlight point ^js selection]}
-   {:keys [clear-ctx-tip! add-hl! upd-hl! del-hl!]}]
+   {:keys [clear-ctx-menu! add-hl! upd-hl! del-hl!]}]
 
 
   (rum/use-effect!
   (rum/use-effect!
    (fn []
    (fn []
-     (let [cb #(clear-ctx-tip!)]
+     (let [cb #(clear-ctx-menu!)]
        (js/setTimeout #(js/document.addEventListener "click" cb))
        (js/setTimeout #(js/document.addEventListener "click" cb))
        #(js/document.removeEventListener "click" cb)))
        #(js/document.removeEventListener "click" cb)))
    [])
    [])
@@ -158,7 +159,7 @@
 
 
                               (reset! *highlight-last-color (keyword action)))))
                               (reset! *highlight-last-color (keyword action)))))
 
 
-                        (and clear? (js/setTimeout #(clear-ctx-tip!) 68))))]
+                        (and clear? (js/setTimeout #(clear-ctx-menu!) 68))))]
 
 
     (rum/use-effect!
     (rum/use-effect!
      (fn []
      (fn []
@@ -208,43 +209,58 @@
      ]))
      ]))
 
 
 (rum/defc pdf-highlights-text-region
 (rum/defc pdf-highlights-text-region
-  [^js viewer vw-hl hl
-   {:keys [show-ctx-tip!]}]
+  [^js viewer vw-hl hl {:keys [show-ctx-menu!]}]
 
 
-  (let [{:keys [rects]} (:position vw-hl)
+  (let [{:keys [id]} hl
+        {:keys [rects]} (:position vw-hl)
         {:keys [color]} (:properties hl)
         {:keys [color]} (:properties hl)
-        open-tip! (fn [^js/MouseEvent e]
-                    (.preventDefault e)
-                    (let [x (.-clientX e)
-                          y (.-clientY e)]
 
 
-                      (show-ctx-tip! viewer hl {:x x :y y})))]
+        open-ctx-menu!
+        (fn [^js/MouseEvent e]
+          (.preventDefault e)
+          (let [x (.-clientX e)
+                y (.-clientY e)]
+
+            (show-ctx-menu! viewer hl {:x x :y y})))
+
+        dragstart-handle!
+        (fn [^js e]
+          (when-let [^js dt (and id (.-dataTransfer e))]
+            (reset! block/*dragging? true)
+            (pdf-assets/ensure-ref-block! (state/get-current-pdf) hl)
+            (.setData dt "text/plain" (str "((" id "))"))))]
 
 
     [:div.extensions__pdf-hls-text-region
     [:div.extensions__pdf-hls-text-region
-     {:on-click        open-tip!
-      :on-context-menu open-tip!}
+     {:on-click        open-ctx-menu!
+      :on-context-menu open-ctx-menu!}
 
 
      (map-indexed
      (map-indexed
       (fn [idx rect]
       (fn [idx rect]
         [:div.hls-text-region-item
         [:div.hls-text-region-item
-         {:key        idx
-          :style      rect
-          :data-color color}])
+         {:key           idx
+          :style         rect
+          :draggable     "true"
+          :on-drag-start dragstart-handle!
+          :data-color    color}])
       rects)]))
       rects)]))
 
 
 (rum/defc ^:large-vars/cleanup-todo pdf-highlight-area-region
 (rum/defc ^:large-vars/cleanup-todo pdf-highlight-area-region
-  [^js viewer vw-hl hl
-   {:keys [show-ctx-tip! upd-hl!]}]
+  [^js viewer vw-hl hl {:keys [show-ctx-menu! upd-hl!]}]
+
+  (let [{:keys [id]} hl
+        *el    (rum/use-ref nil)
+        *dirty (rum/use-ref nil)
+        open-ctx-menu! (fn [^js/MouseEvent e]
+                         (.preventDefault e)
+                         (when-not (rum/deref *dirty)
+                           (let [x (.-clientX e)
+                                 y (.-clientY e)]
 
 
-  (let [*el       (rum/use-ref nil)
-        *dirty    (rum/use-ref nil)
-        open-tip! (fn [^js/MouseEvent e]
-                    (.preventDefault e)
-                    (when-not (rum/deref *dirty)
-                      (let [x (.-clientX e)
-                            y (.-clientY e)]
+                             (show-ctx-menu! viewer hl {:x x :y y}))))
 
 
-                        (show-ctx-tip! viewer hl {:x x :y y}))))]
+        dragstart-handle! (fn [^js e]
+                            (when-let [^js dt (and id (.-dataTransfer e))]
+                              (.setData dt "text/plain" (str "((" id "))"))))]
 
 
     ;; resizable
     ;; resizable
     (rum/use-effect!
     (rum/use-effect!
@@ -331,8 +347,10 @@
          {:ref             *el
          {:ref             *el
           :style           vw-bounding
           :style           vw-bounding
           :data-color      color
           :data-color      color
-          :on-click        open-tip!
-          :on-context-menu open-tip!}]))))
+          :draggable       "true"
+          :on-drag-start   dragstart-handle!
+          :on-click        open-ctx-menu!
+          :on-context-menu open-ctx-menu!}]))))
 
 
 (rum/defc pdf-highlights-region-container
 (rum/defc pdf-highlights-region-container
   "Displays the highlights over a pdf document."
   "Displays the highlights over a pdf document."
@@ -349,7 +367,7 @@
        ))])
        ))])
 
 
 (rum/defc ^:large-vars/cleanup-todo pdf-highlight-area-selection
 (rum/defc ^:large-vars/cleanup-todo pdf-highlight-area-selection
-  [^js viewer {:keys [show-ctx-tip!]}]
+  [^js viewer {:keys [show-ctx-menu!]}]
 
 
   (let [^js viewer-clt          (.. viewer -viewer -classList)
   (let [^js viewer-clt          (.. viewer -viewer -classList)
         ^js cnt-el              (.-container viewer)
         ^js cnt-el              (.-container viewer)
@@ -439,7 +457,7 @@
                                                      :properties {}}]
                                                      :properties {}}]
 
 
                                     ;; ctx tips
                                     ;; ctx tips
-                                    (show-ctx-tip! viewer hl point {:reset-fn #(reset-coords)})
+                                    (show-ctx-menu! viewer hl point {:reset-fn #(reset-coords)})
 
 
                                     ;; export area highlight
                                     ;; export area highlight
                                     ;;(dd "[selection end] :start"
                                     ;;(dd "[selection end] :start"
@@ -481,17 +499,17 @@
         *mounted       (rum/use-ref false)
         *mounted       (rum/use-ref false)
         [sel-state, set-sel-state!] (rum/use-state {:selection nil :range nil :collapsed nil :point nil})
         [sel-state, set-sel-state!] (rum/use-state {:selection nil :range nil :collapsed nil :point nil})
         [highlights, set-highlights!] (rum/use-state initial-hls)
         [highlights, set-highlights!] (rum/use-state initial-hls)
-        [tip-state, set-tip-state!] (rum/use-state {:highlight nil :vw-pos nil :selection nil :point nil :reset-fn nil})
+        [ctx-menu-state, set-ctx-menu-state!] (rum/use-state {:highlight nil :vw-pos nil :selection nil :point nil :reset-fn nil})
 
 
-        clear-ctx-tip! (rum/use-callback
-                        #(let [reset-fn (:reset-fn tip-state)]
-                           (set-tip-state! {})
+        clear-ctx-menu! (rum/use-callback
+                        #(let [reset-fn (:reset-fn ctx-menu-state)]
+                           (set-ctx-menu-state! {})
                            (and (fn? reset-fn) (reset-fn)))
                            (and (fn? reset-fn) (reset-fn)))
-                        [tip-state])
+                        [ctx-menu-state])
 
 
-        show-ctx-tip!  (fn [^js viewer hl point & ops]
+        show-ctx-menu!  (fn [^js viewer hl point & ops]
                          (let [vw-pos (pdf-utils/scaled-to-vw-pos viewer (:position hl))]
                          (let [vw-pos (pdf-utils/scaled-to-vw-pos viewer (:position hl))]
-                           (set-tip-state! (apply merge (list* {:highlight hl :vw-pos vw-pos :point point} ops)))))
+                           (set-ctx-menu-state! (apply merge (list* {:highlight hl :vw-pos vw-pos :point point} ops)))))
 
 
         add-hl!        (fn [hl] (when (:id hl)
         add-hl!        (fn [hl] (when (:id hl)
                                   ;; fix js object
                                   ;; fix js object
@@ -603,7 +621,7 @@
 
 
            ;; show ctx menu
            ;; show ctx menu
            (js/setTimeout (fn []
            (js/setTimeout (fn []
-                            (set-tip-state! {:highlight hl-fn
+                            (set-ctx-menu-state! {:highlight hl-fn
                                              :selection selection
                                              :selection selection
                                              :point     point})))) 0))
                                              :point     point})))) 0))
 
 
@@ -621,7 +639,7 @@
 
 
                (rum/mount
                (rum/mount
                 (pdf-highlights-region-container
                 (pdf-highlights-region-container
-                 viewer page-hls {:show-ctx-tip! show-ctx-tip!
+                 viewer page-hls {:show-ctx-menu! show-ctx-menu!
                                   :upd-hl!       upd-hl!})
                                   :upd-hl!       upd-hl!})
 
 
                 hls-layer)))))
                 hls-layer)))))
@@ -633,10 +651,10 @@
     [:div.extensions__pdf-highlights-cnt
     [:div.extensions__pdf-highlights-cnt
 
 
      ;; hl context tip menu
      ;; hl context tip menu
-     (when-let [_hl (:highlight tip-state)]
+     (when-let [_hl (:highlight ctx-menu-state)]
        (js/ReactDOM.createPortal
        (js/ReactDOM.createPortal
-        (pdf-highlights-ctx-menu viewer tip-state
-                                 {:clear-ctx-tip! clear-ctx-tip!
+        (pdf-highlights-ctx-menu viewer ctx-menu-state
+                                 {:clear-ctx-menu! clear-ctx-menu!
                                   :add-hl!        add-hl!
                                   :add-hl!        add-hl!
                                   :del-hl!        del-hl!
                                   :del-hl!        del-hl!
                                   :upd-hl!        upd-hl!})
                                   :upd-hl!        upd-hl!})
@@ -660,8 +678,8 @@
      ;; area selection container
      ;; area selection container
      (pdf-highlight-area-selection
      (pdf-highlight-area-selection
       viewer
       viewer
-      {:clear-ctx-tip! clear-ctx-tip!
-       :show-ctx-tip!  show-ctx-tip!
+      {:clear-ctx-menu! clear-ctx-menu!
+       :show-ctx-menu!  show-ctx-menu!
        :add-hl!        add-hl!
        :add-hl!        add-hl!
        })]))
        })]))
 
 

+ 1 - 0
src/main/frontend/extensions/pdf/utils.cljs

@@ -183,6 +183,7 @@
         (-> filename
         (-> filename
             (subs 0 (if local-asset? (- len 15) len))
             (subs 0 (if local-asset? (- len 15) len))
             (string/replace #"^hls__" "")
             (string/replace #"^hls__" "")
+            (string/replace #"__[-\d]+$" "")
             (string/replace "_" " ")
             (string/replace "_" " ")
             (string/trimr))
             (string/trimr))
         filename))))
         filename))))

+ 3 - 2
src/main/frontend/fs/sync.cljs

@@ -27,6 +27,7 @@
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.fs :as fs]
             [frontend.fs :as fs]
             [frontend.encrypt :as encrypt]
             [frontend.encrypt :as encrypt]
+            [frontend.pubsub :as pubsub]
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.util :as gp-util]
             [medley.core :refer [dedupe-by]]
             [medley.core :refer [dedupe-by]]
             [rum.core :as rum]
             [rum.core :as rum]
@@ -2766,7 +2767,7 @@
     (async/tap remote->local-sync-mult private-remote->local-sync-chan)
     (async/tap remote->local-sync-mult private-remote->local-sync-chan)
     (async/tap remote->local-full-sync-mult private-remote->local-full-sync-chan)
     (async/tap remote->local-full-sync-mult private-remote->local-full-sync-chan)
     (async/tap pause-resume-mult private-pause-resume-chan)
     (async/tap pause-resume-mult private-pause-resume-chan)
-    (async/tap util/app-wake-up-from-sleep-mult app-awake-from-sleep-chan)
+    (async/tap pubsub/app-wake-up-from-sleep-mult app-awake-from-sleep-chan)
     (go-loop []
     (go-loop []
       (let [{:keys [remote->local remote->local-full-sync local->remote-full-sync local->remote resume pause stop]}
       (let [{:keys [remote->local remote->local-full-sync local->remote-full-sync local->remote resume pause stop]}
             (async/alt!
             (async/alt!
@@ -3074,7 +3075,7 @@
         (async/untap remote->local-sync-mult private-remote->local-sync-chan)
         (async/untap remote->local-sync-mult private-remote->local-sync-chan)
         (async/untap remote->local-full-sync-mult private-remote->local-full-sync-chan)
         (async/untap remote->local-full-sync-mult private-remote->local-full-sync-chan)
         (async/untap pause-resume-mult private-pause-resume-chan)
         (async/untap pause-resume-mult private-pause-resume-chan)
-        (async/untap util/app-wake-up-from-sleep-mult app-awake-from-sleep-chan)
+        (async/untap pubsub/app-wake-up-from-sleep-mult app-awake-from-sleep-chan)
         (when ops-chan (async/close! ops-chan))
         (when ops-chan (async/close! ops-chan))
         (stop-local->remote! local->remote-syncer)
         (stop-local->remote! local->remote-syncer)
         (stop-remote->local! remote->local-syncer)
         (stop-remote->local! remote->local-syncer)

+ 4 - 4
src/main/frontend/handler/editor.cljs

@@ -1660,11 +1660,11 @@
   [up?]
   [up?]
   (fn [event]
   (fn [event]
     (util/stop event)
     (util/stop event)
+    (save-current-block!)
     (let [edit-block-id (:block/uuid (state/get-edit-block))
     (let [edit-block-id (:block/uuid (state/get-edit-block))
           move-nodes (fn [blocks]
           move-nodes (fn [blocks]
                        (outliner-tx/transact!
                        (outliner-tx/transact!
                         {:outliner-op :move-blocks}
                         {:outliner-op :move-blocks}
-                        (save-current-block!)
                         (outliner-core/move-blocks-up-down! blocks up?))
                         (outliner-core/move-blocks-up-down! blocks up?))
                        (when-let [block-node (util/get-first-block-by-id (:block/uuid (first blocks)))]
                        (when-let [block-node (util/get-first-block-by-id (:block/uuid (first blocks)))]
                          (.scrollIntoView block-node #js {:behavior "smooth" :block "nearest"})))]
                          (.scrollIntoView block-node #js {:behavior "smooth" :block "nearest"})))]
@@ -2139,10 +2139,10 @@
   [node]
   [node]
   (when-not (parent-is-page? node)
   (when-not (parent-is-page? node)
     (let [parent-node (tree/-get-parent node)]
     (let [parent-node (tree/-get-parent node)]
+      (save-current-block!)
       (outliner-tx/transact!
       (outliner-tx/transact!
        {:outliner-op :move-blocks
        {:outliner-op :move-blocks
         :real-outliner-op :indent-outdent}
         :real-outliner-op :indent-outdent}
-       (save-current-block!)
        (outliner-core/move-blocks! [(:data node)] (:data parent-node) true)))))
        (outliner-core/move-blocks! [(:data node)] (:data parent-node) true)))))
 
 
 (defn- last-top-level-child?
 (defn- last-top-level-child?
@@ -2666,6 +2666,7 @@
 
 
 (defn indent-outdent
 (defn indent-outdent
   [indent?]
   [indent?]
+  (save-current-block!)
   (state/set-editor-op! :indent-outdent)
   (state/set-editor-op! :indent-outdent)
   (let [pos (some-> (state/get-input) cursor/pos)
   (let [pos (some-> (state/get-input) cursor/pos)
         {:keys [block]} (get-state)]
         {:keys [block]} (get-state)]
@@ -2674,8 +2675,7 @@
       (outliner-tx/transact!
       (outliner-tx/transact!
        {:outliner-op :move-blocks
        {:outliner-op :move-blocks
         :real-outliner-op :indent-outdent}
         :real-outliner-op :indent-outdent}
-       (save-current-block!)
-       (outliner-core/indent-outdent-blocks! [block] indent?)))
+        (outliner-core/indent-outdent-blocks! [block] indent?)))
     (state/set-editor-op! :nil)))
     (state/set-editor-op! :nil)))
 
 
 (defn keydown-tab-handler
 (defn keydown-tab-handler

+ 5 - 1
src/main/frontend/handler/editor/keyboards.cljs

@@ -11,7 +11,7 @@
     (mixins/hide-when-esc-or-outside
     (mixins/hide-when-esc-or-outside
      state
      state
      :on-hide
      :on-hide
-     (fn [_state _e event]
+     (fn [_state e event]
        (cond
        (cond
          (contains?
          (contains?
           #{:commands :block-commands
           #{:commands :block-commands
@@ -25,6 +25,10 @@
          (= :input (state/get-editor-action))
          (= :input (state/get-editor-action))
          nil
          nil
 
 
+         (some-> (.-target e)
+                 (.closest ".ls-keep-editing-when-outside-click"))
+         nil
+
          :else
          :else
          (let [{:keys [on-hide value]} (editor-handler/get-state)]
          (let [{:keys [on-hide value]} (editor-handler/get-state)]
            (when on-hide
            (when on-hide

+ 16 - 17
src/main/frontend/handler/file_sync.cljs

@@ -145,23 +145,22 @@
                                      (apply path/join base-path))
                                      (apply path/join base-path))
             version-file-paths (<! (p->c (fs/readdir version-files-dir :path-only? true)))]
             version-file-paths (<! (p->c (fs/readdir version-files-dir :path-only? true)))]
         (when-not (instance? ExceptionInfo version-file-paths)
         (when-not (instance? ExceptionInfo version-file-paths)
-          (let [version-file-paths (remove #{version-files-dir} version-file-paths)]
-            (when (seq version-file-paths)
-              (->>
-               (mapv
-                (fn [path]
-                  (try
-                    (let [create-time
-                          (-> (path/parse path)
-                              (js->clj :keywordize-keys true)
-                              :name
-                              (#(tf/parse (tf/formatter "yyyy-MM-dd'T'HH_mm_ss.SSSZZ") %)))]
-                      {:create-time create-time :path path :relative-path (string/replace-first path base-path "")})
-                    (catch :default e
-                      (log/error :page-history/parse-format-error e)
-                      nil)))
-                version-file-paths)
-               (remove nil?)))))))))
+          (when (seq version-file-paths)
+            (->>
+             (mapv
+              (fn [path]
+                (try
+                  (let [create-time
+                        (-> (path/parse path)
+                            (js->clj :keywordize-keys true)
+                            :name
+                            (#(tf/parse (tf/formatter "yyyy-MM-dd'T'HH_mm_ss.SSSZZ") %)))]
+                    {:create-time create-time :path path :relative-path (string/replace-first path base-path "")})
+                  (catch :default e
+                    (log/error :page-history/parse-format-error e)
+                    nil)))
+              version-file-paths)
+             (remove nil?))))))))
 
 
 (defn fetch-page-file-versions [graph-uuid page]
 (defn fetch-page-file-versions [graph-uuid page]
   []
   []

+ 33 - 1
src/main/frontend/handler/page.cljs

@@ -427,6 +427,31 @@
     (doseq [page-id page-ids]
     (doseq [page-id page-ids]
       (outliner-file/sync-to-file page-id))))
       (outliner-file/sync-to-file page-id))))
 
 
+(defn- rename-update-namespace!
+  "update :block/namespace of the renamed block"
+  [page old-original-name new-name]
+  (let [old-namespace? (text/namespace-page? old-original-name)
+        new-namespace? (text/namespace-page? new-name)
+        update-namespace! (fn [] (let [namespace (first (gp-util/split-last "/" new-name))]
+                                   (when namespace
+                                     (create! namespace {:redirect? false}) ;; create parent page if not exist, creation of namespace ref is handled in `create!`
+                                     (let [namespace-block (db/pull [:block/name (gp-util/page-name-sanity-lc namespace)])
+                                           repo                (state/get-current-repo)
+                                           page-txs [{:db/id (:db/id page)
+                                                      :block/namespace (:db/id namespace-block)}]]
+                                       (d/transact! (db/get-db repo false) page-txs)))))
+        remove-namespace! (fn []
+                            (db/transact! [[:db/retract (:db/id page) :block/namespace]]))]
+
+    (when old-namespace?
+      (if new-namespace?
+        (update-namespace!)
+        (remove-namespace!)))
+
+    (when-not old-namespace?
+      (when new-namespace?
+        (update-namespace!)))))
+
 (defn- rename-page-aux
 (defn- rename-page-aux
   "Only accepts unsanitized page names"
   "Only accepts unsanitized page names"
   [old-name new-name redirect?]
   [old-name new-name redirect?]
@@ -474,6 +499,8 @@
 
 
         (rename-update-refs! page old-original-name new-name)
         (rename-update-refs! page old-original-name new-name)
 
 
+        (rename-update-namespace! page old-original-name new-name)
+
         (outliner-file/sync-to-file page))
         (outliner-file/sync-to-file page))
 
 
       ;; Redirect to the newly renamed page
       ;; Redirect to the newly renamed page
@@ -580,7 +607,12 @@
 
 
       (rename-update-refs! from-page
       (rename-update-refs! from-page
                            (util/get-page-original-name from-page)
                            (util/get-page-original-name from-page)
-                           (util/get-page-original-name to-page)))
+                           (util/get-page-original-name to-page))
+
+      (rename-update-namespace! from-page
+                                (util/get-page-original-name from-page)
+                                (util/get-page-original-name to-page)))
+
 
 
     (delete! from-page-name nil)
     (delete! from-page-name nil)
 
 

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

@@ -24,7 +24,6 @@
             [promesa.core :as p]
             [promesa.core :as p]
             [shadow.resource :as rc]
             [shadow.resource :as rc]
             [frontend.db.persist :as db-persist]
             [frontend.db.persist :as db-persist]
-            [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser :as graph-parser]
             [logseq.graph-parser :as graph-parser]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.config :as gp-config]
             [electron.ipc :as ipc]
             [electron.ipc :as ipc]
@@ -302,7 +301,7 @@
                              [])
                              [])
               add-or-modify-files (some->>
               add-or-modify-files (some->>
                                    (concat modify-files add-files)
                                    (concat modify-files add-files)
-                                   (gp-util/remove-nils))
+                                   (remove nil?))
               options {:delete-files (concat delete-files delete-pages)
               options {:delete-files (concat delete-files delete-pages)
                        :delete-blocks delete-blocks
                        :delete-blocks delete-blocks
                        :re-render? true}]
                        :re-render? true}]

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

@@ -27,6 +27,7 @@
   (text/remove-level-spaces content format (config/get-block-pattern format)))
   (text/remove-level-spaces content format (config/get-block-pattern format)))
 
 
 (defn search
 (defn search
+  "The aggretation of search results"
   ([q]
   ([q]
    (search (state/get-current-repo) q))
    (search (state/get-current-repo) q))
   ([repo q]
   ([repo q]

+ 1 - 1
src/main/frontend/handler/user.cljs

@@ -146,7 +146,7 @@
         ;; refresh remote graph list by pub login event
         ;; refresh remote graph list by pub login event
         (when (user-uuid) (state/pub-event! [:user/fetch-info-and-graphs]))))))
         (when (user-uuid) (state/pub-event! [:user/fetch-info-and-graphs]))))))
 
 
-(defn login-callback [code]
+(defn ^:export login-callback [code]
   (state/set-state! [:ui/loading? :login] true)
   (state/set-state! [:ui/loading? :login] true)
   (go
   (go
     (let [resp (<! (http/get (str "https://" config/API-DOMAIN "/auth_callback?code=" code)
     (let [resp (<! (http/get (str "https://" config/API-DOMAIN "/auth_callback?code=" code)

+ 3 - 0
src/main/frontend/publishing.cljs

@@ -9,6 +9,7 @@
             [frontend.page :as page]
             [frontend.page :as page]
             [frontend.util :as util]
             [frontend.util :as util]
             [frontend.routes :as routes]
             [frontend.routes :as routes]
+            [frontend.context.i18n :as i18n]
             [reitit.frontend :as rf]
             [reitit.frontend :as rf]
             [reitit.frontend.easy :as rfe]
             [reitit.frontend.easy :as rfe]
             [cljs.reader :as reader]
             [cljs.reader :as reader]
@@ -79,6 +80,8 @@
   ;; this is called in the index.html and must be exported
   ;; this is called in the index.html and must be exported
   ;; so it is available even in :advanced release builds
   ;; so it is available even in :advanced release builds
   (register-components-fns!)
   (register-components-fns!)
+  ;; Set :preferred-lang as some components depend on it
+  (i18n/start)
   (restore-from-transit-str!)
   (restore-from-transit-str!)
   (restore-state!)
   (restore-state!)
   (shortcut/refresh!)
   (shortcut/refresh!)

+ 75 - 0
src/main/frontend/pubsub.cljc

@@ -0,0 +1,75 @@
+(ns frontend.pubsub
+  "All mults and pubs are collected to this ns.
+  vars with suffix '-mult' is a/Mult, use a/tap and a/untap on them. used by event subscribers
+  vars with suffix '-pub' is a/Pub, use a/sub and a/unsub on them. used by event subscribers
+  vars with suffix '-ch' is chan used by event publishers."
+  {:clj-kondo/config {:linters {:unresolved-symbol {:level :off}}}}
+  #?(:cljs (:require-macros [frontend.pubsub :refer [def-mult-or-pub chan-of]]))
+  (:require [clojure.core.async :as a :refer [chan mult pub]]
+            [clojure.core.async.impl.protocols :as ap]
+            [malli.core :as m]
+            [malli.dev.pretty :as mdp]
+            [clojure.pprint :as pp]))
+
+;;; helper macro
+(defmacro chan-of [malli-schema malli-schema-validator & chan-args]
+  `(let [ch# (chan ~@chan-args)]
+     (reify
+       ap/ReadPort
+       (~'take! [~'_ fn1-handler#]
+        (ap/take! ch# fn1-handler#))
+       ap/WritePort
+       (~'put! [~'_ val# fn1-handler#]
+        (if (~malli-schema-validator val#)
+          (ap/put! ch# val# fn1-handler#)
+          (do (mdp/explain ~malli-schema val#)
+              (throw (ex-info "validate chan value failed" {:val val#}))))))))
+
+(defmacro def-mult-or-pub
+  "define following vars:
+  - `symbol-name`-ch for event publisher.
+  - `symbol-name`-mult or `symbol-name`-pub for event subscribers.
+  - `symbol-name`-validator is malli schema validator
+  def -pub var when `:topic-fn` exists otherwise -mult var"
+  [symbol-name doc-string malli-schema & {:keys [ch-buffer topic-fn]
+                                          :or   {ch-buffer 1}}]
+  (let [schema-validator-name (symbol (str symbol-name "-validator"))
+        schema-name           (symbol (str symbol-name "-schema"))
+        ch-name               (symbol (str symbol-name "-ch"))
+        mult-or-pub-name      (if topic-fn
+                                (symbol (str symbol-name "-pub"))
+                                (symbol (str symbol-name "-mult")))
+        doc-string*           (str doc-string "\nMalli-schema:\n" (with-out-str (pp/pprint malli-schema)))]
+    `(do
+       (def ~schema-name ~malli-schema)
+       (def ~schema-validator-name (m/validator ~malli-schema))
+       (def ~ch-name ~doc-string* (chan-of ~malli-schema ~schema-validator-name ~ch-buffer))
+       ~(if topic-fn
+          `(def ~mult-or-pub-name ~doc-string* (pub ~ch-name ~topic-fn))
+          `(def ~mult-or-pub-name ~doc-string* (mult ~ch-name))))))
+
+;;; all chan, mult, pub defined here...
+
+(def-mult-or-pub app-wake-up-from-sleep
+  "app wake up from sleep event"
+  [:map
+   [:last-activated-at :int]
+   [:now :int]])
+
+(def-mult-or-pub sync-events
+  "file-sync events"
+  [:map
+   [:event [:enum
+            :created-local-version-file
+            :finished-local->remote
+            :finished-remote->local
+            :start
+            :pause
+            :resume
+            :exception-decrypt-failed
+            :remote->local-full-sync-failed
+            :local->remote-full-sync-failed
+            :get-remote-graph-failed
+            :get-deletion-logs-failed]]
+   [:data :map]]
+  :topic-fn :event)

+ 11 - 2
src/main/frontend/routes.cljs

@@ -10,8 +10,9 @@
             [frontend.components.search :as search]
             [frontend.components.search :as search]
             [frontend.components.settings :as settings]
             [frontend.components.settings :as settings]
             [frontend.components.shortcut :as shortcut]
             [frontend.components.shortcut :as shortcut]
-            [frontend.components.whiteboard :as whiteboard]
-            [frontend.extensions.zotero :as zotero]))
+            [frontend.components.whiteboard :as whiteboard] 
+            [frontend.extensions.zotero :as zotero]
+            [frontend.components.bug-report :as bug-report]))
 
 
 ;; http://localhost:3000/#?anchor=fn.1
 ;; http://localhost:3000/#?anchor=fn.1
 (def routes
 (def routes
@@ -78,6 +79,14 @@
    ["/import"
    ["/import"
     {:name :import
     {:name :import
      :view setups/importer}]
      :view setups/importer}]
+   
+   ["/bug-report"
+    {:name :bug-report
+     :view bug-report/bug-report}]
+   
+    ["/bug-report-tool/:tool"
+     {:name :bug-report-tools
+      :view bug-report/bug-report-tool-route}]
 
 
    ["/all-journals"
    ["/all-journals"
     {:name :all-journals
     {:name :all-journals

+ 3 - 0
src/main/frontend/search.cljs

@@ -111,6 +111,9 @@
     (protocol/transact-blocks! engine data)))
     (protocol/transact-blocks! engine data)))
 
 
 (defn- transact-pages!
 (defn- transact-pages!
+  "Transact pages to search engine
+   :pages-to-remove-set the set of pages to remove (not include those to update)
+   :pages-to-add        the page entities to add"
   [repo data]
   [repo data]
   (when-let [engine (get-engine repo)]
   (when-let [engine (get-engine repo)]
     (protocol/transact-pages! engine data)))
     (protocol/transact-pages! engine data)))

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

@@ -28,7 +28,8 @@
             [rum.core :as rum]
             [rum.core :as rum]
             [clojure.core.async :as async]
             [clojure.core.async :as async]
             [cljs.core.async.impl.channels :refer [ManyToManyChannel]]
             [cljs.core.async.impl.channels :refer [ManyToManyChannel]]
-            [medley.core :as medley]))
+            [medley.core :as medley]
+            [frontend.pubsub :as pubsub]))
   (:require
   (:require
    [clojure.pprint]
    [clojure.pprint]
    [clojure.string :as string]
    [clojure.string :as string]
@@ -1448,11 +1449,9 @@
 
 
 #?(:cljs
 #?(:cljs
    (do
    (do
-     (def ^:private app-wake-up-from-sleep-chan (async/chan 1))
-     (def app-wake-up-from-sleep-mult (async/mult app-wake-up-from-sleep-chan))
      (defn <app-wake-up-from-sleep-loop
      (defn <app-wake-up-from-sleep-loop
        "start a async/go-loop to check the app awake from sleep.
        "start a async/go-loop to check the app awake from sleep.
-Use (async/tap `app-wake-up-from-sleep-mult`) to receive messages.
+Use (async/tap `pubsub/app-wake-up-from-sleep-mult`) to receive messages.
 Arg *stop: atom, reset to true to stop the loop"
 Arg *stop: atom, reset to true to stop the loop"
        [*stop]
        [*stop]
        (let [*last-activated-at (volatile! (tc/to-epoch (t/now)))]
        (let [*last-activated-at (volatile! (tc/to-epoch (t/now)))]
@@ -1461,7 +1460,7 @@ Arg *stop: atom, reset to true to stop the loop"
              (println :<app-wake-up-from-sleep-loop :stop)
              (println :<app-wake-up-from-sleep-loop :stop)
              (let [now-epoch (tc/to-epoch (t/now))]
              (let [now-epoch (tc/to-epoch (t/now))]
                (when (< @*last-activated-at (- now-epoch 10))
                (when (< @*last-activated-at (- now-epoch 10))
-                 (async/>! app-wake-up-from-sleep-chan {:last-activated-at @*last-activated-at :now now-epoch}))
+                 (async/>! pubsub/app-wake-up-from-sleep-ch {:last-activated-at @*last-activated-at :now now-epoch}))
                (vreset! *last-activated-at now-epoch)
                (vreset! *last-activated-at now-epoch)
                (async/<! (async/timeout 5000))
                (async/<! (async/timeout 5000))
                (recur))))))))
                (recur))))))))

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

@@ -1,3 +1,3 @@
 (ns ^:no-doc frontend.version)
 (ns ^:no-doc frontend.version)
 
 
-(defonce version "0.8.15")
+(defonce version "0.8.16")

+ 12 - 0
src/test/frontend/db/query_custom_test.cljs

@@ -51,6 +51,18 @@
                                            (not [?b :block/marker _])]]]})))
                                            (not [?b :block/marker _])]]]})))
         "advanced query that uses rule from logseq and rule from :inputs")
         "advanced query that uses rule from logseq and rule from :inputs")
 
 
+    (is (= ["LATER b3"]
+           (map :block/content
+                (custom-query {:query '[:find (pull ?b [*])
+                                        :in $ %
+                                        :where
+                                        (starts-with ?b "LA")
+                                        (task ?b #{"LATER"})]
+                               :rules '[[(starts-with ?b ?substr)
+                                         [?b :block/content ?content]
+                                         [(clojure.string/starts-with? ?content ?substr)]]]})))
+        "advanced query that uses :rules and rules from logseq")
+
     (is (= #{"page1"}
     (is (= #{"page1"}
            (set
            (set
             (map #(get-in % [:block/page :block/name])
             (map #(get-in % [:block/page :block/name])

+ 2 - 1
src/test/frontend/extensions/pdf/assets_test.cljs

@@ -7,7 +7,8 @@
     (are [x y] (= y (pdf-utils/fix-local-asset-pagename x))
     (are [x y] (= y (pdf-utils/fix-local-asset-pagename x))
       "2015_Book_Intertwingled_1659920114630_0" "2015 Book Intertwingled"
       "2015_Book_Intertwingled_1659920114630_0" "2015 Book Intertwingled"
       "hls__2015_Book_Intertwingled_1659920114630_0" "2015 Book Intertwingled"
       "hls__2015_Book_Intertwingled_1659920114630_0" "2015 Book Intertwingled"
-      "hls/2015_Book_Intertwingled_1659920114630_0" "hls/2015 Book Intertwingled"))
+      "hls/2015_Book_Intertwingled_1659920114630_0" "hls/2015 Book Intertwingled"
+      "hls__sicp__-1234567" "sicp"))
   (testing "non matched filenames"
   (testing "non matched filenames"
     (are [x y] (= y (pdf-utils/fix-local-asset-pagename x))
     (are [x y] (= y (pdf-utils/fix-local-asset-pagename x))
       "foo" "foo"
       "foo" "foo"