瀏覽代碼

Merge branch 'master' into dev/malli-schema&kondo-config

Junyi Du 2 年之前
父節點
當前提交
414fef6933
共有 53 個文件被更改,包括 1246 次插入685 次删除
  1. 3 1
      .clj-kondo/config.edn
  2. 0 13
      .github/workflows/stale-issues.yml
  3. 2 0
      .gitignore
  4. 132 8
      CODE_OF_CONDUCT.md
  5. 6 1
      README.md
  6. 2 1
      deps.edn
  7. 4 0
      deps/graph-parser/.carve/ignore
  8. 1 1
      deps/graph-parser/src/logseq/graph_parser.cljs
  9. 1 1
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  10. 1 1
      deps/graph-parser/src/logseq/graph_parser/extract.cljc
  11. 8 2
      deps/graph-parser/src/logseq/graph_parser/text.cljs
  12. 12 1
      deps/graph-parser/src/logseq/graph_parser/util.cljs
  13. 26 8
      docs/dev-practices.md
  14. 2 2
      e2e-tests/plugins.spec.ts
  15. 1 1
      e2e-tests/whiteboards.spec.ts
  16. 1 1
      libs/src/LSPlugin.ts
  17. 1 1
      package.json
  18. 12 12
      resources/package.json
  19. 12 9
      src/electron/electron/handler.cljs
  20. 4 2
      src/electron/electron/search.cljs
  21. 1 0
      src/electron/electron/window.cljs
  22. 13 4
      src/main/frontend/components/block.cljs
  23. 1 1
      src/main/frontend/components/query_table.cljs
  24. 2 0
      src/main/frontend/components/search.cljs
  25. 8 0
      src/main/frontend/components/settings.cljs
  26. 5 1
      src/main/frontend/components/sidebar.css
  27. 9 3
      src/main/frontend/db/query_custom.cljs
  28. 1 2
      src/main/frontend/db/utils.cljs
  29. 185 4
      src/main/frontend/dicts.cljc
  30. 20 19
      src/main/frontend/extensions/pdf/assets.cljs
  31. 62 44
      src/main/frontend/extensions/pdf/highlights.cljs
  32. 151 109
      src/main/frontend/fs/sync.cljs
  33. 1 0
      src/main/frontend/handler.cljs
  34. 4 0
      src/main/frontend/handler/config.cljs
  35. 5 1
      src/main/frontend/handler/editor/keyboards.cljs
  36. 16 17
      src/main/frontend/handler/file_sync.cljs
  37. 33 1
      src/main/frontend/handler/page.cljs
  38. 1 2
      src/main/frontend/handler/repo.cljs
  39. 1 0
      src/main/frontend/handler/search.cljs
  40. 11 7
      src/main/frontend/modules/file/core.cljs
  41. 34 29
      src/main/frontend/modules/outliner/file.cljs
  42. 1 1
      src/main/frontend/modules/outliner/pipeline.cljs
  43. 21 3
      src/main/frontend/modules/shortcut/dicts.cljc
  44. 3 0
      src/main/frontend/publishing.cljs
  45. 75 0
      src/main/frontend/pubsub.cljc
  46. 1 0
      src/main/frontend/schema/handler/common_config.cljc
  47. 3 0
      src/main/frontend/search.cljs
  48. 4 0
      src/main/frontend/state.cljs
  49. 20 1
      src/main/frontend/util.cljc
  50. 12 0
      src/test/frontend/db/query_custom_test.cljs
  51. 283 346
      static/yarn.lock
  52. 3 0
      templates/config.edn
  53. 25 24
      yarn.lock

+ 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

+ 0 - 13
.github/workflows/stale-issues.yml

@@ -48,13 +48,6 @@ jobs:
 
 
             Thanks for your contributions to Logseq! If you have any other [**issues**](https://github.com/logseq/logseq/issues/new/choose) or [**feature requests**](https://discuss.logseq.com/c/feature-requests/), please don't hesitate to [let us know](https://github.com/logseq/logseq/issues/new/choose). We always welcome pull requests too!
             Thanks for your contributions to Logseq! If you have any other [**issues**](https://github.com/logseq/logseq/issues/new/choose) or [**feature requests**](https://discuss.logseq.com/c/feature-requests/), please don't hesitate to [let us know](https://github.com/logseq/logseq/issues/new/choose). We always welcome pull requests too!
 
 
-      - name: 'Print outputs'
-        run: |
-          for key in steps.stale_issues.outputs
-            do
-              printf '%s: %s\n' "${key}" "${steps.stale_issues.outputs[key]}"
-            done
-
       - name: '🧹 Close stale awaiting response issues'
       - name: '🧹 Close stale awaiting response issues'
         id: awaiting_issues
         id: awaiting_issues
         uses: actions/stale@6f05e4244c9a0b2ed3401882b05d701dd0a7289b  # v7
         uses: actions/stale@6f05e4244c9a0b2ed3401882b05d701dd0a7289b  # v7
@@ -83,9 +76,3 @@ jobs:
 
 
             Thanks for your contributions to Logseq! If you have any other [**issues**](https://github.com/logseq/logseq/issues/new/choose) or [**feature requests**](https://discuss.logseq.com/c/feature-requests/), please don't hesitate to [let us know](https://github.com/logseq/logseq/issues/new/choose). We always welcome pull requests too!
             Thanks for your contributions to Logseq! If you have any other [**issues**](https://github.com/logseq/logseq/issues/new/choose) or [**feature requests**](https://discuss.logseq.com/c/feature-requests/), please don't hesitate to [let us know](https://github.com/logseq/logseq/issues/new/choose). We always welcome pull requests too!
 
 
-      - name: 'Print outputs'
-        run: |
-          for key in steps.awaiting_issues.outputs
-            do
-              printf '%s: %s\n' "${key}" "${steps.awaiting_issues.outputs[key]}"
-            done

+ 2 - 0
.gitignore

@@ -55,3 +55,5 @@ android/app/src/main/assets/capacitor.config.json
 
 
 *.sublime-*
 *.sublime-*
 /public/static
 /public/static
+.yarn/
+.yarnrc.yml

+ 132 - 8
CODE_OF_CONDUCT.md

@@ -1,16 +1,140 @@
 # Code of Conduct
 # Code of Conduct
 
 
-## Purpose
+## Our Pledge
+
+In the interest of fostering a safe and welcoming environment, we as
+the Logseq team pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
+identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.  Logseq is not merely a note-taking tool, but can potentially become a community for collaboration and learning.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+  and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the overall
+  community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or advances of
+  any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email address,
+  without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Enforcement Responsibilities
+
+Logseq team is responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Logseq team has the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
 
 
-Logseq aims to be a project inclusive to the largest number of people. It is also not merely a note-taking tool, but can potentially become a community for collaboration and learning. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status and religion (or lack thereof).
 
 
 ## Scope
 ## Scope
 
 
-This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
+This Code of Conduct applies to all Logseq communication channels - online or in person,
+and it also applies when an individual is representing the project or its community in
+public spaces. Examples of representing a project or community include using an official
+project e-mail address, posting via an official social media account, or acting
+as an appointed representative at an online or offline event. Representation of
+a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by 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. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The Logseq team
+will maintain confidentiality with regard to the reporter of an incident.
+Enforcement may result in an indefinite ban from all official Logseq communication
+channels, or other actions as deemed appropriate by the Logseq team.
+
+Logseq maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Enforcement Guidelines
+
+Logseq team and community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of
+actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or permanent
+ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the
+community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.1, available at
+[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
 
 
-## Policy
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
+[https://www.contributor-covenant.org/translations][translations].
 
 
-- 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.
-- 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 [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.
+[homepage]: https://www.contributor-covenant.org
+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations

+ 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 - 1
deps.edn

@@ -32,7 +32,8 @@
 
 
  :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"}
-                                cider/cider-nrepl                {:mvn/version "0.28.4"}
+                                org.clojure/tools.namespace      {:mvn/version "0.2.11"}
+                                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`

+ 2 - 2
e2e-tests/plugins.spec.ts

@@ -1,7 +1,7 @@
 import { expect } from '@playwright/test'
 import { expect } from '@playwright/test'
 import { test } from './fixtures'
 import { test } from './fixtures'
 
 
-test('enabled plugin system default', async ({ page }) => {
+test.skip('enabled plugin system default', async ({ page }) => {
   const callAPI = callPageAPI.bind(null, page)
   const callAPI = callPageAPI.bind(null, page)
 
 
   const pluginEnabled = await callAPI('get_state_from_store', 'plugin/enabled')
   const pluginEnabled = await callAPI('get_state_from_store', 'plugin/enabled')
@@ -14,7 +14,7 @@ test('enabled plugin system default', async ({ page }) => {
   expect(Object.keys(currentGraph)).toEqual(['url', 'name', 'path'])
   expect(Object.keys(currentGraph)).toEqual(['url', 'name', 'path'])
 })
 })
 
 
-test('play a plugin<logseq-journals-calendar> from the Marketplace', async ({ page }) => {
+test.skip('play a plugin<logseq-journals-calendar> from the Marketplace', async ({ page }) => {
   await page.keyboard.press('t+p')
   await page.keyboard.press('t+p')
   const searchInput = page.locator('.search-ctls .form-input')
   const searchInput = page.locator('.search-ctls .form-input')
   await searchInput.type('journals')
   await searchInput.type('journals')

+ 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(1000)
   await page.waitForTimeout(1000)
   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 }) => {

+ 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
package.json

@@ -105,7 +105,7 @@
         "d3-force": "3.0.0",
         "d3-force": "3.0.0",
         "diff": "5.0.0",
         "diff": "5.0.0",
         "dompurify": "2.4.0",
         "dompurify": "2.4.0",
-        "electron": "19.1.8",
+        "electron": "20.3.8",
         "electron-dl": "3.3.0",
         "electron-dl": "3.3.0",
         "fs": "0.0.1-security",
         "fs": "0.0.1-security",
         "fs-extra": "9.1.0",
         "fs-extra": "9.1.0",

+ 12 - 12
resources/package.json

@@ -13,14 +13,14 @@
     "electron:make": "electron-forge make",
     "electron:make": "electron-forge make",
     "electron:make-macos-arm64": "electron-forge make --platform=darwin --arch=arm64",
     "electron:make-macos-arm64": "electron-forge make --platform=darwin --arch=arm64",
     "electron:publish:github": "electron-forge publish",
     "electron:publish:github": "electron-forge publish",
-    "rebuild:all": "electron-rebuild -v 19.1.8 -f",
+    "rebuild:all": "electron-rebuild -v 20.3.8 -f",
     "postinstall": "install-app-deps"
     "postinstall": "install-app-deps"
   },
   },
   "config": {
   "config": {
     "forge": "./forge.config.js"
     "forge": "./forge.config.js"
   },
   },
   "dependencies": {
   "dependencies": {
-    "better-sqlite3": "7.4.5",
+    "better-sqlite3": "8.0.1",
     "chokidar": "^3.5.1",
     "chokidar": "^3.5.1",
     "dugite": "1.108.0",
     "dugite": "1.108.0",
     "electron-dl": "3.3.0",
     "electron-dl": "3.3.0",
@@ -44,19 +44,19 @@
     "command-exists": "1.2.9"
     "command-exists": "1.2.9"
   },
   },
   "devDependencies": {
   "devDependencies": {
-    "@electron-forge/cli": "^6.0.0-beta.57",
-    "@electron-forge/maker-deb": "^6.0.0-beta.57",
-    "@electron-forge/maker-dmg": "^6.0.0-beta.57",
-    "@electron-forge/maker-rpm": "^6.0.0-beta.57",
-    "@electron-forge/maker-squirrel": "^6.0.0-beta.57",
-    "@electron-forge/maker-zip": "^6.0.0-beta.57",
-    "electron": "19.1.8",
+    "@electron-forge/cli": "^6.0.4",
+    "@electron-forge/maker-deb": "^6.0.4",
+    "@electron-forge/maker-dmg": "^6.0.4",
+    "@electron-forge/maker-rpm": "^6.0.4",
+    "@electron-forge/maker-squirrel": "^6.0.4",
+    "@electron-forge/maker-zip": "^6.0.4",
+    "@electron/rebuild": "3.2.10",
+    "electron": "20.3.8",
     "electron-builder": "^22.11.7",
     "electron-builder": "^22.11.7",
-    "electron-forge-maker-appimage": "https://github.com/logseq/electron-forge-maker-appimage.git",
-    "electron-rebuild": "3.2.5"
+    "electron-forge-maker-appimage": "https://github.com/logseq/electron-forge-maker-appimage.git"
   },
   },
   "resolutions": {
   "resolutions": {
-    "**/electron": "19.1.8",
+    "**/electron": "20.3.8",
     "**/node-gyp": "9.0.0"
     "**/node-gyp": "9.0.0"
   }
   }
 }
 }

+ 12 - 9
src/electron/electron/handler.cljs

@@ -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]]

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

+ 1 - 0
src/electron/electron/window.cljs

@@ -34,6 +34,7 @@
                      {:plugins                 true ; pdf
                      {:plugins                 true ; pdf
                       :nodeIntegration         false
                       :nodeIntegration         false
                       :nodeIntegrationInWorker false
                       :nodeIntegrationInWorker false
+                      :sandbox                 false
                       :webSecurity             (not dev?)
                       :webSecurity             (not dev?)
                       :contextIsolation        true
                       :contextIsolation        true
                       :spellcheck              ((fnil identity true) (cfgs/get-item :spell-check))
                       :spellcheck              ((fnil identity true) (cfgs/get-item :spell-check))

+ 13 - 4
src/main/frontend/components/block.cljs

@@ -2245,7 +2245,7 @@
         plugin-slotted? (and config/lsp-enabled? (state/slot-hook-exist? uuid))
         plugin-slotted? (and config/lsp-enabled? (state/slot-hook-exist? uuid))
         block-ref? (:block-ref? config)
         block-ref? (:block-ref? config)
         stop-events? (:stop-events? config)
         stop-events? (:stop-events? config)
-        block-ref-with-title? (and block-ref? (seq title))
+        block-ref-with-title? (and block-ref? (not (state/show-full-blocks?)) (seq title))
         block-type (or (:ls-type properties) :default)
         block-type (or (:ls-type properties) :default)
         content (if (string? content) (string/trim content) "")
         content (if (string? content) (string/trim content) "")
         mouse-down-key (if (util/ios?)
         mouse-down-key (if (util/ios?)
@@ -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

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

@@ -170,7 +170,7 @@
                               [: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 (get-in item [:block/properties-text-values 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)]

+ 8 - 0
src/main/frontend/components/settings.cljs

@@ -366,6 +366,12 @@
           logical-outdenting?
           logical-outdenting?
           config-handler/toggle-logical-outdenting!))
           config-handler/toggle-logical-outdenting!))
 
 
+(defn showing-full-blocks [t show-full-blocks?]
+  (toggle "show_full_blocks"
+          (t :settings-page/show-full-blocks)
+          show-full-blocks?
+          config-handler/toggle-show-full-blocks!))
+
 (defn preferred-pasting-file [t preferred-pasting-file?]
 (defn preferred-pasting-file [t preferred-pasting-file?]
   (toggle "preferred_pasting_file"
   (toggle "preferred_pasting_file"
           (t :settings-page/preferred-pasting-file)
           (t :settings-page/preferred-pasting-file)
@@ -607,6 +613,7 @@
         enable-timetracking? (state/enable-timetracking?)
         enable-timetracking? (state/enable-timetracking?)
         enable-all-pages-public? (state/all-pages-public?)
         enable-all-pages-public? (state/all-pages-public?)
         logical-outdenting? (state/logical-outdenting?)
         logical-outdenting? (state/logical-outdenting?)
+        show-full-blocks? (state/show-full-blocks?)
         preferred-pasting-file? (state/preferred-pasting-file?)
         preferred-pasting-file? (state/preferred-pasting-file?)
         enable-tooltip? (state/enable-tooltip?)
         enable-tooltip? (state/enable-tooltip?)
         enable-shortcut-tooltip? (state/sub :ui/shortcut-tooltip?)
         enable-shortcut-tooltip? (state/sub :ui/shortcut-tooltip?)
@@ -622,6 +629,7 @@
 
 
      (when (util/electron?) (switch-spell-check-row t))
      (when (util/electron?) (switch-spell-check-row t))
      (outdenting-row t logical-outdenting?)
      (outdenting-row t logical-outdenting?)
+     (showing-full-blocks t show-full-blocks?)
      (preferred-pasting-file t preferred-pasting-file?)
      (preferred-pasting-file t preferred-pasting-file?)
      (when-not (or (util/mobile?) (mobile-util/native-platform?))
      (when-not (or (util/mobile?) (mobile-util/native-platform?))
        (shortcut-tooltip-row t enable-shortcut-tooltip?))
        (shortcut-tooltip-row t enable-shortcut-tooltip?))

+ 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

+ 185 - 4
src/main/frontend/dicts.cljc

@@ -218,8 +218,9 @@
         :settings-page/spell-checker "Spell checker"
         :settings-page/spell-checker "Spell checker"
         :settings-page/auto-updater "Auto updater"
         :settings-page/auto-updater "Auto updater"
         :settings-page/disable-sentry "Send usage data and diagnostics to Logseq"
         :settings-page/disable-sentry "Send usage data and diagnostics to Logseq"
-        :settings-page/disable-sentry-desc "Logseq will never collect your local graph database or sell your data." 
+        :settings-page/disable-sentry-desc "Logseq will never collect your local graph database or sell your data."
         :settings-page/preferred-outdenting "Logical outdenting"
         :settings-page/preferred-outdenting "Logical outdenting"
+        :settings-page/show-full-blocks "Show all lines of a block reference"
         :settings-page/custom-date-format "Preferred date format"
         :settings-page/custom-date-format "Preferred date format"
         :settings-page/custom-date-format-warning "Re-index required! Existing journal references would be broken!"
         :settings-page/custom-date-format-warning "Re-index required! Existing journal references would be broken!"
         :settings-page/preferred-file-format "Preferred file format"
         :settings-page/preferred-file-format "Preferred file format"
@@ -1449,6 +1450,20 @@
         :user/delete-account-notice "Toutes vos pages publiées sur logseq.com seront supprimées"
         :user/delete-account-notice "Toutes vos pages publiées sur logseq.com seront supprimées"
         :user/delete-your-account "Supprimer votre compte"
         :user/delete-your-account "Supprimer votre compte"
         :whiteboard/link-whiteboard-or-block "Lier un tablau blanc/page/bloc"
         :whiteboard/link-whiteboard-or-block "Lier un tablau blanc/page/bloc"
+
+        :file/validate-existing-file-error "La page existe déjà avec un autre fichier: {1}, fichier actuel: {2}. Veuillez n'en garder qu'un et réindexer votre graphique."
+        :notification/clear-all "Tout effacer"
+        :on-boarding/closed-feature "Fermé {1}"
+        :on-boarding/tour-whiteboard-home "{1} Commencez pour vos tableaux blancs"
+        :on-boarding/tour-whiteboard-home-description "Les tableaux blancs ont leur propre section dans l'application où vous pouvez les voir en un coup d'œil, en créer de nouveaux ou les supprimer facilement."
+        :on-boarding/tour-whiteboard-new "{1} Créer un nouveau tableau blanc"
+        :on-boarding/tour-whiteboard-new-description "Il existe plusieurs façons de créer un nouveau tableau blanc. L'un d'eux est toujours ici dans le tableau de bord."
+        :on-boarding/welcome-whiteboard-modal-description "Les tableaux blancs sont un excellent outil de brainstorming et d'organisation. Vous pouvez maintenant placer n'importe laquelle de vos pensées de la base de connaissances ou de nouvelles à côté l'une de l'autre sur une toile spatiale pour vous connecter, vous associer et comprendre de nouvelles façons."
+        :on-boarding/welcome-whiteboard-modal-skip "Sauter"
+        :on-boarding/welcome-whiteboard-modal-start "Démarrer le tableau blanc"
+        :on-boarding/welcome-whiteboard-modal-title "Un nouveau cadre pour vos pensées."
+        :settings-page/clear-cache-warning "Vider le cache supprimera les graphiques ouverts. Vous perdrez les modifications non enregistrées."
+        :settings-page/disable-sentry-desc "Logseq ne collectera jamais votre base de données de graphes locale ni ne vendra vos données."
      }
      }
 
 
    :zh-CN {:accessibility/skip-to-main-content "跳转到主内容"
    :zh-CN {:accessibility/skip-to-main-content "跳转到主内容"
@@ -1586,7 +1601,7 @@
            :file/name "文件名"
            :file/name "文件名"
            :file/file "文件:"
            :file/file "文件:"
            :file/last-modified-at "最后更改于"
            :file/last-modified-at "最后更改于"
-           :file/no-data "没有数据" 
+           :file/no-data "没有数据"
            :file/validate-existing-file-error "页面已存在另一个文件: {1}, 当前文件: {2}. 请保留其中一个文件,然后重建当前图谱的索引。"
            :file/validate-existing-file-error "页面已存在另一个文件: {1}, 当前文件: {2}. 请保留其中一个文件,然后重建当前图谱的索引。"
            :file-rn/re-index "重命名文件后,如果其他设备同步了改文件,强烈建议在同步成功后重新建立索引。"
            :file-rn/re-index "重命名文件后,如果其他设备同步了改文件,强烈建议在同步成功后重新建立索引。"
            :file-rn/need-action "建议执行文件重命名操作以匹配新格式。当重命名的文件被同步后,请在所有设备上重新建立索引。"
            :file-rn/need-action "建议执行文件重命名操作以匹配新格式。当重命名的文件被同步后,请在所有设备上重新建立索引。"
@@ -2346,6 +2361,7 @@
         :settings-page/auto-updater "Auto actualizador"
         :settings-page/auto-updater "Auto actualizador"
         :settings-page/disable-sentry "Enviar datos de uso y diagnósticos a Logseq"
         :settings-page/disable-sentry "Enviar datos de uso y diagnósticos a Logseq"
         :settings-page/preferred-outdenting "Disminución lógica de sangría"
         :settings-page/preferred-outdenting "Disminución lógica de sangría"
+        :settings-page/show-full-blocks "Mostrar todas las líneas de una referencia a bloque"
         :settings-page/custom-date-format "Formato de fecha preferido"
         :settings-page/custom-date-format "Formato de fecha preferido"
         :settings-page/preferred-file-format "Formato de archivo preferido"
         :settings-page/preferred-file-format "Formato de archivo preferido"
         :settings-page/preferred-workflow "Flujo de trabajo preferido"
         :settings-page/preferred-workflow "Flujo de trabajo preferido"
@@ -3187,7 +3203,89 @@
            :settings-page/custom-global-configuration "Configuração global personalizada"
            :settings-page/custom-global-configuration "Configuração global personalizada"
            :settings-page/edit-global-config-edn "Editar config.edn global"
            :settings-page/edit-global-config-edn "Editar config.edn global"
            :settings-page/sync "Sincronizar"
            :settings-page/sync "Sincronizar"
-           :settings-page/tab-features "Recursos"}
+           :settings-page/tab-features "Recursos"
+           :all-whiteboards "Todos os quadros brancos"
+           :auto-heading "Título automático"
+           :go-to-whiteboard "Ir para o quadro branco"
+           :heading "Título {1}"
+           :new-whiteboard "Novo quadro branco"
+           :not-available-in-mode "Não disponível no modo {1}"
+           :remove-heading "Remover título"
+           :untitled "Sem título"
+           :accessibility/skip-to-main-content "Ir para o conteúdo principal"
+           :color/blue "Azul"
+           :color/gray "Cinza"
+           :color/green "Verde"
+           :color/pink "Rosa"
+           :color/purple "Roxo"
+           :color/red "Vermelho"
+           :color/yellow "Amarelo"
+           :conversion/non-desktop "O diretório Graph em versões antigas precisa ser convertido para o novo formato. Por favor, use o aplicativo de desktop para fazer a conversão."
+           :conversion/write-filename-format "Aplicar formato para arquivos recebidos"
+           :file/validate-existing-file-error "A página já existe com outro arquivo: {1}, arquivo atual: {2}. Por favor, mantenha apenas um deles e reindexe seu gráfico."
+           :file-rn/affected-pages "Páginas afetadas após a alteração do formato"
+           :file-rn/all-action "Aplicar todas as ações!"
+           :file-rn/apply-rename "Aplicar a operação de renomeação de arquivo"
+           :file-rn/close-panel "Fechar o painel"
+           :file-rn/confirm-proceed "Atualizar formato!"
+           :file-rn/filename-desc-1 "Esta configuração define como uma página é armazenada em um arquivo. Logseq armazena uma página em um arquivo com o mesmo nome."
+           :file-rn/filename-desc-2 "Alguns caracteres como \"/\" ou \"?\" são inválidos para um nome de arquivo."
+           :file-rn/filename-desc-3 "Logseq substitui caracteres inválidos por seu URL codificado equivalente para torná-los válidos (por exemplo, \"?\" torna-se \"%3F\")."
+           :file-rn/filename-desc-4 "O separador de espaço de nomes \"/\" também é substituído por \"___\" (sublinhado triplo) para consideração estética."
+           :file-rn/format-deprecated "Você está usando um formato desatualizado. A atualização para o formato mais recente é altamente recomendada. Faça backup de seus dados e feche os clientes Logseq em outros dispositivos antes da operação."
+           :file-rn/instruct-1 "É um processo de 2 etapas para atualizar o formato do nome do arquivo:"
+           :file-rn/instruct-2 "1. Clique em "
+           :file-rn/instruct-3 "2. Siga as instruções abaixo para renomear os arquivos para o novo formato:"
+           :file-rn/legend "🟢 Ações opcionais de renomeação; 🟡 Ação de renomeação necessária para evitar mudança de título; 🔴 Mudança de última hora."
+           :file-rn/need-action "As ações de renomeação de arquivo são sugeridas para corresponder ao novo formato. A reindexação é necessária em todos os dispositivos quando os arquivos renomeados são sincronizados."
+           :file-rn/no-action "Bom trabalho! Nenhuma ação adicional necessária."
+           :file-rn/optional-rename "Sugestão: "
+           :file-rn/or-select-actions " ou renomear individualmente os arquivos abaixo, então "
+           :file-rn/or-select-actions-2 ". Essas ações não estarão disponíveis depois que você fechar este painel."
+           :file-rn/otherwise-breaking "Ou o título se tornará"
+           :file-rn/re-index "A reindexação é fortemente recomendada após a renomeação dos arquivos e em outros dispositivos após a sincronização."
+           :file-rn/rename "renomeie o arquivo \"{1}\" para \"{2}\""
+           :file-rn/select-confirm-proceed "Dev: formato de gravação"
+           :file-rn/select-format "(Opção do modo de desenvolvedor, perigoso!) Selecione o formato do nome do arquivo"
+           :file-rn/suggest-rename "Ação necessária: "
+           :file-rn/unreachable-title "Aviso! O nome da página se tornará {1} no formato de nome de arquivo atual, a menos que a propriedade `title::` seja definida manualmente"
+           :left-side-bar/create "Criar"
+           :left-side-bar/new-whiteboard "Novo quadro branco"
+           :notification/clear-all "Limpar tudo"
+           :on-boarding/closed-feature "Fechado {1}"
+           :on-boarding/tour-whiteboard-home "{1} Início para seus quadros brancos"
+           :on-boarding/tour-whiteboard-home-description "Os quadros brancos têm sua própria seção no aplicativo, onde você pode vê-los rapidamente, criar novos ou excluí-los facilmente."
+           :on-boarding/tour-whiteboard-new "{1} Criar novo quadro branco"
+           :on-boarding/tour-whiteboard-new-description "Existem várias maneiras de criar um novo quadro branco. Um deles está sempre aqui no painel."
+           :on-boarding/welcome-whiteboard-modal-description "Quadros brancos são uma ótima ferramenta para brainstorming e organização. Agora você pode colocar qualquer um dos seus pensamentos da base de conhecimento ou novos próximos uns dos outros em uma tela espacial para conectar, associar e entender de novas maneiras."
+           :on-boarding/welcome-whiteboard-modal-skip "Pular"
+           :on-boarding/welcome-whiteboard-modal-start "Iniciar quadro branco"
+           :on-boarding/welcome-whiteboard-modal-title "Um novo quadro para seus pensamentos."
+           :page/show-whiteboards "Mostrar quadros brancos"
+           :pdf/doc-metadata "Metadados do documento"
+           :pdf/hl-block-colored "Rótulo colorido para bloco de destaque"
+           :plugin.install-from-file/menu-title "Instalar de plugins.edn"
+           :plugin.install-from-file/notice "Os seguintes plugins substituirão seus plugins:"
+           :plugin.install-from-file/success "Todos os plugins instalados"
+           :plugin.install-from-file/title "Instalar plugins de plugins.edn"
+           :right-side-bar/separator "Manipulador de redimensionamento da barra lateral direita"
+           :right-side-bar/whiteboards "Quadros brancos"
+           :search-item/block "Bloco"
+           :search-item/file "Arquivo"
+           :search-item/page "Página"
+           :search-item/whiteboard "Quadro branco"
+           :settings-page/alpha-features "Recursos em fase Alfa"
+           :settings-page/beta-features "Recursos em fase Beta"
+           :settings-page/clear-cache-warning "A limpeza do cache descartará os gráficos abertos. Você perderá as alterações não salvas."
+           :settings-page/custom-date-format-warning "É necessário reindexar! As referências de periódicos existentes seriam quebradas!"
+           :settings-page/disable-sentry-desc "A Logseq nunca coletará seu banco de dados gráfico local ou venderá seus dados."
+           :settings-page/edit-setting "Editar"
+           :settings-page/enable-whiteboards "Quadros brancos"
+           :settings-page/filename-format "Formato dos nomes de arquivos"
+           :settings-page/login-prompt "Para acessar novos recursos antes de qualquer outra pessoa, você deve ser um Patrocinador Coletivo Aberto ou Apoiador do Logseq e, portanto, fazer o login primeiro."
+           :settings-page/preferred-pasting-file "Arquivo preferência para colar"
+           :settings-page/tab-assets "Recursos"
+           :whiteboard/link-whiteboard-or-block "Vincular quadro branco/página/bloco"}
 
 
    :pt-PT {:on-boarding/demo-graph "Isto é um grafo de demonstração, nenhuma mudança será guardada até abrir uma pasta local."
    :pt-PT {:on-boarding/demo-graph "Isto é um grafo de demonstração, nenhuma mudança será guardada até abrir uma pasta local."
            :on-boarding/add-graph "Adicionar grafo"
            :on-boarding/add-graph "Adicionar grafo"
@@ -3529,7 +3627,90 @@
         :settings-page/custom-global-configuration "Configuração global personalizada"
         :settings-page/custom-global-configuration "Configuração global personalizada"
         :settings-page/edit-global-config-edn "Editar config.edn global"
         :settings-page/edit-global-config-edn "Editar config.edn global"
         :settings-page/sync "Sincronizar"
         :settings-page/sync "Sincronizar"
-        :settings-page/tab-features "Recursos"}
+        :settings-page/tab-features "Recursos"
+
+         :all-whiteboards "Todos os quadros brancos"
+         :auto-heading "Título automático"
+         :go-to-whiteboard "Ir para o quadro branco"
+         :heading "Título {1}"
+         :not-available-in-mode "Não disponível no modo {1}"
+         :remove-heading "Remover título"
+         :untitled "Sem título"
+         :accessibility/skip-to-main-content "Ir para o conteúdo principal"
+         :color/blue "Azul"
+         :color/gray "Cinza"
+         :color/green "Verde"
+         :color/pink "Rosa"
+         :color/purple "Roxo"
+         :color/red "Vermelho"
+         :color/yellow "Amarelo"
+         :conversion/non-desktop "O diretório Graph em versões antigas precisa ser convertido para o novo formato. Por favor, use o aplicativo de desktop para fazer a conversão."
+         :conversion/write-filename-format "Aplicar formato para arquivos recebidos"
+         :file/validate-existing-file-error "A página já existe com outro arquivo: {1}, arquivo atual: {2}. Por favor, mantenha apenas um deles e reindexe seu gráfico."
+         :file-rn/affected-pages "Páginas afetadas após a alteração do formato"
+         :file-rn/all-action "Aplicar todas as ações!"
+         :file-rn/apply-rename "Aplicar a operação de renomeação de arquivo"
+         :file-rn/close-panel "Fechar o painel"
+         :file-rn/confirm-proceed "Atualizar formato!"
+         :file-rn/filename-desc-1 "Esta configuração define como uma página é armazenada em um arquivo. Logseq armazena uma página em um arquivo com o mesmo nome."
+         :file-rn/filename-desc-2 "Alguns caracteres como \"/\" ou \"?\" são inválidos para um nome de arquivo."
+         :file-rn/filename-desc-3 "Logseq substitui caracteres inválidos por seu URL codificado equivalente para torná-los válidos (por exemplo, \"?\" torna-se \"%3F\")."
+         :file-rn/filename-desc-4 "O separador de espaço de nomes \"/\" também é substituído por \"___\" (sublinhado triplo) para consideração estética."
+         :file-rn/format-deprecated "Você está usando um formato desatualizado. A atualização para o formato mais recente é altamente recomendada. Faça backup de seus dados e feche os clientes Logseq em outros dispositivos antes da operação."
+         :file-rn/instruct-1 "É um processo de 2 etapas para atualizar o formato do nome do arquivo:"
+         :file-rn/instruct-2 "1. Clique em "
+         :file-rn/instruct-3 "2. Siga as instruções abaixo para renomear os arquivos para o novo formato:"
+         :file-rn/legend "🟢 Ações opcionais de renomeação; 🟡 Ação de renomeação necessária para evitar mudança de título; 🔴 Mudança de última hora."
+         :file-rn/need-action "As ações de renomeação de arquivo são sugeridas para corresponder ao novo formato. A reindexação é necessária em todos os dispositivos quando os arquivos renomeados são sincronizados."
+         :file-rn/no-action "Bom trabalho! Nenhuma ação adicional necessária."
+         :file-rn/optional-rename "Sugestão: "
+         :file-rn/or-select-actions " ou renomear individualmente os arquivos abaixo, então "
+         :file-rn/or-select-actions-2 ". Essas ações não estarão disponíveis depois que você fechar este painel."
+         :file-rn/otherwise-breaking "Ou o título se tornará"
+         :file-rn/re-index "A reindexação é fortemente recomendada após a renomeação dos arquivos e em outros dispositivos após a sincronização."
+         :file-rn/rename "renomeie o arquivo \"{1}\" para \"{2}\""
+         :file-rn/select-confirm-proceed "Dev: formato de gravação"
+         :file-rn/select-format "(Opção do modo de desenvolvedor, perigoso!) Selecione o formato do nome do arquivo"
+         :file-rn/suggest-rename "Ação necessária: "
+         :file-rn/unreachable-title "Aviso! O nome da página se tornará {1} no formato de nome de arquivo atual, a menos que a propriedade `title::` seja definida manualmente"
+         :left-side-bar/create "Criar"
+         :left-side-bar/new-whiteboard "Novo quadro branco"
+         :notification/clear-all "Limpar tudo"
+         :on-boarding/closed-feature "Fechado {1}"
+         :on-boarding/tour-whiteboard-home "{1} Início para seus quadros brancos"
+         :on-boarding/tour-whiteboard-home-description "Os quadros brancos têm sua própria seção no aplicativo, onde você pode vê-los rapidamente, criar novos ou excluí-los facilmente."
+         :on-boarding/tour-whiteboard-new "{1} Criar novo quadro branco"
+         :on-boarding/tour-whiteboard-new-description "Existem várias maneiras de criar um novo quadro branco. Um deles está sempre aqui no painel."
+         :on-boarding/welcome-whiteboard-modal-description "Quadros brancos são uma ótima ferramenta para brainstorming e organização. Agora você pode colocar qualquer um dos seus pensamentos da base de conhecimento ou novos próximos uns dos outros em uma tela espacial para conectar, associar e entender de novas maneiras."
+         :on-boarding/welcome-whiteboard-modal-skip "Pular"
+         :on-boarding/welcome-whiteboard-modal-start "Iniciar quadro branco"
+         :on-boarding/welcome-whiteboard-modal-title "Um novo quadro para seus pensamentos."
+         :page/show-whiteboards "Mostrar quadros brancos"
+         :pdf/doc-metadata "Metadados do documento"
+         :pdf/hl-block-colored "Rótulo colorido para bloco de destaque"
+         :plugin.install-from-file/menu-title "Instalar de plugins.edn"
+         :plugin.install-from-file/notice "Os seguintes plugins substituirão seus plugins:"
+         :plugin.install-from-file/success "Todos os plugins instalados"
+         :plugin.install-from-file/title "Instalar plugins de plugins.edn"
+         :right-side-bar/separator "Manipulador de redimensionamento da barra lateral direita"
+         :right-side-bar/whiteboards "Quadros brancos"
+         :search-item/block "Bloco"
+         :search-item/file "Arquivo"
+         :search-item/page "Página"
+         :search-item/whiteboard "Quadro branco"
+         :settings-page/alpha-features "Recursos em fase Alfa"
+         :settings-page/beta-features "Recursos em fase Beta"
+         :settings-page/clear-cache-warning "A limpeza do cache descartará os gráficos abertos. Você perderá as alterações não salvas."
+         :settings-page/custom-date-format-warning "É necessário reindexar! As referências de periódicos existentes seriam quebradas!"
+         :settings-page/disable-sentry-desc "A Logseq nunca coletará seu banco de dados gráfico local ou venderá seus dados."
+         :settings-page/edit-setting "Editar"
+         :settings-page/enable-whiteboards "Quadros brancos"
+         :settings-page/filename-format "Formato dos nomes de arquivos"
+         :settings-page/login-prompt "Para acessar novos recursos antes de qualquer outra pessoa, você deve ser um Patrocinador Coletivo Aberto ou Apoiador do Logseq e, portanto, fazer o login primeiro."
+         :settings-page/preferred-pasting-file "Arquivo preferência para colar"
+         :settings-page/tab-assets "Recursos"
+         :whiteboard/link-whiteboard-or-block "Vincular quadro branco/página/bloco"
+         :new-whiteboard "Novo quadro branco"}
 
 
    :ru {:on-boarding/demo-graph "Это демонстрационный граф, изменения не будут сохранены, пока вы не откроете локальный файл."
    :ru {:on-boarding/demo-graph "Это демонстрационный граф, изменения не будут сохранены, пока вы не откроете локальный файл."
         :on-boarding/add-graph "Добавить новый граф"
         :on-boarding/add-graph "Добавить новый граф"

+ 20 - 19
src/main/frontend/extensions/pdf/assets.cljs

@@ -184,25 +184,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!
        })]))
        })]))
 
 

+ 151 - 109
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]
@@ -604,28 +605,29 @@
   keep this `FileMetadata` in result"
   keep this `FileMetadata` in result"
   [s1 s2]
   [s1 s2]
   (reduce
   (reduce
-   (fn [result item]
-     (let [path (:path item)
-           encrypted-path (:encrypted-path item)
-           checksum (:etag item)
-           last-modified (:last-modified item)]
-       (if (some
-            #(cond
-               (not= encrypted-path (:encrypted-path %))
-               false
-               (= checksum (:etag %))
-               true
-               (>= last-modified (:last-modified %))
-               false
-               ;; these special files have higher priority in s1
-               (contains? higher-priority-remote-files path)
-               false
-               (< last-modified (:last-modified %))
-               true)
-            s2)
-         result
-         (conj result item))))
-   #{} s1))
+     (fn [result item]
+       (let [path (:path item)
+             lower-case-path (some-> path string/lower-case)
+             ;; encrypted-path (:encrypted-path item)
+             checksum (:etag item)
+             last-modified (:last-modified item)]
+         (if (some
+              #(cond
+                 (not= lower-case-path (some-> (:path %) string/lower-case))
+                 false
+                 (= checksum (:etag %))
+                 true
+                 (>= last-modified (:last-modified %))
+                 false
+                 ;; these special files have higher priority in s1
+                 (contains? higher-priority-remote-files path)
+                 false
+                 (< last-modified (:last-modified %))
+                 true)
+              s2)
+           result
+           (conj result item))))
+     #{} s1))
 
 
 (comment
 (comment
  (defn map->FileMetadata [m]
  (defn map->FileMetadata [m]
@@ -1179,6 +1181,36 @@
     (let [encrypted-paths-to-drop (set (persistent! *encrypted-paths-to-drop))]
     (let [encrypted-paths-to-drop (set (persistent! *encrypted-paths-to-drop))]
       (filterv #(not (contains? encrypted-paths-to-drop (:encrypted-path %))) file-meta-list))))
       (filterv #(not (contains? encrypted-paths-to-drop (:encrypted-path %))) file-meta-list))))
 
 
+(defn- filter-case-different-same-files
+  "filter case-different-but-same-name files, last-modified one wins"
+  [file-meta-list encrypted-path->path-map]
+  (let [seen (volatile! {})]
+    (loop [result-file-meta-list (transient {})
+           [f & others] file-meta-list]
+      (if f
+        (let [origin-path (get encrypted-path->path-map (:encrypted-path f))
+              _ (assert (some? origin-path) f)
+              path (string/lower-case origin-path)
+              last-modified (:last-modified f)
+              last-modified-seen (get @seen path)]
+          (cond
+            (or (and path (nil? last-modified-seen))
+                (and path (some? last-modified-seen) (> last-modified last-modified-seen)))
+            ;; 1. not found in seen
+            ;; 2. found in seen, but current f wins
+            (do (vswap! seen conj [path last-modified])
+                (recur (conj! result-file-meta-list [path f]) others))
+
+            (and path (some? last-modified-seen) (<= last-modified last-modified-seen))
+            ;; found in seen, and seen-f has more recent last-modified epoch
+            (recur result-file-meta-list others)
+
+            :else
+            (do (println :debug-filter-case-different-same-files:unreachable f path)
+                (recur result-file-meta-list others))))
+        (vals (persistent! result-file-meta-list))))))
+
+
 (extend-type RemoteAPI
 (extend-type RemoteAPI
   IRemoteAPI
   IRemoteAPI
   (<user-info [this]
   (<user-info [this]
@@ -1189,28 +1221,28 @@
      (let [file-meta-list      (transient #{})
      (let [file-meta-list      (transient #{})
            encrypted-path-list (transient [])
            encrypted-path-list (transient [])
            exp-r
            exp-r
-                               (<!
-                                (go-loop [continuation-token nil]
-                                  (let [r (<! (.<request this "get_all_files"
-                                                         (into
-                                                          {}
-                                                          (remove (comp nil? second)
-                                                                  {:GraphUUID graph-uuid :ContinuationToken continuation-token}))))]
-                                    (if (instance? ExceptionInfo r)
-                                      r
-                                      (let [next-continuation-token (:NextContinuationToken r)
-                                            objs                    (:Objects r)]
-                                        (apply conj! encrypted-path-list (map (comp remove-user-graph-uuid-prefix :Key) objs))
-                                        (apply conj! file-meta-list
-                                               (map
-                                                #(hash-map :checksum (:checksum %)
-                                                           :encrypted-path (remove-user-graph-uuid-prefix (:Key %))
-                                                           :size (:Size %)
-                                                           :last-modified (:LastModified %)
-                                                           :txid (:Txid %))
-                                                objs))
-                                        (when-not (empty? next-continuation-token)
-                                          (recur next-continuation-token)))))))]
+           (<!
+            (go-loop [continuation-token nil]
+              (let [r (<! (.<request this "get_all_files"
+                                     (into
+                                      {}
+                                      (remove (comp nil? second)
+                                              {:GraphUUID graph-uuid :ContinuationToken continuation-token}))))]
+                (if (instance? ExceptionInfo r)
+                  r
+                  (let [next-continuation-token (:NextContinuationToken r)
+                        objs                    (:Objects r)]
+                    (apply conj! encrypted-path-list (map (comp remove-user-graph-uuid-prefix :Key) objs))
+                    (apply conj! file-meta-list
+                           (map
+                            #(hash-map :checksum (:checksum %)
+                                       :encrypted-path (remove-user-graph-uuid-prefix (:Key %))
+                                       :size (:Size %)
+                                       :last-modified (:LastModified %)
+                                       :txid (:Txid %))
+                            objs))
+                    (when-not (empty? next-continuation-token)
+                      (recur next-continuation-token)))))))]
        (if (instance? ExceptionInfo exp-r)
        (if (instance? ExceptionInfo exp-r)
          exp-r
          exp-r
          (let [file-meta-list*      (persistent! file-meta-list)
          (let [file-meta-list*      (persistent! file-meta-list)
@@ -1229,7 +1261,9 @@
                                   true
                                   true
                                   (:txid %)
                                   (:txid %)
                                   nil)
                                   nil)
-                 (filter-files-with-unnormalized-path file-meta-list* encrypted-path->path-map))))))))))
+                 (-> file-meta-list*
+                     (filter-files-with-unnormalized-path encrypted-path->path-map)
+                     (filter-case-different-same-files encrypted-path->path-map)))))))))))
 
 
   (<get-remote-files-meta [this graph-uuid filepaths]
   (<get-remote-files-meta [this graph-uuid filepaths]
     {:pre [(coll? filepaths)]}
     {:pre [(coll? filepaths)]}
@@ -1286,59 +1320,59 @@
                                                (:Transactions r))
                                                (:Transactions r))
                encrypted-paths           (mapcat :paths txns-with-encrypted-paths)
                encrypted-paths           (mapcat :paths txns-with-encrypted-paths)
                encrypted-path->path-map
                encrypted-path->path-map
-                                         (zipmap
-                                          encrypted-paths
-                                          (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
+               (zipmap
+                encrypted-paths
+                (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
                txns
                txns
-                                         (mapv
-                                          (fn [txn]
-                                            (assoc txn :paths (mapv #(get encrypted-path->path-map %) (:paths txn))))
-                                          txns-with-encrypted-paths)]
+               (mapv
+                (fn [txn]
+                  (assoc txn :paths (mapv #(get encrypted-path->path-map %) (:paths txn))))
+                txns-with-encrypted-paths)]
            txns)))))
            txns)))))
 
 
   (<get-diff [this graph-uuid from-txid]
   (<get-diff [this graph-uuid from-txid]
-   ;; TODO: path in transactions should be relative path(now s3 key, which includes graph-uuid and user-uuid)
+    ;; TODO: path in transactions should be relative path(now s3 key, which includes graph-uuid and user-uuid)
     (user/<wrap-ensure-id&access-token
     (user/<wrap-ensure-id&access-token
      (let [r (<! (.<request this "get_diff" {:GraphUUID graph-uuid :FromTXId from-txid}))]
      (let [r (<! (.<request this "get_diff" {:GraphUUID graph-uuid :FromTXId from-txid}))]
        (if (instance? ExceptionInfo r)
        (if (instance? ExceptionInfo r)
          r
          r
          (let [txns-with-encrypted-paths (sort-by :TXId (:Transactions r))
          (let [txns-with-encrypted-paths (sort-by :TXId (:Transactions r))
                txns-with-encrypted-paths*
                txns-with-encrypted-paths*
-                                         (mapv
-                                          (fn [txn]
-                                            (assoc txn :TXContent
-                                                   (mapv
-                                                    (fn [[to-path from-path checksum]]
-                                                      [(remove-user-graph-uuid-prefix to-path)
-                                                       (some-> from-path remove-user-graph-uuid-prefix)
-                                                       checksum])
-                                                    (:TXContent txn))))
-                                          txns-with-encrypted-paths)
+               (mapv
+                (fn [txn]
+                  (assoc txn :TXContent
+                         (mapv
+                          (fn [[to-path from-path checksum]]
+                            [(remove-user-graph-uuid-prefix to-path)
+                             (some-> from-path remove-user-graph-uuid-prefix)
+                             checksum])
+                          (:TXContent txn))))
+                txns-with-encrypted-paths)
                encrypted-paths
                encrypted-paths
-                                         (mapcat
-                                          (fn [txn]
-                                            (remove
-                                             #(or (nil? %) (not (string/starts-with? % "e.")))
-                                             (mapcat
-                                              (fn [[to-path from-path _checksum]] [to-path from-path])
-                                              (:TXContent txn))))
-                                          txns-with-encrypted-paths*)
+               (mapcat
+                (fn [txn]
+                  (remove
+                   #(or (nil? %) (not (string/starts-with? % "e.")))
+                   (mapcat
+                    (fn [[to-path from-path _checksum]] [to-path from-path])
+                    (:TXContent txn))))
+                txns-with-encrypted-paths*)
                encrypted-path->path-map
                encrypted-path->path-map
-                                         (zipmap
-                                          encrypted-paths
-                                          (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
+               (zipmap
+                encrypted-paths
+                (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
                txns
                txns
-                                         (mapv
-                                          (fn [txn]
-                                            (assoc
-                                              txn :TXContent
-                                              (mapv
-                                               (fn [[to-path from-path checksum]]
-                                                 [(get encrypted-path->path-map to-path to-path)
-                                                  (some->> from-path (get encrypted-path->path-map))
-                                                  checksum])
-                                               (:TXContent txn))))
-                                          txns-with-encrypted-paths*)]
+               (mapv
+                (fn [txn]
+                  (assoc
+                   txn :TXContent
+                   (mapv
+                    (fn [[to-path from-path checksum]]
+                      [(get encrypted-path->path-map to-path to-path)
+                       (some->> from-path (get encrypted-path->path-map))
+                       checksum])
+                    (:TXContent txn))))
+                txns-with-encrypted-paths*)]
            [txns
            [txns
             (:TXId (last txns))
             (:TXId (last txns))
             (:TXId (first txns))])))))
             (:TXId (first txns))])))))
@@ -2314,7 +2348,9 @@
               {:stop true})
               {:stop true})
           (let [remote-all-files-meta   remote-all-files-meta-or-exp
           (let [remote-all-files-meta   remote-all-files-meta-or-exp
                 local-all-files-meta    (<! local-all-files-meta-c)
                 local-all-files-meta    (<! local-all-files-meta-c)
-                diff-remote-files       (diff-file-metadata-sets remote-all-files-meta local-all-files-meta)
+                {diff-remote-files :result elapsed-time :time}
+                (util/with-time (diff-file-metadata-sets remote-all-files-meta local-all-files-meta))
+                _ (println ::diff-file-metadata-sets-elapsed-time elapsed-time "ms")
                 recent-10-days-range    ((juxt #(tc/to-long (t/minus % (t/days 10))) #(tc/to-long %)) (t/today))
                 recent-10-days-range    ((juxt #(tc/to-long (t/minus % (t/days 10))) #(tc/to-long %)) (t/today))
                 sorted-diff-remote-files
                 sorted-diff-remote-files
                                         (sort-by
                                         (sort-by
@@ -2476,12 +2512,14 @@
                  true)
                  true)
                (or (string/starts-with? (.-dir e) base-path)
                (or (string/starts-with? (.-dir e) base-path)
                    (string/starts-with? (str "file://" (.-dir e)) base-path)) ; valid path prefix
                    (string/starts-with? (str "file://" (.-dir e)) base-path)) ; valid path prefix
-               (not (ignored? e))     ;not ignored
+               (not (ignored? e))       ;not ignored
                ;; download files will also trigger file-change-events, ignore them
                ;; download files will also trigger file-change-events, ignore them
-               (when-some [recent-remote->local-file-item
-                           (<! (<file-change-event=>recent-remote->local-file-item
-                                graph-uuid e))]
-                 (not (contains? (:recent-remote->local-files @*sync-state) recent-remote->local-file-item)))))))
+               (if (= "unlink" (:type e))
+                 true
+                 (when-some [recent-remote->local-file-item
+                             (<! (<file-change-event=>recent-remote->local-file-item
+                                  graph-uuid e))]
+                   (not (contains? (:recent-remote->local-files @*sync-state) recent-remote->local-file-item))))))))
 
 
   (set-remote->local-syncer! [_ s] (set! remote->local-syncer s))
   (set-remote->local-syncer! [_ s] (set! remote->local-syncer s))
 
 
@@ -2504,7 +2542,7 @@
        (fn [e]
        (fn [e]
          (go
          (go
            (and (rsapi-ready? rsapi graph-uuid)
            (and (rsapi-ready? rsapi graph-uuid)
-                 (<! (<fast-filter-e-fn e))
+                (<! (<fast-filter-e-fn e))
                 (do
                 (do
                   (swap! *sync-state sync-state--add-queued-local->remote-files e)
                   (swap! *sync-state sync-state--add-queued-local->remote-files e)
                   (let [v (<! (<filter-local-changes-pred e base-path graph-uuid))]
                   (let [v (<! (<filter-local-changes-pred e base-path graph-uuid))]
@@ -2688,7 +2726,7 @@
                ^:mutable ratelimit-local-changes-chan
                ^:mutable ratelimit-local-changes-chan
                *txid *txid-for-get-deletion-log
                *txid *txid-for-get-deletion-log
                ^:mutable state ^:mutable remote-change-chan ^:mutable *ws *stopped? *paused?
                ^:mutable state ^:mutable remote-change-chan ^:mutable *ws *stopped? *paused?
-               ^:mutable ops-chan
+               ^:mutable ops-chan ^:mutable app-awake-from-sleep-chan
                ;; control chans
                ;; control chans
                private-full-sync-chan private-remote->local-sync-chan
                private-full-sync-chan private-remote->local-sync-chan
                private-remote->local-full-sync-chan private-pause-resume-chan]
                private-remote->local-full-sync-chan private-pause-resume-chan]
@@ -2720,6 +2758,7 @@
 
 
   (start [this]
   (start [this]
     (set! ops-chan (chan (async/dropping-buffer 10)))
     (set! ops-chan (chan (async/dropping-buffer 10)))
+    (set! app-awake-from-sleep-chan (chan (async/sliding-buffer 1)))
     (set! *ws (atom nil))
     (set! *ws (atom nil))
     (set! remote-change-chan (ws-listen! graph-uuid *ws))
     (set! remote-change-chan (ws-listen! graph-uuid *ws))
     (set! ratelimit-local-changes-chan (<ratelimit local->remote-syncer local-changes-revised-chan))
     (set! ratelimit-local-changes-chan (<ratelimit local->remote-syncer local-changes-revised-chan))
@@ -2728,23 +2767,26 @@
     (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 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!
-             private-remote->local-full-sync-chan {:remote->local-full-sync true}
-             private-remote->local-sync-chan {:remote->local true}
-             private-full-sync-chan {:local->remote-full-sync true}
-             private-pause-resume-chan ([v] (if v {:resume true} {:pause true}))
-             remote-change-chan ([v] (println "remote change:" v) {:remote->local v})
-             ratelimit-local-changes-chan ([v]
-                                           (if (nil? v)
-                                             {:stop true}
-                                             (let [rest-v (util/drain-chan ratelimit-local-changes-chan)
-                                                   vs     (cons v rest-v)]
-                                               (println "local changes:" vs)
-                                               {:local->remote vs})))
-             (timeout (* 20 60 1000)) {:local->remote-full-sync true}
-             :priority true)]
+              private-remote->local-full-sync-chan {:remote->local-full-sync true}
+              private-remote->local-sync-chan {:remote->local true}
+              private-full-sync-chan {:local->remote-full-sync true}
+              private-pause-resume-chan ([v] (if v {:resume true} {:pause true}))
+              remote-change-chan ([v] (println "remote change:" v) {:remote->local v})
+              ratelimit-local-changes-chan ([v]
+                                            (if (nil? v)
+                                              {:stop true}
+                                              (let [rest-v (util/drain-chan ratelimit-local-changes-chan)
+                                                    vs     (cons v rest-v)]
+                                                (println "local changes:" vs)
+                                                {:local->remote vs})))
+              app-awake-from-sleep-chan {:remote->local true}
+              (timeout (* 20 60 1000)) {:local->remote-full-sync true}
+              (timeout (* 10 60 1000)) {:remote->local true}
+              :priority true)]
         (cond
         (cond
           stop
           stop
           nil
           nil
@@ -3033,6 +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 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)
@@ -3066,7 +3109,7 @@
     (.set-local->remote-syncer! remote->local-syncer local->remote-syncer)
     (.set-local->remote-syncer! remote->local-syncer local->remote-syncer)
     (swap! *sync-state sync-state--update-current-syncing-graph-uuid graph-uuid)
     (swap! *sync-state sync-state--update-current-syncing-graph-uuid graph-uuid)
     (->SyncManager user-uuid graph-uuid base-path *sync-state local->remote-syncer remote->local-syncer remoteapi-with-stop
     (->SyncManager user-uuid graph-uuid base-path *sync-state local->remote-syncer remote->local-syncer remoteapi-with-stop
-                   nil *txid *txid-for-get-deletion-log nil nil nil *stopped? *paused? nil (chan 1) (chan 1) (chan 1) (chan 1))))
+                   nil *txid *txid-for-get-deletion-log nil nil nil *stopped? *paused? nil nil (chan 1) (chan 1) (chan 1) (chan 1))))
 
 
 (defn sync-manager-singleton
 (defn sync-manager-singleton
   [user-uuid graph-uuid base-path repo txid *sync-state]
   [user-uuid graph-uuid base-path repo txid *sync-state]
@@ -3296,5 +3339,4 @@
 (comment
 (comment
  (def *x (atom nil))
  (def *x (atom nil))
  (add-tap (fn [v] (reset! *x v)))
  (add-tap (fn [v] (reset! *x v)))
-
  )
  )

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

@@ -228,6 +228,7 @@
 
 
   (db/run-batch-txs!)
   (db/run-batch-txs!)
   (file/<ratelimit-file-writes!)
   (file/<ratelimit-file-writes!)
+  (util/<app-wake-up-from-sleep-loop (atom false))
 
 
   (when config/dev?
   (when config/dev?
     (enable-datalog-console))
     (enable-datalog-console))

+ 4 - 0
src/main/frontend/handler/config.cljs

@@ -44,6 +44,10 @@
   (let [logical-outdenting? (state/logical-outdenting?)]
   (let [logical-outdenting? (state/logical-outdenting?)]
     (set-config! :editor/logical-outdenting? (not logical-outdenting?))))
     (set-config! :editor/logical-outdenting? (not logical-outdenting?))))
 
 
+(defn toggle-show-full-blocks! []
+  (let [show-full-blocks? (state/show-full-blocks?)]
+    (set-config! :ui/show-full-blocks? (not show-full-blocks?))))
+
 (defn toggle-ui-enable-tooltip! []
 (defn toggle-ui-enable-tooltip! []
   (let [enable-tooltip? (state/enable-tooltip?)]
   (let [enable-tooltip? (state/enable-tooltip?)]
     (set-config! :ui/enable-tooltip? (not enable-tooltip?))))
     (set-config! :ui/enable-tooltip? (not enable-tooltip?))))

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

+ 11 - 7
src/main/frontend/modules/file/core.cljs

@@ -136,7 +136,7 @@
 (defn- remove-transit-ids [block] (dissoc block :db/id :block/file))
 (defn- remove-transit-ids [block] (dissoc block :db/id :block/file))
 
 
 (defn save-tree-aux!
 (defn save-tree-aux!
-  [page-block tree]
+  [page-block tree blocks-just-deleted?]
   (let [page-block (db/pull (:db/id page-block))
   (let [page-block (db/pull (:db/id page-block))
         file-db-id (-> page-block :block/file :db/id)
         file-db-id (-> page-block :block/file :db/id)
         file-path (-> (db-utils/entity file-db-id) :file/path)]
         file-path (-> (db-utils/entity file-db-id) :file/path)]
@@ -146,17 +146,21 @@
                            (up/ugly-pr-str {:blocks tree
                            (up/ugly-pr-str {:blocks tree
                                             :pages (list (remove-transit-ids page-block))})
                                             :pages (list (remove-transit-ids page-block))})
                            (string/triml))
                            (string/triml))
-                          (tree->file-content tree {:init-level init-level}))
-            files [[file-path new-content]]
-            repo (state/get-current-repo)]
-        (file-handler/alter-files-handler! repo files {} {}))
+                          (tree->file-content tree {:init-level init-level}))]
+        (if (and (string/blank? new-content)
+                 (not blocks-just-deleted?))
+          (state/pub-event! [:capture-error {:error (js/Error. "Empty content")
+                                             :payload {:file-path file-path}}])
+          (let [files [[file-path new-content]]
+                repo (state/get-current-repo)]
+            (file-handler/alter-files-handler! repo files {} {}))))
       ;; In e2e tests, "card" page in db has no :file/path
       ;; In e2e tests, "card" page in db has no :file/path
       (js/console.error "File path from page-block is not valid" page-block tree))))
       (js/console.error "File path from page-block is not valid" page-block tree))))
 
 
 (defn save-tree!
 (defn save-tree!
-  [page-block tree]
+  [page-block tree blocks-just-deleted?]
   {:pre [(map? page-block)]}
   {:pre [(map? page-block)]}
-  (let [ok-handler #(save-tree-aux! page-block tree)
+  (let [ok-handler #(save-tree-aux! page-block tree blocks-just-deleted?)
         file (or (:block/file page-block)
         file (or (:block/file page-block)
                  (when-let [page (:db/id (:block/page page-block))]
                  (when-let [page (:db/id (:block/page page-block))]
                    (:block/file (db-utils/entity page))))]
                    (:block/file (db-utils/entity page))))]

+ 34 - 29
src/main/frontend/modules/outliner/file.cljs

@@ -44,34 +44,37 @@
 
 
 
 
 (defn do-write-file!
 (defn do-write-file!
-  [repo page-db-id]
+  [repo page-db-id outliner-op]
   (let [page-block (db/pull repo '[*] page-db-id)
   (let [page-block (db/pull repo '[*] page-db-id)
         page-db-id (:db/id page-block)
         page-db-id (:db/id page-block)
         whiteboard? (= "whiteboard" (:block/type page-block))
         whiteboard? (= "whiteboard" (:block/type page-block))
-        blocks-count (model/get-page-blocks-count repo page-db-id)]
-    (if (or (and (> blocks-count 500)
-                 (not (state/input-idle? repo {:diff 3000}))) ;; long page
-            ;; when this whiteboard page is just being updated
-            (and whiteboard? (not (state/whiteboard-page-idle? repo page-block))))
-      (async/put! (state/get-file-write-chan) [repo page-db-id])
-      (let [pull-keys (if whiteboard? whiteboard-blocks-pull-keys-with-persisted-ids '[*])
-            blocks (model/get-page-blocks-no-cache repo (:block/name page-block) {:pull-keys pull-keys})
-            blocks (if whiteboard? (map cleanup-whiteboard-block blocks) blocks)]
-        (when-not (and (= 1 (count blocks))
-                       (string/blank? (:block/content (first blocks)))
-                       (nil? (:block/file page-block)))
-          (let [tree-or-blocks (if whiteboard? blocks
-                                   (tree/blocks->vec-tree repo blocks (:block/name page-block)))]
-            (if page-block
-              (file/save-tree! page-block tree-or-blocks)
-              (js/console.error (str "can't find page id: " page-db-id)))))))))
+        blocks-count (model/get-page-blocks-count repo page-db-id)
+        blocks-just-deleted? (and (zero? blocks-count)
+                                  (contains? #{:delete-blocks :move-blocks} outliner-op))]
+    (when (or (>= blocks-count 1) blocks-just-deleted?)
+      (if (or (and (> blocks-count 500)
+                   (not (state/input-idle? repo {:diff 3000}))) ;; long page
+              ;; when this whiteboard page is just being updated
+              (and whiteboard? (not (state/whiteboard-page-idle? repo page-block))))
+        (async/put! (state/get-file-write-chan) [repo page-db-id outliner-op])
+        (let [pull-keys (if whiteboard? whiteboard-blocks-pull-keys-with-persisted-ids '[*])
+              blocks (model/get-page-blocks-no-cache repo (:block/name page-block) {:pull-keys pull-keys})
+              blocks (if whiteboard? (map cleanup-whiteboard-block blocks) blocks)]
+          (when-not (and (= 1 (count blocks))
+                         (string/blank? (:block/content (first blocks)))
+                         (nil? (:block/file page-block)))
+            (let [tree-or-blocks (if whiteboard? blocks
+                                     (tree/blocks->vec-tree repo blocks (:block/name page-block)))]
+              (if page-block
+                (file/save-tree! page-block tree-or-blocks blocks-just-deleted?)
+                (js/console.error (str "can't find page id: " page-db-id))))))))))
 
 
 (defn write-files!
 (defn write-files!
   [pages]
   [pages]
   (when (seq pages)
   (when (seq pages)
     (when-not config/publishing?
     (when-not config/publishing?
-      (doseq [[repo page-id] (set pages)]
-        (try (do-write-file! repo page-id)
+      (doseq [[repo page-id outliner-op] (set pages)]
+        (try (do-write-file! repo page-id outliner-op)
              (catch :default e
              (catch :default e
                (notification/show!
                (notification/show!
                 [:div
                 [:div
@@ -81,15 +84,17 @@
                (log/error :file/write-file-error {:error e})))))))
                (log/error :file/write-file-error {:error e})))))))
 
 
 (defn sync-to-file
 (defn sync-to-file
-  [{page-db-id :db/id}]
-  (if (nil? page-db-id)
-    (notification/show!
-     "Write file failed, can't find the current page!"
-     :error)
-    (when-let [repo (state/get-current-repo)]
-      (if (:graph/importing @state/state) ; write immediately
-        (write-files! [[repo page-db-id]])
-        (async/put! (state/get-file-write-chan) [repo page-db-id (tc/to-long (t/now))])))))
+  ([page]
+   (sync-to-file page nil))
+  ([{page-db-id :db/id} outliner-op]
+   (if (nil? page-db-id)
+     (notification/show!
+      "Write file failed, can't find the current page!"
+      :error)
+     (when-let [repo (state/get-current-repo)]
+       (if (:graph/importing @state/state) ; write immediately
+         (write-files! [[repo page-db-id outliner-op]])
+         (async/put! (state/get-file-write-chan) [repo page-db-id outliner-op (tc/to-long (t/now))]))))))
 
 
 (def *writes-finished? (atom {}))
 (def *writes-finished? (atom {}))
 
 

+ 1 - 1
src/main/frontend/modules/outliner/pipeline.cljs

@@ -11,7 +11,7 @@
 (defn updated-page-hook
 (defn updated-page-hook
   [tx-report page]
   [tx-report page]
   (when-not (get-in tx-report [:tx-meta :created-from-journal-template?])
   (when-not (get-in tx-report [:tx-meta :created-from-journal-template?])
-    (file/sync-to-file page)))
+    (file/sync-to-file page (:outliner-op (:tx-meta tx-report)))))
 
 
 ;; TODO: it'll be great if we can calculate the :block/path-refs before any
 ;; TODO: it'll be great if we can calculate the :block/path-refs before any
 ;; outliner transaction, this way we can group together the real outliner tx
 ;; outliner transaction, this way we can group together the real outliner tx

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

@@ -565,6 +565,8 @@
              :shortcut.category/navigating  "Navigation"
              :shortcut.category/navigating  "Navigation"
              :shortcut.category/others  "Autres"
              :shortcut.category/others  "Autres"
              :shortcut.category/toggle  "Basculer"
              :shortcut.category/toggle  "Basculer"
+             :command.editor/select-parent "Sélectionnez le bloc parent"
+             :command.sidebar/close-top "Ferme l'élément supérieur dans la barre latérale droite"
              }
              }
 
 
    :af      {:shortcut.category/formatting           "Formatering"
    :af      {:shortcut.category/formatting           "Formatering"
@@ -1020,7 +1022,14 @@
              :command.go/electron-find-in-page        "Procurar texto na página"
              :command.go/electron-find-in-page        "Procurar texto na página"
              :command.go/electron-jump-to-the-next    "Ir para a próxima correspondência da sua pesquisa"
              :command.go/electron-jump-to-the-next    "Ir para a próxima correspondência da sua pesquisa"
              :command.go/electron-jump-to-the-previous "Voltar para a correspondência anterior da sua pesquisa"
              :command.go/electron-jump-to-the-previous "Voltar para a correspondência anterior da sua pesquisa"
-             :command.graph/re-index                  "Reindexar o grafo atual"}
+             :command.graph/re-index                  "Reindexar o grafo atual"
+             :command.editor/new-whiteboard           "Novo quadro branco"
+             :command.editor/select-parent            "Selecione o bloco pai"
+             :command.go/whiteboards                  "Ir para os quadros brancos"
+             :command.graph/export-as-html            "Exportar páginas de gráficos públicos como html"
+             :command.pdf/find                        "PDF: Pesquisar no documento PDF atual"
+             :command.sidebar/close-top               "Fechar item superior na barra lateral direita"
+             :command.ui/install-plugins-from-file    "Instalar plugins de plugins.edn"}
 
 
    :pt-BR   {:shortcut.category/formatting            "Formatação"
    :pt-BR   {:shortcut.category/formatting            "Formatação"
              :shortcut.category/basics                "Básico"
              :shortcut.category/basics                "Básico"
@@ -1144,7 +1153,15 @@
              :command.go/electron-find-in-page        "Localizar texto na página"
              :command.go/electron-find-in-page        "Localizar texto na página"
              :command.go/electron-jump-to-the-next    "Ir para a próxima correspondência da sua pesquisa"
              :command.go/electron-jump-to-the-next    "Ir para a próxima correspondência da sua pesquisa"
              :command.go/electron-jump-to-the-previous "Voltar para a correspondência anterior da sua pesquisa"
              :command.go/electron-jump-to-the-previous "Voltar para a correspondência anterior da sua pesquisa"
-             :command.graph/re-index                  "Reindexar o grafo atual"}
+             :command.graph/re-index                  "Reindexar o grafo atual"
+             
+             :command.editor/new-whiteboard           "Novo quadro branco"
+             :command.editor/select-parent            "Selecione o bloco pai"
+             :command.go/whiteboards                  "Ir para os quadros brancos"
+             :command.graph/export-as-html            "Exportar páginas de gráficos públicos como html"
+             :command.pdf/find                        "PDF: Pesquisar no documento PDF atual"
+             :command.sidebar/close-top               "Fechar item superior na barra lateral direita"
+             :command.ui/install-plugins-from-file    "Instalar plugins de plugins.edn"}
 
 
    :ja      {:shortcut.category/formatting            "フォーマット"
    :ja      {:shortcut.category/formatting            "フォーマット"
              :shortcut.category/basics                "基本操作"
              :shortcut.category/basics                "基本操作"
@@ -1522,7 +1539,8 @@
              :command.ui/install-plugins-from-file    "Eklentileri plugins.edn dosyasından yükleyin"
              :command.ui/install-plugins-from-file    "Eklentileri plugins.edn dosyasından yükleyin"
              :command.editor/toggle-open-blocks       "Açık blokları kapat/aç (tüm blokları daralt veya genişlet)"
              :command.editor/toggle-open-blocks       "Açık blokları kapat/aç (tüm blokları daralt veya genişlet)"
              :command.ui/toggle-cards                 "Kartları aç/kapat"
              :command.ui/toggle-cards                 "Kartları aç/kapat"
-             :command.git/commit                      "Git commit mesajı"}
+             :command.git/commit                      "Git commit mesajı"
+             :command.editor/select-parent            "Ebeveyn bloğunu seçin"}
 
 
    :ko      {:shortcut.category/formatting             "포맷"
    :ko      {:shortcut.category/formatting             "포맷"
              :shortcut.category/basics                "기본 동작"
              :shortcut.category/basics                "기본 동작"

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

+ 1 - 0
src/main/frontend/schema/handler/common_config.cljc

@@ -38,6 +38,7 @@
     [:block/content-max-length :int]
     [:block/content-max-length :int]
     [:ui/show-command-doc? :boolean]
     [:ui/show-command-doc? :boolean]
     [:ui/show-empty-bullets? :boolean]
     [:ui/show-empty-bullets? :boolean]
+    [:ui/show-full-blocks? :boolean]
     [:query/views [:map-of
     [:query/views [:map-of
                    :keyword
                    :keyword
                    [:sequential any?]]]
                    [:sequential any?]]]

+ 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 - 0
src/main/frontend/state.cljs

@@ -670,6 +670,10 @@ Similar to re-frame subscriptions"
   []
   []
   (:editor/logical-outdenting? (sub-config)))
   (:editor/logical-outdenting? (sub-config)))
 
 
+(defn show-full-blocks?
+  []
+  (:ui/show-full-blocks? (sub-config)))
+
 (defn preferred-pasting-file?
 (defn preferred-pasting-file?
   []
   []
   (:editor/preferred-pasting-file? (sub-config)))
   (:editor/preferred-pasting-file? (sub-config)))

+ 20 - 1
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]
@@ -1461,3 +1462,21 @@
           (reset! last-mem ret)
           (reset! last-mem ret)
           ret)
           ret)
         @last-mem))))
         @last-mem))))
+
+#?(:cljs
+   (do
+     (defn <app-wake-up-from-sleep-loop
+       "start a async/go-loop to check the app awake from sleep.
+Use (async/tap `pubsub/app-wake-up-from-sleep-mult`) to receive messages.
+Arg *stop: atom, reset to true to stop the loop"
+       [*stop]
+       (let [*last-activated-at (volatile! (tc/to-epoch (t/now)))]
+         (async/go-loop []
+           (if @*stop
+             (println :<app-wake-up-from-sleep-loop :stop)
+             (let [now-epoch (tc/to-epoch (t/now))]
+               (when (< @*last-activated-at (- now-epoch 10))
+                 (async/>! pubsub/app-wake-up-from-sleep-ch {:last-activated-at @*last-activated-at :now now-epoch}))
+               (vreset! *last-activated-at now-epoch)
+               (async/<! (async/timeout 5000))
+               (recur))))))))

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

文件差異過大導致無法顯示
+ 283 - 346
static/yarn.lock


+ 3 - 0
templates/config.edn

@@ -27,6 +27,9 @@
  ;; Show brackets around page references
  ;; Show brackets around page references
  ;; :ui/show-brackets? true
  ;; :ui/show-brackets? true
 
 
+ ;; Enable showing the body of blocks when referencing them.
+ :ui/show-full-blocks? false
+
  ;; Enable Block timestamp
  ;; Enable Block timestamp
  :feature/enable-block-timestamps? false
  :feature/enable-block-timestamps? false
 
 

+ 25 - 24
yarn.lock

@@ -992,6 +992,13 @@
     "@types/expect" "^1.20.4"
     "@types/expect" "^1.20.4"
     "@types/node" "*"
     "@types/node" "*"
 
 
+"@types/yauzl@^2.9.1":
+  version "2.10.0"
+  resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
+  integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==
+  dependencies:
+    "@types/node" "*"
+
 acorn-node@^1.8.2:
 acorn-node@^1.8.2:
   version "1.8.2"
   version "1.8.2"
   resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
   resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
@@ -1932,7 +1939,7 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
   integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
 
 
-concat-stream@^1.6.0, concat-stream@^1.6.2:
+concat-stream@^1.6.0:
   version "1.6.2"
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
   integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
   integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
@@ -2203,7 +2210,7 @@ dayjs@^1.10.0:
   resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93"
   resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93"
   integrity sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==
   integrity sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==
 
 
-debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
+debug@^2.2.0, debug@^2.3.3:
   version "2.6.9"
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -2509,14 +2516,14 @@ electron-to-chromium@^1.4.251:
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592"
   integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==
   integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==
 
 
-electron@19.1.8:
-  version "19.1.8"
-  resolved "https://registry.yarnpkg.com/electron/-/electron-19.1.8.tgz#3ce19c270ca86d05bbf0df5ceeaea2d23edc7083"
-  integrity sha512-UfPQdFjgKI0xCm1V5sV3iAVOs0kCwAE91xWzV5tI7ij14yOkxTdXp9BqTzFaSbQYLYxn6q1BUUe1nlzjJjzAnw==
+electron@20.3.8:
+  version "20.3.8"
+  resolved "https://registry.yarnpkg.com/electron/-/electron-20.3.8.tgz#94a4b2bb89a3e6bbeb824365cfd0f65658bc1a2c"
+  integrity sha512-P93ZWoJjXEVwsjUJ4q6640KZ4+rmWMZ9O5IB1eq/L9S52ZYsXo0o82afpPXq3j8vpneY0N5J62hgRzC1ZKzYmw==
   dependencies:
   dependencies:
     "@electron/get" "^1.14.1"
     "@electron/get" "^1.14.1"
     "@types/node" "^16.11.26"
     "@types/node" "^16.11.26"
-    extract-zip "^1.0.3"
+    extract-zip "^2.0.1"
 
 
 element-resize-detector@^1.1.14:
 element-resize-detector@^1.1.14:
   version "1.2.4"
   version "1.2.4"
@@ -2802,15 +2809,16 @@ extglob@^2.0.4:
     snapdragon "^0.8.1"
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
     to-regex "^3.0.1"
 
 
-extract-zip@^1.0.3:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927"
-  integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==
+extract-zip@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
+  integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
   dependencies:
   dependencies:
-    concat-stream "^1.6.2"
-    debug "^2.6.9"
-    mkdirp "^0.5.4"
+    debug "^4.1.1"
+    get-stream "^5.1.0"
     yauzl "^2.10.0"
     yauzl "^2.10.0"
+  optionalDependencies:
+    "@types/yauzl" "^2.9.1"
 
 
 fancy-log@^1.3.2:
 fancy-log@^1.3.2:
   version "1.3.3"
   version "1.3.3"
@@ -4642,13 +4650,6 @@ mixin-deep@^1.2.0:
     for-in "^1.0.2"
     for-in "^1.0.2"
     is-extendable "^1.0.1"
     is-extendable "^1.0.1"
 
 
-mkdirp@^0.5.4:
-  version "0.5.6"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
-  integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
-  dependencies:
-    minimist "^1.2.6"
-
 mkdirp@^1.0.3:
 mkdirp@^1.0.3:
   version "1.0.4"
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
@@ -4682,9 +4683,9 @@ mute-stdout@^1.0.0:
   integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==
   integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==
 
 
 nan@^2.12.1:
 nan@^2.12.1:
-  version "2.16.0"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916"
-  integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==
+  version "2.17.0"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
+  integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
 
 
 nanoid@^3.3.4:
 nanoid@^3.3.4:
   version "3.3.4"
   version "3.3.4"

部分文件因文件數量過多而無法顯示