Przeglądaj źródła

Merge remote-tracking branch 'origin/master' into enhance/rtc-migrate

rcmerci 3 miesięcy temu
rodzic
commit
c139e6be93
100 zmienionych plików z 2356 dodań i 317 usunięć
  1. 3 1
      .clj-kondo/config.edn
  2. 2 11
      .github/workflows/build-android.yml
  3. 27 27
      .github/workflows/build-desktop-release.yml
  4. 1 1
      .github/workflows/build-ios-release.yml
  5. 1 1
      .github/workflows/clj-e2e.yml
  6. 1 1
      .github/workflows/clj-rtc-e2e.yml
  7. 1 1
      .github/workflows/deploy-db-test-pages.yml
  8. 3 5
      CONTRIBUTING.md
  9. 1 2
      README.md
  10. 7 8
      android/app/capacitor.build.gradle
  11. 13 7
      android/app/src/main/AndroidManifest.xml
  12. 8 8
      android/app/src/main/assets/capacitor.plugins.json
  13. 29 0
      android/app/src/main/java/com/logseq/app/MainActivity.java
  14. BIN
      android/app/src/main/res/drawable-land-night-hdpi/splash.png
  15. BIN
      android/app/src/main/res/drawable-land-night-ldpi/splash.png
  16. BIN
      android/app/src/main/res/drawable-land-night-mdpi/splash.png
  17. BIN
      android/app/src/main/res/drawable-land-night-xhdpi/splash.png
  18. BIN
      android/app/src/main/res/drawable-land-night-xxhdpi/splash.png
  19. BIN
      android/app/src/main/res/drawable-land-night-xxxhdpi/splash.png
  20. BIN
      android/app/src/main/res/drawable-night/splash.png
  21. BIN
      android/app/src/main/res/drawable-port-night-hdpi/splash.png
  22. BIN
      android/app/src/main/res/drawable-port-night-ldpi/splash.png
  23. BIN
      android/app/src/main/res/drawable-port-night-mdpi/splash.png
  24. BIN
      android/app/src/main/res/drawable-port-night-xhdpi/splash.png
  25. BIN
      android/app/src/main/res/drawable-port-night-xxhdpi/splash.png
  26. BIN
      android/app/src/main/res/drawable-port-night-xxxhdpi/splash.png
  27. BIN
      android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
  28. BIN
      android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
  29. BIN
      android/app/src/main/res/mipmap-ldpi/ic_launcher_background.png
  30. BIN
      android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png
  31. BIN
      android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
  32. BIN
      android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
  33. BIN
      android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
  34. BIN
      android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
  35. BIN
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
  36. BIN
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
  37. BIN
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
  38. BIN
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
  39. 3 3
      android/app/src/main/res/values/colors.xml
  40. 6 6
      android/capacitor.settings.gradle
  41. BIN
      assets/icon-background.png
  42. BIN
      assets/icon-foreground.png
  43. BIN
      assets/splash-dark.png
  44. 11 1
      capacitor.config.ts
  45. 2 1
      clj-e2e/.clj-kondo/config.edn
  46. 8 2
      clj-e2e/test/logseq/e2e/editor_basic_test.clj
  47. 5 1
      clj-e2e/test/logseq/e2e/plugins_basic_test.clj
  48. 1 1
      deps.edn
  49. 2 0
      deps/cli/.carve/ignore
  50. 5 1
      deps/cli/.clj-kondo/config.edn
  51. 9 1
      deps/cli/CHANGELOG.md
  52. 16 4
      deps/cli/README.md
  53. 1 1
      deps/cli/bb.edn
  54. 5 3
      deps/cli/package.json
  55. 38 13
      deps/cli/src/logseq/cli.cljs
  56. 14 0
      deps/cli/src/logseq/cli/commands/append.cljs
  57. 80 0
      deps/cli/src/logseq/cli/commands/export.cljs
  58. 12 13
      deps/cli/src/logseq/cli/commands/export_edn.cljs
  59. 3 3
      deps/cli/src/logseq/cli/commands/query.cljs
  60. 4 4
      deps/cli/src/logseq/cli/commands/search.cljs
  61. 808 0
      deps/cli/src/logseq/cli/common/export/common.cljs
  62. 493 0
      deps/cli/src/logseq/cli/common/export/text.cljs
  63. 21 4
      deps/cli/src/logseq/cli/common/file.cljs
  64. 1 1
      deps/cli/src/logseq/cli/common/graph.cljs
  65. 36 0
      deps/cli/src/logseq/cli/common/util.cljc
  66. 17 9
      deps/cli/src/logseq/cli/spec.cljs
  67. 21 1
      deps/cli/src/logseq/cli/text_util.cljs
  68. 14 6
      deps/cli/src/logseq/cli/util.cljs
  69. 418 5
      deps/cli/yarn.lock
  70. 1 1
      deps/common/package.json
  71. 4 13
      deps/common/src/logseq/common/defkeywords.cljc
  72. 0 1
      deps/common/src/logseq/common/util.cljs
  73. 7 5
      deps/common/src/logseq/common/util/date_time.cljs
  74. 3 3
      deps/common/yarn.lock
  75. 2 0
      deps/db/.carve/ignore
  76. 1 1
      deps/db/bb.edn
  77. 1 1
      deps/db/deps.edn
  78. 1 1
      deps/db/package.json
  79. 5 4
      deps/db/src/logseq/db.cljs
  80. 1 2
      deps/db/src/logseq/db/common/delete_blocks.cljs
  81. 1 1
      deps/db/src/logseq/db/common/entity_plus.cljc
  82. 10 4
      deps/db/src/logseq/db/common/initial_data.cljs
  83. 24 11
      deps/db/src/logseq/db/common/reference.cljs
  84. 8 2
      deps/db/src/logseq/db/common/view.cljs
  85. 3 9
      deps/db/src/logseq/db/file_based/rules.cljc
  86. 0 4
      deps/db/src/logseq/db/file_based/schema.cljs
  87. 3 3
      deps/db/src/logseq/db/frontend/class.cljs
  88. 8 9
      deps/db/src/logseq/db/frontend/content.cljs
  89. 31 28
      deps/db/src/logseq/db/frontend/kv_entity.cljs
  90. 4 6
      deps/db/src/logseq/db/frontend/malli_schema.cljs
  91. 12 20
      deps/db/src/logseq/db/frontend/property.cljs
  92. 7 0
      deps/db/src/logseq/db/frontend/property/type.cljs
  93. 16 2
      deps/db/src/logseq/db/frontend/rules.cljc
  94. 1 1
      deps/db/src/logseq/db/frontend/schema.cljs
  95. 5 2
      deps/db/test/logseq/db/sqlite/export_test.cljs
  96. 3 3
      deps/db/yarn.lock
  97. 1 1
      deps/graph-parser/package.json
  98. 34 0
      deps/graph-parser/src/logseq/graph_parser/exporter.cljs
  99. 3 6
      deps/graph-parser/src/logseq/graph_parser/whiteboard.cljs
  100. 5 16
      deps/graph-parser/test/logseq/graph_parser/exporter_test.cljs

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

@@ -72,7 +72,6 @@
              frontend.commands commands
              frontend.commands commands
              frontend.common.file-based.db common-file-db
              frontend.common.file-based.db common-file-db
              frontend.common.date common-date
              frontend.common.date common-date
-             frontend.common.file.core common-file
              frontend.common.file.util wfu
              frontend.common.file.util wfu
              frontend.common.missionary-util c.m
              frontend.common.missionary-util c.m
              frontend.common.schema-register sr
              frontend.common.schema-register sr
@@ -167,6 +166,9 @@
              lambdaisland.glogi log
              lambdaisland.glogi log
              logseq.cli.common.graph cli-common-graph
              logseq.cli.common.graph cli-common-graph
              logseq.cli.text-util cli-text-util
              logseq.cli.text-util cli-text-util
+             logseq.cli.common.export.common cli-export-common
+             logseq.cli.common.export.text cli-export-text
+             logseq.cli.common.file common-file
              logseq.common.config common-config
              logseq.common.config common-config
              logseq.common.date-time-util date-time-util
              logseq.common.date-time-util date-time-util
              logseq.common.graph common-graph
              logseq.common.graph common-graph

+ 2 - 11
.github/workflows/build-android.yml

@@ -43,7 +43,7 @@ on:
 env:
 env:
   CLOJURE_VERSION: '1.11.1.1413'
   CLOJURE_VERSION: '1.11.1.1413'
   NODE_VERSION: '22'
   NODE_VERSION: '22'
-  JAVA_VERSION: '17'
+  JAVA_VERSION: '21'
 
 
 jobs:
 jobs:
   build-apk:
   build-apk:
@@ -108,7 +108,7 @@ jobs:
           echo "ENABLE_FILE_SYNC_PRODUCTION=${{ inputs.enable-file-sync-production || github.event.inputs.enable-file-sync-production || inputs.build-target == '' }}" >> $GITHUB_ENV
           echo "ENABLE_FILE_SYNC_PRODUCTION=${{ inputs.enable-file-sync-production || github.event.inputs.enable-file-sync-production || inputs.build-target == '' }}" >> $GITHUB_ENV
 
 
       - name: Compile CLJS - app variant, use es6 instead of es-next
       - name: Compile CLJS - app variant, use es6 instead of es-next
-        run: yarn install && yarn release-app
+        run: yarn install && yarn release-mobile
         env:
         env:
           LOGSEQ_SENTRY_DSN: ${{ secrets.LOGSEQ_SENTRY_DSN }}
           LOGSEQ_SENTRY_DSN: ${{ secrets.LOGSEQ_SENTRY_DSN }}
           LOGSEQ_POSTHOG_TOKEN: ${{ secrets.LOGSEQ_POSTHOG_TOKEN }}
           LOGSEQ_POSTHOG_TOKEN: ${{ secrets.LOGSEQ_POSTHOG_TOKEN }}
@@ -126,15 +126,6 @@ jobs:
           SENTRY_ORG: logseq
           SENTRY_ORG: logseq
           SENTRY_PROJECT: logseq
           SENTRY_PROJECT: logseq
 
 
-      - name: Prepare public Directory
-        run: |
-          cp -r static public/
-          rm -rvf public/js/publishing
-          rm -rvf public/js/*.js.map || true
-          rm -rvf public/*.*
-          rm -rvf public/ios
-          rm -rvf android/app/src/main/assets/public || true
-
       - name: Sync public to Android Project
       - name: Sync public to Android Project
         run: npx cap sync android
         run: npx cap sync android
 
 

+ 27 - 27
.github/workflows/build-desktop-release.yml

@@ -38,11 +38,11 @@ on:
         type: boolean
         type: boolean
         required: true
         required: true
         default: true
         default: true
-      # build-android:
-      #   description: 'Build Android App'
-      #   type: boolean
-      #   required: true
-      #   default: true
+      build-android:
+        description: 'Build Android App'
+        type: boolean
+        required: true
+        default: true
   # schedule: # Every workday at the 2 P.M. (UTC) we run a scheduled nightly build
   # schedule: # Every workday at the 2 P.M. (UTC) we run a scheduled nightly build
   #   - cron: '0 14 * * MON-FRI'
   #   - cron: '0 14 * * MON-FRI'
 
 
@@ -492,17 +492,17 @@ jobs:
           path: builds
           path: builds
 
 
   # reuse workflow via workflow_call
   # reuse workflow via workflow_call
-  # build-android:
-  #   uses: ./.github/workflows/build-android.yml
-  #   if: ${{ github.event_name == 'schedule' || github.event.inputs.build-android == 'true' }}
-  #   with:
-  #     build-target: "${{ github.event.inputs.build-target }}"
-  #     # if scheduled, use production mode
-  #     enable-file-sync-production: "${{ github.event_name == 'schedule' || github.event.inputs.enable-file-sync-production == 'true' }}"
-  #   secrets:
-  #     ANDROID_KEYSTORE: "${{ secrets.ANDROID_KEYSTORE }}"
-  #     ANDROID_KEYSTORE_PASSWORD: "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}"
-  #     SENTRY_AUTH_TOKEN: "${{ secrets.SENTRY_AUTH_TOKEN }}"
+  build-android:
+    uses: ./.github/workflows/build-android.yml
+    if: ${{ github.event_name == 'schedule' || github.event.inputs.build-android == 'true' }}
+    with:
+      build-target: "${{ github.event.inputs.build-target }}"
+      # if scheduled, use production mode
+      enable-file-sync-production: "${{ github.event_name == 'schedule' || github.event.inputs.enable-file-sync-production == 'true' }}"
+    secrets:
+      ANDROID_KEYSTORE: "${{ secrets.ANDROID_KEYSTORE }}"
+      ANDROID_KEYSTORE_PASSWORD: "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}"
+      SENTRY_AUTH_TOKEN: "${{ secrets.SENTRY_AUTH_TOKEN }}"
 
 
   codesign-windows:
   codesign-windows:
     if: ${{ github.event_name == 'schedule' || github.event.inputs.build-target == 'nightly' || github.event.inputs.build-target == 'beta' }}
     if: ${{ github.event_name == 'schedule' || github.event.inputs.build-target == 'nightly' || github.event.inputs.build-target == 'beta' }}
@@ -572,11 +572,11 @@ jobs:
           name: logseq-win64-builds
           name: logseq-win64-builds
           path: ./
           path: ./
 
 
-      # - name: Download Android Artifacts
-      #   uses: actions/download-artifact@v4
-      #   with:
-      #     name: logseq-android-builds
-      #     path: ./
+      - name: Download Android Artifacts
+        uses: actions/download-artifact@v4
+        with:
+          name: logseq-android-builds
+          path: ./
 
 
       - name: Generate SHA256 checksums
       - name: Generate SHA256 checksums
         run: |
         run: |
@@ -652,12 +652,12 @@ jobs:
           name: logseq-win64-builds
           name: logseq-win64-builds
           path: ./
           path: ./
 
 
-      # - name: Download Android Artifacts
-      #   uses: actions/download-artifact@v4
-      #   if: ${{ github.event_name == 'schedule' || github.event.inputs.build-android == 'true' }}
-      #   with:
-      #     name: logseq-android-builds
-      #     path: ./
+      - name: Download Android Artifacts
+        uses: actions/download-artifact@v4
+        if: ${{ github.event_name == 'schedule' || github.event.inputs.build-android == 'true' }}
+        with:
+          name: logseq-android-builds
+          path: ./
 
 
       - name: List files
       - name: List files
         run: ls -rl
         run: ls -rl

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

@@ -25,7 +25,7 @@ jobs:
           ref: ${{ github.event.inputs.git-ref }}
           ref: ${{ github.event.inputs.git-ref }}
       - uses: maxim-lobanov/setup-xcode@v1
       - uses: maxim-lobanov/setup-xcode@v1
         with:
         with:
-          xcode-version: 16.1
+          xcode-version: latest-stable
       - name: Install Node.js, NPM and Yarn
       - name: Install Node.js, NPM and Yarn
         uses: actions/setup-node@v4
         uses: actions/setup-node@v4
         with:
         with:

+ 1 - 1
.github/workflows/clj-e2e.yml

@@ -78,7 +78,7 @@ jobs:
       # NOTE: require the app to be build with DEV-RELEASE flag
       # NOTE: require the app to be build with DEV-RELEASE flag
       - name: Prepare E2E test build
       - name: Prepare E2E test build
         run: |
         run: |
-          yarn gulp:build && clojure -M:cljs release app workers --config-merge "{:closure-defines {frontend.config/DEV-RELEASE true}}" --debug && yarn webpack-app-build
+          yarn gulp:build && clojure -M:cljs release app db-worker inference-worker --config-merge "{:closure-defines {frontend.config/DEV-RELEASE true}}" --debug && yarn webpack-app-build
 
 
       - name: Run e2e tests
       - name: Run e2e tests
         run: cd clj-e2e && timeout 30m bb dev
         run: cd clj-e2e && timeout 30m bb dev

+ 1 - 1
.github/workflows/clj-rtc-e2e.yml

@@ -79,7 +79,7 @@ jobs:
       # NOTE: require the app to be build with DEV-RELEASE flag
       # NOTE: require the app to be build with DEV-RELEASE flag
       - name: Prepare E2E test build
       - name: Prepare E2E test build
         run: |
         run: |
-          yarn gulp:build && clojure -M:cljs release app workers --config-merge "{:closure-defines {frontend.config/DEV-RELEASE true}}" --debug && yarn webpack-app-build
+          yarn gulp:build && clojure -M:cljs release app db-worker inference-worker --config-merge "{:closure-defines {frontend.config/DEV-RELEASE true}}" --debug && yarn webpack-app-build
           rsync -avz --exclude node_modules --exclude android --exclude ios ./static/ ./public/
           rsync -avz --exclude node_modules --exclude android --exclude ios ./static/ ./public/
           ls -lR ./public
           ls -lR ./public
 
 

+ 1 - 1
.github/workflows/deploy-db-test-pages.yml

@@ -42,7 +42,7 @@ jobs:
 
 
       - name: Build Released-Web
       - name: Build Released-Web
         run: |
         run: |
-          yarn gulp:build && clojure -M:cljs release app workers  --config-merge '{:compiler-options {:source-map true :source-map-include-sources-content true :source-map-detail-level :symbols}}' && yarn webpack-app-build
+          yarn gulp:build && clojure -M:cljs release app db-worker inference-worker  --config-merge '{:compiler-options {:source-map true :source-map-include-sources-content true :source-map-detail-level :symbols}}' && yarn webpack-app-build
           rsync -avz --exclude node_modules --exclude android --exclude ios --exclude mobile ./static/ ./public/
           rsync -avz --exclude node_modules --exclude android --exclude ios --exclude mobile ./static/ ./public/
           ls -lR ./public && mkdir r2 && mv ./public/js/main.js.map ./r2/db-test.main.js.map
           ls -lR ./public && mkdir r2 && mv ./public/js/main.js.map ./r2/db-test.main.js.map
           sed -i 's/=main.js.map/=https:\/\/assets.logseq.io\/db-test.main.js.map/g' ./public/js/main.js
           sed -i 's/=main.js.map/=https:\/\/assets.logseq.io\/db-test.main.js.map/g' ./public/js/main.js

+ 3 - 5
CONTRIBUTING.md

@@ -36,10 +36,7 @@ issues](https://github.com/logseq/logseq/issues?q=is%3Aopen+is%3Aissue+label%3A%
 to help you get started. These are issues that are beginner-friendly and do not
 to help you get started. These are issues that are beginner-friendly and do not
 require advanced knowledge of the codebase. We encourage new contributors to
 require advanced knowledge of the codebase. We encourage new contributors to
 start with these issues and gradually work their way up to more challenging
 start with these issues and gradually work their way up to more challenging
-tasks. We also have a project board to keep track of community contributions
-[Logseq - Develop Together
-💪](https://github.com/orgs/logseq/projects/5?query=is%3Aopen+sort%3Aupdated-desc).
-Another way to help with coding is by extending Logseq with
+tasks. Another way to help with coding is by extending Logseq with
 [plugins](https://docs.logseq.com/#/page/Plugins) and submitting them to the [marketplace](https://github.com/logseq/marketplace) so that the
 [plugins](https://docs.logseq.com/#/page/Plugins) and submitting them to the [marketplace](https://github.com/logseq/marketplace) so that the
 whole community can benefit.
 whole community can benefit.
 
 
@@ -100,7 +97,8 @@ behavior and design you'd like to add.
 code without a signed CLA.
 code without a signed CLA.
 
 
 After doing the above, you are ready to work on your PR! To create a PR, fork
 After doing the above, you are ready to work on your PR! To create a PR, fork
-this repository and then create a branch for the fix. Once you push your code to
+this repository and then create a branch for the fix. If contributing to the [database version](/README.md#-database-version), create a branch from master. If contributing to the file (text-only) version e.g. any version up to `0.10.X`, create a branch from [version/file](https://github.com/logseq/logseq/tree/version/file/).
+Once you push your code to
 your fork, you'll be able to open a PR to the Logseq repository. For more info,
 your fork, you'll be able to open a PR to the Logseq repository. For more info,
 you can follow this [GitHub
 you can follow this [GitHub
 guide](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).
 guide](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).

+ 1 - 2
README.md

@@ -68,7 +68,7 @@
 
 
 ## 🚀 Database Version
 ## 🚀 Database Version
 
 
-The Database version (DB version) of Logseq introduces DB graphs while maintaining support for file graphs. [See this page](https://github.com/logseq/docs/blob/master/db-version.md) to get an overview of the main features for DB graphs. If you are an existing user, [see changes with the DB version](https://github.com/logseq/docs/blob/master/db-version-changes.md). The DB version has its own new mobile app! To participate in the mobile app alpha, [please complete this brief form](https://forms.gle/nfefJv51jUuULbFB9). The DB version also has a new sync approach, RTC (Real Time Collaboration)! You can use it to sync graphs between multiple devices or collaborate with others. To participate in the RTC alpha, [please fill out this form](https://forms.gle/YSyF4WfKPSDuwyjH6).
+The Database version (DB version) of Logseq introduces DB graphs while maintaining support for file graphs. [See this page](https://github.com/logseq/docs/blob/master/db-version.md) to get an overview of the main features for DB graphs. If you are an existing user, [see changes with the DB version](https://github.com/logseq/docs/blob/master/db-version-changes.md). The DB version has its own new mobile app (on iOS, with Android coming soon)! To participate in the mobile app alpha, [please complete this brief form](https://forms.gle/nfefJv51jUuULbFB9). The DB version also has a new sync approach, RTC (Real Time Collaboration)! You can use it to sync graphs between multiple devices or collaborate with others. To participate in the RTC alpha, [please fill out this form](https://forms.gle/YSyF4WfKPSDuwyjH6).
 
 
 The DB version is in beta status while the new mobile app and RTC is in alpha. This means that **data loss is possible** so we recommend [automated backups](https://github.com/logseq/docs/blob/master/db-version.md#automated-backup) or [regular SQLite DB backups](https://github.com/logseq/docs/blob/master/db-version.md#graph-export). When using DB graphs, we recommend you create a dedicated test graph and choose one project that’s not crucial for you. When using file graphs, **data corruption is possible** as some file content can be duplicated. We only recommend using it with file graphs if you make regular backups with git.
 The DB version is in beta status while the new mobile app and RTC is in alpha. This means that **data loss is possible** so we recommend [automated backups](https://github.com/logseq/docs/blob/master/db-version.md#automated-backup) or [regular SQLite DB backups](https://github.com/logseq/docs/blob/master/db-version.md#graph-export). When using DB graphs, we recommend you create a dedicated test graph and choose one project that’s not crucial for you. When using file graphs, **data corruption is possible** as some file content can be duplicated. We only recommend using it with file graphs if you make regular backups with git.
 
 
@@ -111,7 +111,6 @@ That's it! You can now enjoy the benefits of using Logseq to streamline your wor
   * FAQ page: [Logseq Docs:  FAQ](https://docs.logseq.com/#/page/faq)
   * FAQ page: [Logseq Docs:  FAQ](https://docs.logseq.com/#/page/faq)
 * Blog: [blog.logseq.com](https://blog.logseq.com)
 * Blog: [blog.logseq.com](https://blog.logseq.com)
   * Please visit our [About page](https://blog.logseq.com/about) for the latest updates.
   * Please visit our [About page](https://blog.logseq.com/about) for the latest updates.
-* Logseq Hub: [hub.logseq.com](https://hub.logseq.com)
 * Forum: [discuss.logseq.com](https://discuss.logseq.com) - Where we answer questions, discuss workflows, and share tips
 * Forum: [discuss.logseq.com](https://discuss.logseq.com) - Where we answer questions, discuss workflows, and share tips
   * FAQ forum section: [Logseq Forum: FAQ](https://discuss.logseq.com/c/faq/6)
   * FAQ forum section: [Logseq Forum: FAQ](https://discuss.logseq.com/c/faq/6)
 * [Awesome Logseq](https://github.com/logseq/awesome-logseq) - Awesome Logseq extensions and resources created by the community <3
 * [Awesome Logseq](https://github.com/logseq/awesome-logseq) - Awesome Logseq extensions and resources created by the community <3

+ 7 - 8
android/app/capacitor.build.gradle

@@ -1,15 +1,15 @@
 // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
 // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
 
 
 android {
 android {
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_21
-        targetCompatibility JavaVersion.VERSION_21
-    }
+  compileOptions {
+      sourceCompatibility JavaVersion.VERSION_21
+      targetCompatibility JavaVersion.VERSION_21
+  }
 }
 }
 
 
 apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
 apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
 dependencies {
 dependencies {
-    implementation 'androidx.core:core-ktx:1.16.0'
+    implementation project(':capacitor-community-safe-area')
     implementation project(':capacitor-action-sheet')
     implementation project(':capacitor-action-sheet')
     implementation project(':capacitor-app')
     implementation project(':capacitor-app')
     implementation project(':capacitor-camera')
     implementation project(':capacitor-camera')
@@ -17,11 +17,10 @@ dependencies {
     implementation project(':capacitor-filesystem')
     implementation project(':capacitor-filesystem')
     implementation project(':capacitor-haptics')
     implementation project(':capacitor-haptics')
     implementation project(':capacitor-keyboard')
     implementation project(':capacitor-keyboard')
+    implementation project(':capacitor-network')
     implementation project(':capacitor-share')
     implementation project(':capacitor-share')
     implementation project(':capacitor-splash-screen')
     implementation project(':capacitor-splash-screen')
     implementation project(':capacitor-status-bar')
     implementation project(':capacitor-status-bar')
-    implementation project(':capawesome-capacitor-background-task')
-    implementation project(':capgo-capacitor-navigation-bar')
     implementation project(':capacitor-voice-recorder')
     implementation project(':capacitor-voice-recorder')
     implementation project(':send-intent')
     implementation project(':send-intent')
     implementation project(':jcesarmobile-ssl-skip')
     implementation project(':jcesarmobile-ssl-skip')
@@ -30,5 +29,5 @@ dependencies {
 
 
 
 
 if (hasProperty('postBuildExtras')) {
 if (hasProperty('postBuildExtras')) {
-    postBuildExtras()
+  postBuildExtras()
 }
 }

+ 13 - 7
android/app/src/main/AndroidManifest.xml

@@ -11,21 +11,22 @@
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
 
 
     <application
     <application
-        android:networkSecurityConfig="@xml/network_security_config"
         android:allowBackup="true"
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
         android:label="@string/app_name"
+        android:networkSecurityConfig="@xml/network_security_config"
+        android:requestLegacyExternalStorage="true"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
         android:supportsRtl="true"
-        android:requestLegacyExternalStorage="true"
         android:theme="@style/AppTheme">
         android:theme="@style/AppTheme">
         <activity
         <activity
-            android:exported="true"
-            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
             android:name="com.logseq.app.MainActivity"
             android:name="com.logseq.app.MainActivity"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
+            android:exported="true"
+            android:windowSoftInputMode="adjustNothing"
             android:label="@string/title_activity_main"
             android:label="@string/title_activity_main"
-            android:theme="@style/AppTheme.NoActionBarLaunch"
-            android:launchMode="singleTask">
+            android:launchMode="singleTask"
+            android:theme="@style/AppTheme.NoActionBarLaunch">
             <intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
                 <category android:name="android.intent.category.LAUNCHER" />
@@ -34,6 +35,7 @@
             <intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.SEND" />
                 <action android:name="android.intent.action.SEND" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.DEFAULT" />
+
                 <data android:mimeType="text/plain" />
                 <data android:mimeType="text/plain" />
                 <data android:mimeType="image/*" />
                 <data android:mimeType="image/*" />
                 <data android:mimeType="application/*" />
                 <data android:mimeType="application/*" />
@@ -42,8 +44,10 @@
 
 
             <intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <action android:name="android.intent.action.VIEW" />
+
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.BROWSABLE" />
                 <category android:name="android.intent.category.BROWSABLE" />
+
                 <data android:scheme="logseq" />
                 <data android:scheme="logseq" />
             </intent-filter>
             </intent-filter>
         </activity>
         </activity>
@@ -53,7 +57,9 @@
             android:authorities="${applicationId}.fileprovider"
             android:authorities="${applicationId}.fileprovider"
             android:exported="false"
             android:exported="false"
             android:grantUriPermissions="true">
             android:grantUriPermissions="true">
-            <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
         </provider>
         </provider>
     </application>
     </application>
 </manifest>
 </manifest>

+ 8 - 8
android/app/src/main/assets/capacitor.plugins.json

@@ -1,4 +1,8 @@
 [
 [
+	{
+		"pkg": "@capacitor-community/safe-area",
+		"classpath": "com.getcapacitor.community.safearea.SafeAreaPlugin"
+	},
 	{
 	{
 		"pkg": "@capacitor/action-sheet",
 		"pkg": "@capacitor/action-sheet",
 		"classpath": "com.capacitorjs.plugins.actionsheet.ActionSheetPlugin"
 		"classpath": "com.capacitorjs.plugins.actionsheet.ActionSheetPlugin"
@@ -27,6 +31,10 @@
 		"pkg": "@capacitor/keyboard",
 		"pkg": "@capacitor/keyboard",
 		"classpath": "com.capacitorjs.plugins.keyboard.KeyboardPlugin"
 		"classpath": "com.capacitorjs.plugins.keyboard.KeyboardPlugin"
 	},
 	},
+	{
+		"pkg": "@capacitor/network",
+		"classpath": "com.capacitorjs.plugins.network.NetworkPlugin"
+	},
 	{
 	{
 		"pkg": "@capacitor/share",
 		"pkg": "@capacitor/share",
 		"classpath": "com.capacitorjs.plugins.share.SharePlugin"
 		"classpath": "com.capacitorjs.plugins.share.SharePlugin"
@@ -39,14 +47,6 @@
 		"pkg": "@capacitor/status-bar",
 		"pkg": "@capacitor/status-bar",
 		"classpath": "com.capacitorjs.plugins.statusbar.StatusBarPlugin"
 		"classpath": "com.capacitorjs.plugins.statusbar.StatusBarPlugin"
 	},
 	},
-	{
-		"pkg": "@capawesome/capacitor-background-task",
-		"classpath": "io.capawesome.capacitorjs.plugins.backgroundtask.BackgroundTaskPlugin"
-	},
-	{
-		"pkg": "@capgo/capacitor-navigation-bar",
-		"classpath": "ee.forgr.capacitor_navigation_bar.NavigationBarPlugin"
-	},
 	{
 	{
 		"pkg": "capacitor-voice-recorder",
 		"pkg": "capacitor-voice-recorder",
 		"classpath": "com.tchvu3.capacitorvoicerecorder.VoiceRecorder"
 		"classpath": "com.tchvu3.capacitorvoicerecorder.VoiceRecorder"

+ 29 - 0
android/app/src/main/java/com/logseq/app/MainActivity.java

@@ -1,8 +1,13 @@
 package com.logseq.app;
 package com.logseq.app;
 
 
 import android.content.Intent;
 import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Bundle;
+import android.view.View;
+import android.view.Window;
 import android.webkit.ValueCallback;
 import android.webkit.ValueCallback;
+import android.webkit.WebView;
 
 
 import com.getcapacitor.BridgeActivity;
 import com.getcapacitor.BridgeActivity;
 
 
@@ -16,6 +21,12 @@ public class MainActivity extends BridgeActivity {
         registerPlugin(UILocal.class);
         registerPlugin(UILocal.class);
 
 
         super.onCreate(savedInstanceState);
         super.onCreate(savedInstanceState);
+        WebView webView = getBridge().getWebView();
+        webView.setOverScrollMode(WebView.OVER_SCROLL_NEVER);
+        webView.getSettings().setUseWideViewPort(true);
+        webView.getSettings().setLoadWithOverviewMode(true);
+
+        setNavigationBarColorBasedOnTheme();
 
 
         new Timer().schedule(new TimerTask() {
         new Timer().schedule(new TimerTask() {
             @Override
             @Override
@@ -51,4 +62,22 @@ public class MainActivity extends BridgeActivity {
             });
             });
         }
         }
     }
     }
+
+    private void setNavigationBarColorBasedOnTheme() {
+        Window window = getWindow();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // API 26及以上支持
+            // 根据主题选择颜色
+            int navBarColor = isDarkMode()
+                    ? getResources().getColor(R.color.colorPrimaryDark, getTheme())
+                    : getResources().getColor(R.color.colorPrimary, getTheme());
+
+            // 设置导航栏颜色
+            window.setNavigationBarColor(navBarColor);
+        }
+    }
+
+    private boolean isDarkMode() {
+        int nightModeFlags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        return nightModeFlags == Configuration.UI_MODE_NIGHT_YES;
+    }
 }
 }

BIN
android/app/src/main/res/drawable-land-night-hdpi/splash.png


BIN
android/app/src/main/res/drawable-land-night-ldpi/splash.png


BIN
android/app/src/main/res/drawable-land-night-mdpi/splash.png


BIN
android/app/src/main/res/drawable-land-night-xhdpi/splash.png


BIN
android/app/src/main/res/drawable-land-night-xxhdpi/splash.png


BIN
android/app/src/main/res/drawable-land-night-xxxhdpi/splash.png


BIN
android/app/src/main/res/drawable-night/splash.png


BIN
android/app/src/main/res/drawable-port-night-hdpi/splash.png


BIN
android/app/src/main/res/drawable-port-night-ldpi/splash.png


BIN
android/app/src/main/res/drawable-port-night-mdpi/splash.png


BIN
android/app/src/main/res/drawable-port-night-xhdpi/splash.png


BIN
android/app/src/main/res/drawable-port-night-xxhdpi/splash.png


BIN
android/app/src/main/res/drawable-port-night-xxxhdpi/splash.png


BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png


BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png


BIN
android/app/src/main/res/mipmap-ldpi/ic_launcher_background.png


BIN
android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png


BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png


BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png


BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png


BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png


BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png


BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png


BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png


BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png


+ 3 - 3
android/app/src/main/res/values/colors.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
 <resources>
-    <color name="logoPrimary">#002b36</color>
-    <color name="colorPrimary">#ffffff</color>
-    <color name="colorPrimaryDark">#002b36</color>
+    <color name="logoPrimary">#0f262e</color>
+    <color name="colorPrimary">#f7f7f7</color>
+    <color name="colorPrimaryDark">#0f262e</color>
 </resources>
 </resources>

+ 6 - 6
android/capacitor.settings.gradle

@@ -2,6 +2,9 @@
 include ':capacitor-android'
 include ':capacitor-android'
 project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
 project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
 
 
+include ':capacitor-community-safe-area'
+project(':capacitor-community-safe-area').projectDir = new File('../node_modules/@capacitor-community/safe-area/android')
+
 include ':capacitor-action-sheet'
 include ':capacitor-action-sheet'
 project(':capacitor-action-sheet').projectDir = new File('../node_modules/@capacitor/action-sheet/android')
 project(':capacitor-action-sheet').projectDir = new File('../node_modules/@capacitor/action-sheet/android')
 
 
@@ -23,6 +26,9 @@ project(':capacitor-haptics').projectDir = new File('../node_modules/@capacitor/
 include ':capacitor-keyboard'
 include ':capacitor-keyboard'
 project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android')
 project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android')
 
 
+include ':capacitor-network'
+project(':capacitor-network').projectDir = new File('../node_modules/@capacitor/network/android')
+
 include ':capacitor-share'
 include ':capacitor-share'
 project(':capacitor-share').projectDir = new File('../node_modules/@capacitor/share/android')
 project(':capacitor-share').projectDir = new File('../node_modules/@capacitor/share/android')
 
 
@@ -32,12 +38,6 @@ project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capa
 include ':capacitor-status-bar'
 include ':capacitor-status-bar'
 project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
 project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
 
 
-include ':capawesome-capacitor-background-task'
-project(':capawesome-capacitor-background-task').projectDir = new File('../node_modules/@capawesome/capacitor-background-task/android')
-
-include ':capgo-capacitor-navigation-bar'
-project(':capgo-capacitor-navigation-bar').projectDir = new File('../node_modules/@capgo/capacitor-navigation-bar/android')
-
 include ':capacitor-voice-recorder'
 include ':capacitor-voice-recorder'
 project(':capacitor-voice-recorder').projectDir = new File('../node_modules/capacitor-voice-recorder/android')
 project(':capacitor-voice-recorder').projectDir = new File('../node_modules/capacitor-voice-recorder/android')
 
 

BIN
assets/icon-background.png


BIN
assets/icon-foreground.png


BIN
assets/splash-dark.png


+ 11 - 1
capacitor.config.ts

@@ -9,7 +9,7 @@ const config: CapacitorConfig = {
   webDir: 'static/mobile',
   webDir: 'static/mobile',
   loggingBehavior: 'debug',
   loggingBehavior: 'debug',
   server: {
   server: {
-      androidScheme: 'http',
+    androidScheme: 'http',
   },
   },
   plugins: {
   plugins: {
     StatusBar: {
     StatusBar: {
@@ -28,6 +28,16 @@ const config: CapacitorConfig = {
 
 
     Keyboard: {
     Keyboard: {
       resize: 'none'
       resize: 'none'
+    },
+
+    SafeArea: {
+      enabled: true,
+      customColorsForSystemBars: true,
+      statusBarColor: '#000000',
+      statusBarContent: 'light',
+      navigationBarColor: '#000000',
+      navigationBarContent: 'light',
+      offset: 0
     }
     }
   },
   },
   android: {
   android: {

+ 2 - 1
clj-e2e/.clj-kondo/config.edn

@@ -1,3 +1,4 @@
 {:linters
 {:linters
- {:unresolved-var {:exclude [wally.main/click
+ {:redundant-let {:level :off}
+  :unresolved-var {:exclude [wally.main/click
                              wally.main/fill]}}}
                              wally.main/fill]}}}

+ 8 - 2
clj-e2e/test/logseq/e2e/editor_basic_test.clj

@@ -31,8 +31,9 @@
     (b/select-blocks 3)
     (b/select-blocks 3)
     (b/toggle-property "Tags" "Page")
     (b/toggle-property "Tags" "Page")
     (assert/assert-is-visible ".ls-page-blocks .ls-block .ls-icon-file")
     (assert/assert-is-visible ".ls-page-blocks .ls-block .ls-icon-file")
-    (w/wait-for (format "#ac-0.menu-link:has-text('%s')" "Page"))
-    (k/enter)
+    (w/wait-for (format ".menu-link:has-text('%s')" "Page"))
+    (k/esc)
+    (b/toggle-property "Tags" "Page")
     (w/wait-for-not-visible ".ls-page-blocks .ls-block .ls-icon-file")))
     (w/wait-for-not-visible ".ls-page-blocks .ls-block .ls-icon-file")))
 
 
 (deftest disallow-adding-page-tag-to-normal-pages
 (deftest disallow-adding-page-tag-to-normal-pages
@@ -66,6 +67,7 @@
     (w/fill "input[placeholder=\"Move blocks to\"]" "Target page 2")
     (w/fill "input[placeholder=\"Move blocks to\"]" "Target page 2")
     (w/wait-for (w/get-by-test-id "Target page 2"))
     (w/wait-for (w/get-by-test-id "Target page 2"))
     (.focus (w/-query ".cp__cmdk-search-input"))
     (.focus (w/-query ".cp__cmdk-search-input"))
+    (k/arrow-down)
     (k/enter)
     (k/enter)
     (assert/assert-have-count ".ls-page-blocks .page-blocks-inner .ls-block" 0)))
     (assert/assert-have-count ".ls-page-blocks .page-blocks-inner .ls-block" 0)))
 
 
@@ -75,6 +77,8 @@
     (p/new-page "test page")
     (p/new-page "test page")
     (b/new-blocks ["block1" "block2" "block3"])
     (b/new-blocks ["block1" "block2" "block3"])
     (b/select-blocks 3)
     (b/select-blocks 3)
+    (b/toggle-property "Tags" "Page")
+    (assert/assert-is-visible ".ls-page-blocks .ls-block .ls-icon-file")
     (k/press "ControlOrMeta+Shift+m")
     (k/press "ControlOrMeta+Shift+m")
     (w/fill "input[placeholder=\"Move blocks to\"]" "Library")
     (w/fill "input[placeholder=\"Move blocks to\"]" "Library")
     (w/wait-for (w/get-by-test-id "Library"))
     (w/wait-for (w/get-by-test-id "Library"))
@@ -86,6 +90,8 @@
     (p/goto-page "test page")
     (p/goto-page "test page")
     (b/new-blocks ["block4" "block5"])
     (b/new-blocks ["block4" "block5"])
     (b/select-blocks 2)
     (b/select-blocks 2)
+    (b/toggle-property "Tags" "Page")
+    (assert/assert-is-visible ".ls-page-blocks .ls-block .ls-icon-file")
     (k/press "ControlOrMeta+Shift+m")
     (k/press "ControlOrMeta+Shift+m")
     (w/fill "input[placeholder=\"Move blocks to\"]" "Library")
     (w/fill "input[placeholder=\"Move blocks to\"]" "Library")
     (w/wait-for (w/get-by-test-id "Library"))
     (w/wait-for (w/get-by-test-id "Library"))

+ 5 - 1
clj-e2e/test/logseq/e2e/plugins_basic_test.clj

@@ -61,6 +61,8 @@
     (page/new-page "test-block-apis")
     (page/new-page "test-block-apis")
     (ls-api-call! :ui.showMsg "hello world" "info")
     (ls-api-call! :ui.showMsg "hello world" "info")
     (let [ret (ls-api-call! :editor.appendBlockInPage "test-block-apis" "append-block-in-page-0")
     (let [ret (ls-api-call! :editor.appendBlockInPage "test-block-apis" "append-block-in-page-0")
+          ret1 (ls-api-call! :editor.appendBlockInPage "append-block-in-current-page-0")
+          _ (assert-api-ls-block! ret1)
           uuid' (assert-api-ls-block! ret)]
           uuid' (assert-api-ls-block! ret)]
       (-> (ls-api-call! :editor.insertBlock uuid' "insert-0")
       (-> (ls-api-call! :editor.insertBlock uuid' "insert-0")
           (assert-api-ls-block!))
           (assert-api-ls-block!))
@@ -75,11 +77,13 @@
     (let [ret (ls-api-call! :editor.appendBlockInPage "test-block-properties-apis" "block-in-page-0" {:properties {:p1 1}})
     (let [ret (ls-api-call! :editor.appendBlockInPage "test-block-properties-apis" "block-in-page-0" {:properties {:p1 1}})
           uuid' (assert-api-ls-block! ret)
           uuid' (assert-api-ls-block! ret)
           prop1 (ls-api-call! :editor.getBlockProperty uuid' "p1")
           prop1 (ls-api-call! :editor.getBlockProperty uuid' "p1")
-          props1 (ls-api-call! :editor.getBlockProperties uuid' "p1")]
+          props1 (ls-api-call! :editor.getBlockProperties uuid' "p1")
+          props2 (ls-api-call! :editor.getPageProperties "test-block-properties-apis")]
       (w/wait-for ".property-k:text('p1')")
       (w/wait-for ".property-k:text('p1')")
       (is (= 1 (get prop1 "value")))
       (is (= 1 (get prop1 "value")))
       (is (= (get prop1 "ident") ":plugin.property._api/p1"))
       (is (= (get prop1 "ident") ":plugin.property._api/p1"))
       (is (= 1 (get props1 ":plugin.property._api/p1")))
       (is (= 1 (get props1 ":plugin.property._api/p1")))
+      (is (= ["Page"] (get props2 ":block/tags")))
       (ls-api-call! :editor.upsertBlockProperty uuid' "p2" "p2")
       (ls-api-call! :editor.upsertBlockProperty uuid' "p2" "p2")
       (ls-api-call! :editor.upsertBlockProperty uuid' "p3" true)
       (ls-api-call! :editor.upsertBlockProperty uuid' "p3" true)
       (ls-api-call! :editor.upsertBlockProperty uuid' "p4" {:a 1, :b [2, 3]})
       (ls-api-call! :editor.upsertBlockProperty uuid' "p4" {:a 1, :b [2, 3]})

+ 1 - 1
deps.edn

@@ -5,7 +5,7 @@
                                          :sha     "5d672bf84ed944414b9f61eeb83808ead7be9127"}
                                          :sha     "5d672bf84ed944414b9f61eeb83808ead7be9127"}
 
 
   datascript/datascript                 {:git/url "https://github.com/logseq/datascript" ;; fork
   datascript/datascript                 {:git/url "https://github.com/logseq/datascript" ;; fork
-                                         :sha     "b28f6574b9447bba9ccaa5d2b0cfd79308acf0e3"}
+                                         :sha     "45f6721bf2038c24eb9fe3afb422322ab3f473b5"}
 
 
   datascript-transit/datascript-transit {:mvn/version "0.3.0"}
   datascript-transit/datascript-transit {:mvn/version "0.3.0"}
   borkdude/rewrite-edn                  {:mvn/version "0.4.9"}
   borkdude/rewrite-edn                  {:mvn/version "0.4.9"}

+ 2 - 0
deps/cli/.carve/ignore

@@ -4,3 +4,5 @@ logseq.cli.commands.graph/show-graph
 logseq.cli.commands.graph/list-graphs
 logseq.cli.commands.graph/list-graphs
 logseq.cli.commands.query/query
 logseq.cli.commands.query/query
 logseq.cli.commands.search/search
 logseq.cli.commands.search/search
+logseq.cli.commands.export/export
+logseq.cli.commands.append/append

+ 5 - 1
deps/cli/.clj-kondo/config.edn

@@ -2,7 +2,8 @@
  {:aliased-namespace-symbol {:level :warning}
  {:aliased-namespace-symbol {:level :warning}
   :namespace-name-mismatch {:level :warning}
   :namespace-name-mismatch {:level :warning}
   :used-underscored-binding {:level :warning}
   :used-underscored-binding {:level :warning}
-  :shadowed-var {:level :warning}
+  :shadowed-var {:level :warning
+                 :exclude [meta time min meta name type]}
 
 
   :consistent-alias
   :consistent-alias
   {:aliases {"fs" fs
   {:aliases {"fs" fs
@@ -12,6 +13,9 @@
              datascript.core d
              datascript.core d
              logseq.cli.commands.graph cli-graph
              logseq.cli.commands.graph cli-graph
              logseq.cli.common.graph cli-common-graph
              logseq.cli.common.graph cli-common-graph
+             logseq.cli.common.export.text cli-export-text
+             logseq.cli.common.export.common cli-export-common
+             logseq.cli.common.file common-file
              logseq.cli.util cli-util
              logseq.cli.util cli-util
              logseq.cli.text-util cli-text-util
              logseq.cli.text-util cli-text-util
              logseq.common.config common-config
              logseq.common.config common-config

+ 9 - 1
deps/cli/CHANGELOG.md

@@ -1,6 +1,14 @@
+## 0.2.0
+* Add export command to export graph as markdown
+* Add append command to add text to current page
+* Change export-edn command to default to writing to a file, like the export command
+* Rename --api-query-token options to --api-server-token
+* API related commands can also authenticate with $LOGSEQ_API_SERVER_TOKEN
+* Add descriptions for most commands to explain in-depth usage
+
 ## 0.1.0
 ## 0.1.0
 
 
 * Initial release!
 * Initial release!
 * Provides commands: list, show, search, query, export-edn and help
 * Provides commands: list, show, search, query, export-edn and help
-* All commands except search work offline. search and query have options for calling HTTP Server of
+* All commands work offline. search and query have options for calling HTTP API Server of
   open desktop Logseq app
   open desktop Logseq app

+ 16 - 4
deps/cli/README.md

@@ -12,7 +12,7 @@ This section assumes you have installed the CLI from npm or via the [dev
 setup](#setup). If you haven't, substitute `node cli.mjs` for `logseq` e.g.
 setup](#setup). If you haven't, substitute `node cli.mjs` for `logseq` e.g.
 `node.cli.mjs -h`.
 `node.cli.mjs -h`.
 
 
-All commands can be used offline or on CI. The `search` command and any command that has an api-query-token option require the [HTTP API Server](https://docs.logseq.com/#/page/local%20http%20server) to be turned on.
+All commands except for `append` can be used offline or on CI. The `search` command and any command that has an api-server-token option require the [HTTP API Server](https://docs.logseq.com/#/page/local%20http%20server) to be turned on.
 
 
 Now let's use it!
 Now let's use it!
 
 
@@ -26,12 +26,13 @@ Options:
 Commands:
 Commands:
 list                 List graphs
 list                 List graphs
 show                 Show DB graph(s) info
 show                 Show DB graph(s) info
-search [options]     Search current DB graph
+search [options]     Search DB graph
 query [options]      Query DB graph(s)
 query [options]      Query DB graph(s)
+export [options]     Export DB graph as Markdown
 export-edn [options] Export DB graph as EDN
 export-edn [options] Export DB graph as EDN
+append [options]     Appends text to current page
 help                 Print a command's help
 help                 Print a command's help
 
 
-
 $ logseq list
 $ logseq list
 DB Graphs:
 DB Graphs:
 db-test
 db-test
@@ -60,6 +61,9 @@ Search found 100 results:
 dev:db-export woot woot.edn && dev:db-create woot2 woot.edn
 dev:db-export woot woot.edn && dev:db-create woot2 woot.edn
 dev:db-diff woot woot2
 dev:db-diff woot woot2
 ...
 ...
+# Can also authenticate api with $LOGSEQ_API_SERVER_TOKEN
+$ LOGSEQ_API_SERVER_TOKEN=my-token logseq search woot
+...
 
 
 # Search a local graph
 # Search a local graph
 $ logseq search woot page
 $ logseq search woot page
@@ -110,9 +114,17 @@ $ logseq query '(task DOING)' -a my-token
   :uuid "68795144-e5f6-48e8-849d-79cd6473b952"}
   :uuid "68795144-e5f6-48e8-849d-79cd6473b952"}
   ...
   ...
 
 
-# Export your DB graph as EDN
+# Export DB graph as markdown
+$ logseq export yep
+Exported 41 pages to yep_markdown_1756128259.zip
+
+# Export DB graph as EDN
 $ logseq export-edn woot -f woot.edn
 $ logseq export-edn woot -f woot.edn
 Exported 16 properties, 16 classes and 36 pages
 Exported 16 properties, 16 classes and 36 pages
+
+# Append text to current page
+$ logseq append add this text -a my-token
+Success!
 ```
 ```
 
 
 ## API
 ## API

+ 1 - 1
deps/cli/bb.edn

@@ -40,5 +40,5 @@
 
 
  :tasks/config
  :tasks/config
  {:large-vars
  {:large-vars
-  {:max-lines-count 30
+  {:max-lines-count 45
    :metadata-exceptions #{:large-vars/cleanup-todo}}}}
    :metadata-exceptions #{:large-vars/cleanup-todo}}}}

+ 5 - 3
deps/cli/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@logseq/cli",
   "name": "@logseq/cli",
-  "version": "0.1.0",
+  "version": "0.2.0",
   "description": "Logseq CLI",
   "description": "Logseq CLI",
   "bin": {
   "bin": {
     "logseq": "cli.mjs"
     "logseq": "cli.mjs"
@@ -10,9 +10,11 @@
   },
   },
   "license": "MIT",
   "license": "MIT",
   "dependencies": {
   "dependencies": {
-    "@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v26",
+    "@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v28",
     "better-sqlite3": "~11.10.0",
     "better-sqlite3": "~11.10.0",
-    "fs-extra": "^11.3.0"
+    "fs-extra": "^11.3.0",
+    "jszip": "3.8.0",
+    "mldoc": "^1.5.9"
   },
   },
   "repository": {
   "repository": {
     "type": "git",
     "type": "git",

+ 38 - 13
deps/cli/src/logseq/cli.cljs

@@ -6,6 +6,7 @@
             [clojure.string :as string]
             [clojure.string :as string]
             [logseq.cli.common.graph :as cli-common-graph]
             [logseq.cli.common.graph :as cli-common-graph]
             [logseq.cli.spec :as cli-spec]
             [logseq.cli.spec :as cli-spec]
+            [logseq.cli.text-util :as cli-text-util]
             [nbb.error]
             [nbb.error]
             [promesa.core :as p]))
             [promesa.core :as p]))
 
 
@@ -38,15 +39,20 @@
                      (aget "version")))))
                      (aget "version")))))
     (help m)))
     (help m)))
 
 
-(defn- command-help [{{:keys [command]} :opts}]
+(defn- print-command-help [command cmd-map]
+  (println (str "Usage: logseq " command
+                (when (:args->opts cmd-map)
+                  (str " " (string/join " "
+                                        (map #(str "[" (name %) "]") (:args->opts cmd-map)))))
+                (when (:spec cmd-map)
+                  (str " [options]\n\nOptions:\n"
+                       (cli/format-opts {:spec (:spec cmd-map)})))
+                (when (:description cmd-map)
+                  (str "\n\nDescription:\n" (cli-text-util/wrap-text (:description cmd-map) 80))))))
+
+(defn- help-command [{{:keys [command]} :opts}]
   (if-let [cmd-map (and command (some #(when (= command (first (:cmds %))) %) table))]
   (if-let [cmd-map (and command (some #(when (= command (first (:cmds %))) %) table))]
-    (println (str "Usage: logseq " command
-                  (when (:args->opts cmd-map)
-                    (str " " (string/join " "
-                                          (map #(str "[" (name %) "]") (:args->opts cmd-map)))))
-                  (when (:spec cmd-map)
-                    (str " [options]\n\nOptions:\n"
-                         (cli/format-opts {:spec (:spec cmd-map)})))))
+    (print-command-help command cmd-map)
     (println "Command" (pr-str command) "does not exist")))
     (println "Command" (pr-str command) "does not exist")))
 
 
 (defn- lazy-load-fn
 (defn- lazy-load-fn
@@ -65,22 +71,35 @@
   [{:cmds ["list"] :desc "List graphs"
   [{:cmds ["list"] :desc "List graphs"
     :fn (lazy-load-fn 'logseq.cli.commands.graph/list-graphs)}
     :fn (lazy-load-fn 'logseq.cli.commands.graph/list-graphs)}
    {:cmds ["show"] :desc "Show DB graph(s) info"
    {:cmds ["show"] :desc "Show DB graph(s) info"
+    :description "For each graph, prints information related to a graph's creation and anything that is helpful for debugging."
     :fn (lazy-load-fn 'logseq.cli.commands.graph/show-graph)
     :fn (lazy-load-fn 'logseq.cli.commands.graph/show-graph)
     :args->opts [:graphs] :coerce {:graphs []} :require [:graphs]}
     :args->opts [:graphs] :coerce {:graphs []} :require [:graphs]}
    {:cmds ["search"]
    {:cmds ["search"]
     :fn (lazy-load-fn 'logseq.cli.commands.search/search)
     :fn (lazy-load-fn 'logseq.cli.commands.search/search)
     :desc "Search DB graph"
     :desc "Search DB graph"
+    :description "Search a local graph or the current in-app graph if --api-server-token is given. For a local graph it only searches the :block/title of blocks."
     :args->opts [:graph :search-terms] :coerce {:search-terms []} :require [:graph]
     :args->opts [:graph :search-terms] :coerce {:search-terms []} :require [:graph]
     :spec cli-spec/search}
     :spec cli-spec/search}
    {:cmds ["query"] :desc "Query DB graph(s)"
    {:cmds ["query"] :desc "Query DB graph(s)"
+    :description "Query a local graph or the current in-app graph if --api-server-token is given. For a local graph, queries are a datalog query or an entity query. A datalog query can use built-in rules. An entity query consists of one or more integers, uuids or :db/ident keywords. For an in-app query, queries can be an advanced or simple query."
     :fn (lazy-load-fn 'logseq.cli.commands.query/query)
     :fn (lazy-load-fn 'logseq.cli.commands.query/query)
     :args->opts [:graph :args] :coerce {:args []} :no-keyword-opts true :require [:graph]
     :args->opts [:graph :args] :coerce {:args []} :no-keyword-opts true :require [:graph]
     :spec cli-spec/query}
     :spec cli-spec/query}
+   {:cmds ["export"] :desc "Export DB graph as Markdown"
+    :description "Export a graph to Markdown like the in-app graph export."
+    :fn (lazy-load-fn 'logseq.cli.commands.export/export)
+    :args->opts [:graph] :require [:graph]
+    :spec cli-spec/export}
    {:cmds ["export-edn"] :desc "Export DB graph as EDN"
    {:cmds ["export-edn"] :desc "Export DB graph as EDN"
+    :description "Export a graph to EDN like the in-app graph EDN export. See https://github.com/logseq/docs/blob/master/db-version.md#edn-data-export for more about this export type."
     :fn (lazy-load-fn 'logseq.cli.commands.export-edn/export)
     :fn (lazy-load-fn 'logseq.cli.commands.export-edn/export)
     :args->opts [:graph] :require [:graph]
     :args->opts [:graph] :require [:graph]
     :spec cli-spec/export-edn}
     :spec cli-spec/export-edn}
-   {:cmds ["help"] :fn command-help :desc "Print a command's help"
+   {:cmds ["append"] :desc "Appends text to current page"
+    :fn (lazy-load-fn 'logseq.cli.commands.append/append)
+    :args->opts [:args] :require [:args] :coerce {:args []}
+    :spec cli-spec/append}
+   {:cmds ["help"] :fn help-command :desc "Print a command's help"
     :args->opts [:command] :require [:command]}
     :args->opts [:command] :require [:command]}
    {:cmds []
    {:cmds []
     :spec default-spec
     :spec default-spec
@@ -101,12 +120,18 @@
                   {:error-fn (fn [{:keys [cause msg option] type' :type :as data}]
                   {:error-fn (fn [{:keys [cause msg option] type' :type :as data}]
                                (if (and (= :org.babashka/cli type')
                                (if (and (= :org.babashka/cli type')
                                         (= :require cause))
                                         (= :require cause))
-                                 (println "Error: Command missing required"
-                                          (if (get-in data [:spec option]) "option" "argument")
-                                          option)
+                                 (do
+                                   (println "Error: Command missing required"
+                                            (if (get-in data [:spec option]) "option" "argument")
+                                            (pr-str (name option)))
+                                   (when-let [cmd-m (some #(when (= {:spec (:spec %)
+                                                                     :require (:require %)}
+                                                                    (select-keys data [:spec :require])) %) table)]
+                                     (print-command-help (-> cmd-m :cmds first) cmd-m)))
                                  (throw (ex-info msg data)))
                                  (throw (ex-info msg data)))
                                (js/process.exit 1))})
                                (js/process.exit 1))})
     (catch ^:sci/error js/Error e
     (catch ^:sci/error js/Error e
-      (nbb.error/print-error-report e))))
+      (nbb.error/print-error-report e)
+      (js/process.exit 1))))
 
 
 #js {:main -main}
 #js {:main -main}

+ 14 - 0
deps/cli/src/logseq/cli/commands/append.cljs

@@ -0,0 +1,14 @@
+(ns logseq.cli.commands.append
+  "Command to append to a page"
+  (:require [clojure.string :as string]
+            [logseq.cli.util :as cli-util]
+            [promesa.core :as p]))
+
+(defn append
+  [{{:keys [api-server-token args]} :opts}]
+  (let [text (string/join " " args)]
+    (-> (p/let [resp (cli-util/api-fetch api-server-token "logseq.app.append_block_in_page" [text nil nil])]
+          (if (= 200 (.-status resp))
+            (println "Success!")
+            (cli-util/api-handle-error-response resp)))
+        (p/catch cli-util/command-catch-handler))))

+ 80 - 0
deps/cli/src/logseq/cli/commands/export.cljs

@@ -0,0 +1,80 @@
+(ns logseq.cli.commands.export
+  "Export MD command"
+  (:require ["fs" :as fs]
+            [cljs.pprint]
+            [clojure.string :as string]
+            [datascript.core :as d]
+            [logseq.cli.common.export.common :as cli-export-common]
+            [logseq.cli.common.export.text :as cli-export-text]
+            [logseq.cli.common.file :as common-file]
+            [logseq.cli.common.util :as cli-common-util]
+            [logseq.cli.util :as cli-util]
+            [logseq.common.config :as common-config]
+            [logseq.common.util :as common-util]
+            [logseq.db.common.sqlite-cli :as sqlite-cli]))
+
+(defn- get-content-config [db]
+  (let [repo-config (-> (d/q '[:find ?content
+                               :where [?b :file/path "logseq/config.edn"] [?b :file/content ?content]]
+                             db)
+                        ffirst
+                        common-util/safe-read-map-string)
+        indent
+        ;; Copy of state/get-export-bullet-indentation
+        (case (get repo-config :export/bullet-indentation :tab)
+          :eight-spaces
+          "        "
+          :four-spaces
+          "    "
+          :two-spaces
+          "  "
+          :tab
+          "\t")]
+    {:export-bullet-indentation indent}))
+
+(defn- get-file-contents
+  "Modified version of export.common/<get-file-contents which doesn't have to deal with worker threads"
+  [repo db content-config suffix]
+  (let [page->content (common-file/get-all-page->content repo db content-config)]
+    (map (fn [[page-title content]]
+           {:path (str page-title "." suffix)
+            :content content
+            :title page-title
+            :format :markdown})
+         page->content)))
+
+(defn- export-files-as-markdown
+  "Modified version of handler.export.text/export-files-as-markdown for the CLI"
+  [repo files options]
+  (mapv
+   (fn [{:keys [path title content]}]
+     [(or path title) (cli-export-text/export-helper repo content :markdown options)])
+   files))
+
+(defn- export-repo-as-markdown!
+  "Modified version of handler.export.text/export-repo-as-markdown for the CLI"
+  [repo db {:keys [file]}]
+  (let [content-config (get-content-config db)
+        files* (get-file-contents repo db content-config "md")]
+    (when (seq files*)
+      (let [files (binding [cli-export-common/*current-db* db
+                            cli-export-common/*current-repo* repo
+                            cli-export-common/*content-config* content-config]
+                    (export-files-as-markdown repo files* nil))
+            repo' (string/replace repo common-config/db-version-prefix "")
+            zip-file-name (if file
+                            (string/replace-first file #"(?i)\.zip$" "")
+                            (str repo' "_markdown_" (quot (common-util/time-ms) 1000)))
+            file-name (or file (str zip-file-name ".zip"))
+            zip (cli-common-util/make-export-zip zip-file-name files)
+            ;; matches behavior in make-export-zip
+            exported-files (remove #(string/blank? (second %)) files)]
+        (-> (.generateNodeStream zip #js {:streamFiles true :type "nodebuffer"})
+            (.pipe (fs/createWriteStream file-name)))
+        (println "Exported" (count exported-files) "pages to" file-name)))))
+
+(defn export [{{:keys [graph] :as opts} :opts}]
+  (if (fs/existsSync (cli-util/get-graph-dir graph))
+    (let [conn (apply sqlite-cli/open-db! (cli-util/->open-db-args graph))]
+      (export-repo-as-markdown! (str common-config/db-version-prefix graph) @conn opts))
+    (cli-util/error "Graph" (pr-str graph) "does not exist")))

+ 12 - 13
deps/cli/src/logseq/cli/commands/export_edn.cljs

@@ -4,21 +4,20 @@
             [clojure.pprint :as pprint]
             [clojure.pprint :as pprint]
             [logseq.db.common.sqlite-cli :as sqlite-cli]
             [logseq.db.common.sqlite-cli :as sqlite-cli]
             [logseq.db.sqlite.export :as sqlite-export]
             [logseq.db.sqlite.export :as sqlite-export]
+            [logseq.common.util :as common-util]
             [logseq.cli.util :as cli-util]))
             [logseq.cli.util :as cli-util]))
 
 
 (defn export [{{:keys [graph] :as options} :opts}]
 (defn export [{{:keys [graph] :as options} :opts}]
   (if (fs/existsSync (cli-util/get-graph-dir graph))
   (if (fs/existsSync (cli-util/get-graph-dir graph))
-   (let [conn (apply sqlite-cli/open-db! (cli-util/->open-db-args graph))
-         export-map (sqlite-export/build-export @conn
-                                                (cond-> {:export-type (:export-type options)}
-                                                  (= :graph (:export-type options))
-                                                  (assoc :graph-options (dissoc options :file :export-type :graph))))]
-     (if (:file options)
-       (do
-         (println "Exported" (count (:properties export-map)) "properties,"
-                  (count (:properties export-map)) "classes and"
-                  (count (:pages-and-blocks export-map)) "pages")
-         (fs/writeFileSync (:file options)
-                           (with-out-str (pprint/pprint export-map))))
-       (pprint/pprint export-map)))
+    (let [conn (apply sqlite-cli/open-db! (cli-util/->open-db-args graph))
+          export-map (sqlite-export/build-export @conn
+                                                 (cond-> {:export-type (:export-type options)}
+                                                   (= :graph (:export-type options))
+                                                   (assoc :graph-options (dissoc options :file :export-type :graph))))
+          file (or (:file options) (str graph "_" (quot (common-util/time-ms) 1000) ".edn"))]
+      (println "Exported" (count (:properties export-map)) "properties,"
+               (count (:properties export-map)) "classes and"
+               (count (:pages-and-blocks export-map)) "pages to" file)
+      (fs/writeFileSync file
+                        (with-out-str (pprint/pprint export-map))))
     (cli-util/error "Graph" (pr-str graph) "does not exist")))
     (cli-util/error "Graph" (pr-str graph) "does not exist")))

+ 3 - 3
deps/cli/src/logseq/cli/commands/query.cljs

@@ -99,8 +99,8 @@
         (cli-util/error "Graph" (pr-str graph') "does not exist")))))
         (cli-util/error "Graph" (pr-str graph') "does not exist")))))
 
 
 (defn query
 (defn query
-  [{{:keys [graph args api-query-token]} :opts :as m}]
-  (if api-query-token
+  [{{:keys [graph args api-server-token]} :opts :as m}]
+  (if api-server-token
     ;; graph can be query since it's not used for api-query
     ;; graph can be query since it's not used for api-query
-    (api-query (or graph (first args)) api-query-token)
+    (api-query (or graph (first args)) api-server-token)
     (local-query m)))
     (local-query m)))

+ 4 - 4
deps/cli/src/logseq/cli/commands/search.cljs

@@ -41,8 +41,8 @@
                                 (map highlight-fn)))))))
                                 (map highlight-fn)))))))
 
 
 (defn- api-search
 (defn- api-search
-  [search-term {{:keys [api-query-token raw limit]} :opts}]
-  (-> (p/let [resp (cli-util/api-fetch api-query-token "logseq.app.search" [search-term {:limit limit}])]
+  [search-term {{:keys [api-server-token raw limit]} :opts}]
+  (-> (p/let [resp (cli-util/api-fetch api-server-token "logseq.app.search" [search-term {:limit limit}])]
         (if (= 200 (.-status resp))
         (if (= 200 (.-status resp))
           (p/let [body (.json resp)]
           (p/let [body (.json resp)]
             (let [{:keys [blocks]} (js->clj body :keywordize-keys true)]
             (let [{:keys [blocks]} (js->clj body :keywordize-keys true)]
@@ -61,7 +61,7 @@
       (format-results nodes search-term {:raw raw}))
       (format-results nodes search-term {:raw raw}))
     (cli-util/error "Graph" (pr-str graph) "does not exist")))
     (cli-util/error "Graph" (pr-str graph) "does not exist")))
 
 
-(defn search [{{:keys [graph search-terms api-query-token]} :opts :as m}]
-  (if api-query-token
+(defn search [{{:keys [graph search-terms api-server-token]} :opts :as m}]
+  (if api-server-token
     (api-search (string/join " " (into [graph] search-terms)) m)
     (api-search (string/join " " (into [graph] search-terms)) m)
     (local-search (string/join " " search-terms) m)))
     (local-search (string/join " " search-terms) m)))

+ 808 - 0
deps/cli/src/logseq/cli/common/export/common.cljs

@@ -0,0 +1,808 @@
+(ns logseq.cli.common.export.common
+  "common fns for exporting.
+  exclude some fns which produce lazy-seq, which can cause strange behaviors
+  when use together with dynamic var."
+  (:refer-clojure :exclude [map filter])
+  (:require [cljs.core.match :refer [match]]
+            [clojure.string :as string]
+            [datascript.core :as d]
+            [logseq.cli.common.file :as common-file]
+            [logseq.cli.common.util :as cli-common-util :refer-macros [removev concatv mapcatv]]
+            [logseq.common.util :as common-util]
+            [logseq.db :as ldb]
+            [logseq.graph-parser.mldoc :as gp-mldoc]
+            [logseq.outliner.tree :as otree]
+            [malli.core :as m]
+            [malli.util :as mu]))
+
+;;; TODO: split frontend.handler.export.text related states
+(def ^:dynamic *state*
+  "dynamic var, state used for exporting"
+  {;; current level of Heading, start from 1(same as mldoc), use when `block-ast->simple-ast`
+   :current-level 1
+   ;; emphasis symbol (use when `block-ast->simple-ast`)
+   :outside-em-symbol nil
+   ;; (use when `block-ast->simple-ast`)
+   :indent-after-break-line? false
+   ;; TODO: :last-empty-heading? false
+   ;;       current:  |  want:
+   ;;       -         |  - xxx
+   ;;         xxx     |    yyy
+   ;;         yyy     |
+
+   ;; this submap is used when replace block-reference, block-embed, page-embed
+   :replace-ref-embed
+   {;; start from 1
+    :current-level 1
+    :block-ref-replaced? false
+    :block&page-embed-replaced? false}
+
+   ;; submap for :newline-after-block internal state
+   :newline-after-block
+   {:current-block-is-first-heading-block? true}
+
+   ;; export-options submap
+   :export-options
+   {;; dashes, spaces, no-indent
+    :indent-style "dashes"
+    :remove-page-ref-brackets? false
+    :remove-emphasis? false
+    :remove-tags? false
+    :remove-properties? true
+    :keep-only-level<=N :all
+    :newline-after-block false}})
+
+;; Global vars that are not explicitly passed in all fns
+;; These vars must be bound in order to use most fns in this namespace
+(def ^:dynamic *current-db* nil)
+(def ^:dynamic *current-repo* nil)
+;; Config used by logseq.cli.common.file fns
+(def ^:dynamic *content-config* nil)
+
+;;; internal utils
+(defn ^:api get-blocks-contents
+  [repo root-block-uuid & {:keys [init-level]
+                           :or {init-level 1}}]
+  (let [block (d/entity *current-db* [:block/uuid root-block-uuid])
+        link (:block/link block)
+        block' (or link block)
+        root-id (:block/uuid block')
+        blocks (ldb/get-block-and-children *current-db* root-id)
+        tree (otree/blocks->vec-tree repo *current-db* blocks root-id {:link link})]
+    (common-file/tree->file-content *current-repo* *current-db* tree
+                                    {:init-level init-level :link link}
+                                    *content-config*)))
+
+(declare remove-block-ast-pos Properties-block-ast?)
+
+(defn- block-uuid->ast
+  [block-uuid]
+  (let [block (into {} (d/entity *current-db* [:block/uuid block-uuid]))
+        content (common-file/tree->file-content *current-repo* *current-db* [block] {:init-level 1} *content-config*)
+        format :markdown]
+    (when content
+      (removev Properties-block-ast?
+               (mapv remove-block-ast-pos
+                     (gp-mldoc/->edn *current-repo* content format))))))
+
+(defn- block-uuid->ast-with-children
+  [block-uuid]
+  (let [content (get-blocks-contents *current-repo* block-uuid)
+        format :markdown]
+    (when content
+      (removev Properties-block-ast?
+               (mapv remove-block-ast-pos
+                     (gp-mldoc/->edn *current-repo* content format))))))
+
+(defn ^:api get-page-content
+  [page-uuid]
+  (common-file/block->content *current-repo* *current-db* page-uuid nil *content-config*))
+
+(defn- page-name->ast
+  [page-name]
+  (let [page (ldb/get-page *current-db* page-name)]
+    (when-let [content (get-page-content (:block/uuid page))]
+      (when content
+        (let [format :markdown]
+          (removev Properties-block-ast?
+                   (mapv remove-block-ast-pos
+                         (gp-mldoc/->edn *current-repo* content format))))))))
+
+(defn- update-level-in-block-ast-coll
+  [block-ast-coll origin-level]
+  (mapv
+   (fn [block-ast]
+     (let [[ast-type ast-content] block-ast]
+       (if (= ast-type "Heading")
+         [ast-type (update ast-content :level #(+ (dec %) origin-level))]
+         block-ast)))
+   block-ast-coll))
+
+(defn- plain-indent-inline-ast
+  [level & {:keys [spaces] :or {spaces "  "}}]
+  ["Plain" (str (reduce str (repeat (dec level) "\t")) spaces)])
+
+(defn- mk-paragraph-ast
+  [inline-coll meta]
+  (with-meta ["Paragraph" inline-coll] meta))
+
+;;; internal utils (ends)
+
+;;; utils
+
+(defn priority->string
+  [priority]
+  (str "[#" priority "]"))
+
+(defn- repetition-to-string
+  [[[kind] [duration] n]]
+  (let [kind (case kind
+               "Dotted" "."
+               "Plus" "+"
+               "DoublePlus" "++")]
+    (str kind n (string/lower-case (str (first duration))))))
+
+(defn timestamp-to-string
+  [{:keys [date time repetition wday active]}]
+  (let [{:keys [year month day]} date
+        {:keys [hour min]} time
+        [open close] (if active ["<" ">"] ["[" "]"])
+        repetition (if repetition
+                     (str " " (repetition-to-string repetition))
+                     "")
+        hour (when hour (common-util/zero-pad hour))
+        min  (when min (common-util/zero-pad min))
+        time (cond
+               (and hour min)
+               (common-util/format " %s:%s" hour min)
+               hour
+               (common-util/format " %s" hour)
+               :else
+               "")]
+    (common-util/format "%s%s-%s-%s %s%s%s%s"
+                        open
+                        (str year)
+                        (common-util/zero-pad month)
+                        (common-util/zero-pad day)
+                        wday
+                        time
+                        repetition
+                        close)))
+
+(defn hashtag-value->string
+  [inline-coll]
+  (reduce str
+          (mapv
+           (fn [inline]
+             (let [[ast-type ast-content] inline]
+               (case ast-type
+                 "Nested_link"
+                 (:content ast-content)
+                 "Link"
+                 (:full_text ast-content)
+                 "Plain"
+                 ast-content)))
+           inline-coll)))
+
+;;; utils (ends)
+
+;;; replace block-ref, block-embed, page-embed
+
+(defn- replace-block-reference-in-heading
+  [{:keys [title] :as ast-content}]
+  (let [inline-coll  title
+        inline-coll*
+        (mapcatv
+         #(match [%]
+            [["Link" {:url ["Block_ref" block-uuid]}]]
+            (let [[[_ {title-inline-coll :title}]]
+                  (block-uuid->ast (uuid block-uuid))]
+              (set! *state* (assoc-in *state* [:replace-ref-embed :block-ref-replaced?] true))
+              title-inline-coll)
+
+            :else [%])
+         inline-coll)]
+    (assoc ast-content :title inline-coll*)))
+
+(defn- replace-block-reference-in-paragraph
+  [inline-coll]
+  (mapcatv
+   #(match [%]
+      [["Link" {:url ["Block_ref" block-uuid]}]]
+      (let [[[_ {title-inline-coll :title}]]
+            (block-uuid->ast (uuid block-uuid))]
+        (set! *state* (assoc-in *state* [:replace-ref-embed :block-ref-replaced?] true))
+        title-inline-coll)
+      :else [%])
+   inline-coll))
+
+(declare replace-block-references)
+
+(defn- replace-block-reference-in-list
+  [list-items]
+  (mapv
+   (fn [{block-ast-coll :content sub-items :items :as item}]
+     (assoc item
+            :content (mapv replace-block-references block-ast-coll)
+            :items (replace-block-reference-in-list sub-items)))
+   list-items))
+
+(defn- replace-block-reference-in-quote
+  [block-ast-coll]
+  (mapv replace-block-references block-ast-coll))
+
+(defn- replace-block-reference-in-table
+  [{:keys [header groups] :as table}]
+  (let [header*
+        (mapv
+         (fn [col]
+           (mapcatv
+            #(match [%]
+               [["Link" {:url ["Block_ref" block-uuid]}]]
+               (let [[[_ {title-inline-coll :title}]]
+                     (block-uuid->ast (uuid block-uuid))]
+                 (set! *state* (assoc-in *state* [:replace-ref-embed :block-ref-replaced?] true))
+                 title-inline-coll)
+               :else [%])
+            col))
+         header)
+        groups*
+        (mapv
+         (fn [group]
+           (mapv
+            (fn [row]
+              (mapv
+               (fn [col]
+                 (mapcatv
+                  #(match [%]
+                     [["Link" {:url ["Block_ref" block-uuid]}]]
+                     (let [[[_ {title-inline-coll :title}]]
+                           (block-uuid->ast (uuid block-uuid))]
+                       (set! *state* (assoc-in *state* [:replace-ref-embed :block-ref-replaced?] true))
+                       title-inline-coll)
+                     :else [%])
+                  col))
+               row))
+            group))
+         groups)]
+    (assoc table :header header* :groups groups*)))
+
+(defn- replace-block-references
+  [block-ast]
+  (let [[ast-type ast-content] block-ast]
+    (case ast-type
+      "Heading"
+      [ast-type (replace-block-reference-in-heading ast-content)]
+
+      "Paragraph"
+      (mk-paragraph-ast (replace-block-reference-in-paragraph ast-content) (meta block-ast))
+
+      "List"
+      [ast-type (replace-block-reference-in-list ast-content)]
+
+      "Quote"
+      [ast-type (replace-block-reference-in-quote ast-content)]
+
+      "Table"
+      [ast-type (replace-block-reference-in-table ast-content)]
+      ;; else
+      block-ast)))
+
+(defn- replace-block-references-until-stable
+  [block-ast]
+  (binding [*state* *state*]
+    (loop [block-ast block-ast]
+      (let [block-ast* (replace-block-references block-ast)]
+        (if (get-in *state* [:replace-ref-embed :block-ref-replaced?])
+          (do (set! *state* (assoc-in *state* [:replace-ref-embed :block-ref-replaced?] false))
+              (recur block-ast*))
+          block-ast*)))))
+
+(defn- replace-block-embeds-helper
+  [current-paragraph-inlines block-uuid blocks-tcoll level]
+  (let [block-uuid* (subs block-uuid 2 (- (count block-uuid) 2))
+        ast-coll (update-level-in-block-ast-coll
+                  (block-uuid->ast-with-children (uuid block-uuid*))
+                  level)]
+    (cond-> blocks-tcoll
+      (seq current-paragraph-inlines)
+      (conj! ["Paragraph" current-paragraph-inlines])
+      true
+      (#(reduce conj! % ast-coll)))))
+
+(defn- replace-page-embeds-helper
+  [current-paragraph-inlines page-name blocks-tcoll level]
+  (let [page-name* (subs page-name 2 (- (count page-name) 2))
+        ast-coll (update-level-in-block-ast-coll
+                  (page-name->ast page-name*)
+                  level)]
+    (cond-> blocks-tcoll
+      (seq current-paragraph-inlines)
+      (conj! ["Paragraph" current-paragraph-inlines])
+      true
+      (#(reduce conj! % ast-coll)))))
+
+(defn- replace-block&page-embeds-in-heading
+  [{inline-coll :title origin-level :level :as ast-content}]
+  (set! *state* (assoc-in *state* [:replace-ref-embed :current-level] origin-level))
+  (if (empty? inline-coll)
+    ;; it's just a empty Heading, return itself
+    [["Heading" ast-content]]
+    (loop [[inline & other-inlines] inline-coll
+           heading-exist? false
+           current-paragraph-inlines []
+           r (transient [])]
+      (if-not inline
+        (persistent!
+         (if (seq current-paragraph-inlines)
+           (conj! r (if heading-exist?
+                      ["Paragraph" current-paragraph-inlines]
+                      ["Heading" (assoc ast-content :title current-paragraph-inlines)]))
+           r))
+        (match [inline]
+          [["Macro" {:name "embed" :arguments [block-uuid-or-page-name]}]]
+          (cond
+            (and (string/starts-with? block-uuid-or-page-name "((")
+                 (string/ends-with? block-uuid-or-page-name "))"))
+            (do (set! *state* (assoc-in *state* [:replace-ref-embed :block&page-embed-replaced?] true))
+                (recur other-inlines true []
+                       (replace-block-embeds-helper
+                        current-paragraph-inlines block-uuid-or-page-name r origin-level)))
+            (and (string/starts-with? block-uuid-or-page-name "[[")
+                 (string/ends-with? block-uuid-or-page-name "]]"))
+            (do (set! *state* (assoc-in *state* [:replace-ref-embed :block&page-embed-replaced?] true))
+                (recur other-inlines true []
+                       (replace-page-embeds-helper
+                        current-paragraph-inlines block-uuid-or-page-name r origin-level)))
+            :else ;; not ((block-uuid)) or [[page-name]], just drop the original ast
+            (recur other-inlines heading-exist? current-paragraph-inlines r))
+
+          :else
+          (let [current-paragraph-inlines*
+                (if (and (empty? current-paragraph-inlines)
+                         heading-exist?)
+                  (conj current-paragraph-inlines (plain-indent-inline-ast origin-level))
+                  current-paragraph-inlines)]
+            (recur other-inlines heading-exist? (conj current-paragraph-inlines* inline) r)))))))
+
+(defn- replace-block&page-embeds-in-paragraph
+  [inline-coll meta]
+  (let [current-level (get-in *state* [:replace-ref-embed :current-level])]
+    (loop [[inline & other-inlines] inline-coll
+           current-paragraph-inlines []
+           just-after-embed? false
+           blocks (transient [])]
+      (if-not inline
+        (let [[first-block & other-blocks] (persistent!
+                                            (if (seq current-paragraph-inlines)
+                                              (conj! blocks ["Paragraph" current-paragraph-inlines])
+                                              blocks))]
+          (if first-block
+            (apply vector (with-meta first-block meta) other-blocks)
+            []))
+        (match [inline]
+          [["Macro" {:name "embed" :arguments [block-uuid-or-page-name]}]]
+          (cond
+            (and (string/starts-with? block-uuid-or-page-name "((")
+                 (string/ends-with? block-uuid-or-page-name "))"))
+            (do (set! *state* (assoc-in *state* [:replace-ref-embed :block&page-embed-replaced?] true))
+                (recur other-inlines [] true
+                       (replace-block-embeds-helper
+                        current-paragraph-inlines block-uuid-or-page-name blocks current-level)))
+            (and (string/starts-with? block-uuid-or-page-name "[[")
+                 (string/ends-with? block-uuid-or-page-name "]]"))
+            (do (set! *state* (assoc-in *state* [:replace-ref-embed :block&page-embed-replaced?] true))
+                (recur other-inlines [] true
+                       (replace-page-embeds-helper
+                        current-paragraph-inlines block-uuid-or-page-name blocks current-level)))
+            :else ;; not ((block-uuid)) or [[page-name]], just drop the original ast
+            (recur other-inlines current-paragraph-inlines false blocks))
+
+          :else
+          (let [current-paragraph-inlines*
+                (if just-after-embed?
+                  (conj current-paragraph-inlines (plain-indent-inline-ast current-level))
+                  current-paragraph-inlines)]
+            (recur other-inlines (conj current-paragraph-inlines* inline) false blocks)))))))
+
+(declare replace-block&page-embeds)
+
+(defn- replace-block&page-embeds-in-list-helper
+  [list-items]
+  (binding [*state* (update-in *state* [:replace-ref-embed :current-level] inc)]
+    (mapv
+     (fn [{block-ast-coll :content sub-items :items :as item}]
+       (assoc item
+              :content (mapcatv replace-block&page-embeds block-ast-coll)
+              :items (replace-block&page-embeds-in-list-helper sub-items)))
+     list-items)))
+
+(defn- replace-block&page-embeds-in-list
+  [list-items]
+  [["List" (replace-block&page-embeds-in-list-helper list-items)]])
+
+(defn- replace-block&page-embeds-in-quote
+  [block-ast-coll]
+  (->> block-ast-coll
+       (mapcatv replace-block&page-embeds)
+       (vector "Quote")
+       vector))
+
+(defn- replace-block&page-embeds
+  [block-ast]
+  (let [[ast-type ast-content] block-ast]
+    (case ast-type
+      "Heading"
+      (replace-block&page-embeds-in-heading ast-content)
+      "Paragraph"
+      (replace-block&page-embeds-in-paragraph ast-content (meta block-ast))
+      "List"
+      (replace-block&page-embeds-in-list ast-content)
+      "Quote"
+      (replace-block&page-embeds-in-quote ast-content)
+      "Table"
+      ;; TODO: block&page embeds in table are not replaced yet
+      [block-ast]
+      ;; else
+      [block-ast])))
+
+(defn replace-block&page-reference&embed
+  "add meta :embed-depth to the embed replaced block-ast,
+  to avoid too deep block-ref&embed (or maybe it's a cycle)"
+  [block-ast-coll]
+  (loop [block-ast-coll block-ast-coll
+         result-block-ast-tcoll (transient [])
+         block-ast-coll-to-replace-references []
+         block-ast-coll-to-replace-embeds []]
+    (cond
+      (seq block-ast-coll-to-replace-references)
+      (let [[block-ast-to-replace-ref & other-block-asts-to-replace-ref]
+            block-ast-coll-to-replace-references
+            embed-depth (:embed-depth (meta block-ast-to-replace-ref) 0)
+            block-ast-replaced (-> (replace-block-references-until-stable block-ast-to-replace-ref)
+                                   (with-meta {:embed-depth embed-depth}))]
+        (if (>= embed-depth 5)
+          ;; if :embed-depth >= 5, dont replace embed for this block anymore
+          ;; there is too deep, or maybe it just a ref/embed cycle
+          (recur block-ast-coll (conj! result-block-ast-tcoll block-ast-replaced)
+                 (vec other-block-asts-to-replace-ref) block-ast-coll-to-replace-embeds)
+          (recur block-ast-coll result-block-ast-tcoll (vec other-block-asts-to-replace-ref)
+                 (conj block-ast-coll-to-replace-embeds block-ast-replaced))))
+
+      (seq block-ast-coll-to-replace-embeds)
+      (let [[block-ast-to-replace-embed & other-block-asts-to-replace-embed]
+            block-ast-coll-to-replace-embeds
+            embed-depth (:embed-depth (meta block-ast-to-replace-embed) 0)
+            block-ast-coll-replaced (->> (replace-block&page-embeds block-ast-to-replace-embed)
+                                         (mapv #(with-meta % {:embed-depth (inc embed-depth)})))]
+        (if (get-in *state* [:replace-ref-embed :block&page-embed-replaced?])
+          (do (set! *state* (assoc-in *state* [:replace-ref-embed :block&page-embed-replaced?] false))
+              (recur block-ast-coll result-block-ast-tcoll
+                     (concatv block-ast-coll-to-replace-references block-ast-coll-replaced)
+                     (vec other-block-asts-to-replace-embed)))
+          (recur block-ast-coll (reduce conj! result-block-ast-tcoll block-ast-coll-replaced)
+                 (vec block-ast-coll-to-replace-references) (vec other-block-asts-to-replace-embed))))
+
+      :else
+      (let [[block-ast & other-block-ast] block-ast-coll]
+        (if-not block-ast
+          (persistent! result-block-ast-tcoll)
+          (recur other-block-ast result-block-ast-tcoll
+                 (conj block-ast-coll-to-replace-references block-ast)
+                 (vec block-ast-coll-to-replace-embeds)))))))
+
+;;; replace block-ref, block-embed, page-embed (ends)
+
+(def remove-block-ast-pos
+  "[[ast-type ast-content] _pos] -> [ast-type ast-content]"
+  first)
+
+(defn Properties-block-ast?
+  [[tp _]]
+  (= tp "Properties"))
+
+(defn replace-Heading-with-Paragraph
+  "works on block-ast
+  replace all heading with paragraph when indent-style is no-indent"
+  [heading-ast]
+  (let [[heading-type {:keys [title marker priority size]}] heading-ast]
+    (if (= heading-type "Heading")
+      (let [inline-coll
+            (cond->> title
+              priority (cons ["Plain" (str (priority->string priority) " ")])
+              marker (cons ["Plain" (str marker " ")])
+              size (cons ["Plain" (str (reduce str (repeat size "#")) " ")])
+              true vec)]
+        (mk-paragraph-ast inline-coll {:origin-ast heading-ast}))
+      heading-ast)))
+
+(defn keep-only-level<=n
+  [block-ast-coll n]
+  (-> (reduce
+       (fn [{:keys [result-ast-tcoll accepted-heading] :as r} ast]
+         (let [[heading-type {level :level}] ast
+               is-heading?                   (= heading-type "Heading")]
+           (cond
+             (and (not is-heading?) accepted-heading)
+             {:result-ast-tcoll (conj! result-ast-tcoll ast) :accepted-heading accepted-heading}
+
+             (and (not is-heading?) (not accepted-heading))
+             r
+
+             (and is-heading? (<= level n))
+             {:result-ast-tcoll (conj! result-ast-tcoll ast) :accepted-heading true}
+
+             (and is-heading? (> level n))
+             {:result-ast-tcoll result-ast-tcoll :accepted-heading false})))
+       {:result-ast-tcoll  (transient []) :accepted-heading false}
+       block-ast-coll)
+      :result-ast-tcoll
+      persistent!))
+
+;;; inline transformers
+
+(defn remove-emphasis
+  ":mapcat-fns-on-inline-ast"
+  [inline-ast]
+  (let [[ast-type ast-content] inline-ast]
+    (case ast-type
+      "Emphasis"
+      (let [[_ inline-coll] ast-content]
+        inline-coll)
+      ;; else
+      [inline-ast])))
+
+(defn remove-page-ref-brackets
+  ":map-fns-on-inline-ast"
+  [inline-ast]
+  (let [[ast-type ast-content] inline-ast]
+    (case ast-type
+      "Link"
+      (let [{:keys [url label]} ast-content]
+        (if (and (= "Page_ref" (first url))
+                 (or (empty? label)
+                     (= label [["Plain" ""]])))
+          ["Plain" (second url)]
+          inline-ast))
+      ;; else
+      inline-ast)))
+
+(defn remove-tags
+  ":mapcat-fns-on-inline-ast"
+  [inline-ast]
+  (let [[ast-type _ast-content] inline-ast]
+    (case ast-type
+      "Tag"
+      []
+      ;; else
+      [inline-ast])))
+
+(defn remove-prefix-spaces-in-Plain
+  [inline-coll]
+  (:r
+   (reduce
+    (fn [{:keys [r after-break-line?]} ast]
+      (let [[ast-type ast-content] ast]
+        (case ast-type
+          "Plain"
+          (let [trimmed-content (string/triml ast-content)]
+            (if after-break-line?
+              (if (empty? trimmed-content)
+                {:r r :after-break-line? false}
+                {:r (conj r ["Plain" trimmed-content]) :after-break-line? false})
+              {:r (conj r ast) :after-break-line? false}))
+          ("Break_Line" "Hard_Break_Line")
+          {:r (conj r ast) :after-break-line? true}
+        ;; else
+          {:r (conj r ast) :after-break-line? false})))
+    {:r [] :after-break-line? true}
+    inline-coll)))
+
+;;; inline transformers (ends)
+
+;;; walk on block-ast, apply inline transformers
+
+(defn- walk-block-ast-helper
+  [inline-coll map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll]
+  (->>
+   (reduce (fn [inline-coll f] (f inline-coll)) inline-coll fns-on-inline-coll)
+   (mapv #(reduce (fn [inline-ast f] (f inline-ast)) % map-fns-on-inline-ast))
+   (mapcatv #(reduce
+              (fn [inline-ast-coll f] (mapcatv f inline-ast-coll)) [%] mapcat-fns-on-inline-ast))))
+
+(declare walk-block-ast)
+
+(defn- walk-block-ast-for-list
+  [list-items map-fns-on-inline-ast mapcat-fns-on-inline-ast]
+  (mapv
+   (fn [{block-ast-coll :content sub-items :items :as item}]
+     (assoc item
+            :content
+            (mapv
+             (partial walk-block-ast
+                      {:map-fns-on-inline-ast map-fns-on-inline-ast
+                       :mapcat-fns-on-inline-ast mapcat-fns-on-inline-ast})
+             block-ast-coll)
+            :items
+            (walk-block-ast-for-list sub-items map-fns-on-inline-ast mapcat-fns-on-inline-ast)))
+   list-items))
+
+(defn walk-block-ast
+  [{:keys [map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll] :as fns}
+   block-ast]
+  (let [[ast-type ast-content] block-ast]
+    (case ast-type
+      "Paragraph"
+      (mk-paragraph-ast
+       (walk-block-ast-helper ast-content map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll)
+       (meta block-ast))
+      "Heading"
+      (let [{:keys [title]} ast-content]
+        ["Heading"
+         (assoc ast-content
+                :title
+                (walk-block-ast-helper title map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll))])
+      "List"
+      ["List" (walk-block-ast-for-list ast-content map-fns-on-inline-ast mapcat-fns-on-inline-ast)]
+      "Quote"
+      ["Quote" (mapv (partial walk-block-ast fns) ast-content)]
+      "Footnote_Definition"
+      (let [[name contents] (rest block-ast)]
+        ["Footnote_Definition"
+         name (walk-block-ast-helper contents map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll)])
+      "Table"
+      (let [{:keys [header groups]} ast-content
+            header* (mapv
+                     #(walk-block-ast-helper % map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll)
+                     header)
+            groups* (mapv
+                     (fn [group]
+                       (mapv
+                        (fn [row]
+                          (mapv
+                           (fn [col]
+                             (walk-block-ast-helper col map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll))
+                           row))
+                        group))
+                     groups)]
+        ["Table" (assoc ast-content :header header* :groups groups*)])
+
+       ;; else
+      block-ast)))
+
+;;; walk on block-ast, apply inline transformers (ends)
+
+;;; simple ast
+(def simple-ast-malli-schema
+  (mu/closed-schema
+   [:or
+    [:map
+     [:type [:= :raw-text]]
+     [:content :string]]
+    [:map
+     [:type [:= :space]]]
+    [:map
+     [:type [:= :newline]]
+     [:line-count :int]]
+    [:map
+     [:type [:= :indent]]
+     [:level :int]
+     [:extra-space-count :int]]]))
+
+(defn raw-text [& contents]
+  {:type :raw-text :content (reduce str contents)})
+(def space {:type :space})
+(defn newline* [line-count]
+  {:type :newline :line-count line-count})
+(defn indent [level extra-space-count]
+  {:type :indent :level level :extra-space-count extra-space-count})
+
+(defn- simple-ast->string
+  [simple-ast]
+  {:pre [(m/validate simple-ast-malli-schema simple-ast)]}
+  (case (:type simple-ast)
+    :raw-text (:content simple-ast)
+    :space " "
+    :newline (reduce str (repeat (:line-count simple-ast) "\n"))
+    :indent (reduce str (concatv (repeat (:level simple-ast) "\t")
+                                 (repeat (:extra-space-count simple-ast) " ")))))
+
+(defn- ^:large-vars/cleanup-todo merge-adjacent-spaces&newlines
+  [simple-ast-coll]
+  (loop [r                             (transient [])
+         last-ast                      nil
+         last-raw-text-space-suffix?   false
+         last-raw-text-newline-suffix? false
+         [simple-ast & other-ast-coll] simple-ast-coll]
+    (if (nil? simple-ast)
+      (persistent! (if last-ast (conj! r last-ast) r))
+      (let [tp            (:type simple-ast)
+            last-ast-type (:type last-ast)]
+        (case tp
+          :space
+          (if (or (contains? #{:space :newline :indent} last-ast-type)
+                  last-raw-text-space-suffix?
+                  last-raw-text-newline-suffix?)
+            ;; drop this :space
+            (recur r last-ast last-raw-text-space-suffix? last-raw-text-newline-suffix? other-ast-coll)
+            (recur (if last-ast (conj! r last-ast) r) simple-ast false false other-ast-coll))
+
+          :newline
+          (case last-ast-type
+            (:space :indent) ;; drop last-ast
+            (recur r simple-ast false false other-ast-coll)
+            :newline
+            (let [last-newline-count (:line-count last-ast)
+                  current-newline-count (:line-count simple-ast)
+                  kept-ast (if (> last-newline-count current-newline-count) last-ast simple-ast)]
+              (recur r kept-ast false false other-ast-coll))
+            :raw-text
+            (if last-raw-text-newline-suffix?
+              (recur r last-ast last-raw-text-space-suffix? last-raw-text-newline-suffix? other-ast-coll)
+              (recur (if last-ast (conj! r last-ast) r) simple-ast false false other-ast-coll))
+            ;; no-last-ast
+            (recur r simple-ast false false other-ast-coll))
+
+          :indent
+          (case last-ast-type
+            (:space :indent)            ; drop last-ast
+            (recur r simple-ast false false other-ast-coll)
+            :newline
+            (recur (if last-ast (conj! r last-ast) r) simple-ast false false other-ast-coll)
+            :raw-text
+            (if last-raw-text-space-suffix?
+              ;; drop this :indent
+              (recur r last-ast last-raw-text-space-suffix? last-raw-text-newline-suffix? other-ast-coll)
+              (recur (if last-ast (conj! r last-ast) r) simple-ast false false other-ast-coll))
+            ;; no-last-ast
+            (recur r simple-ast false false other-ast-coll))
+
+          :raw-text
+          (let [content         (:content simple-ast)
+                empty-content?  (empty? content)
+                first-ch        (first content)
+                last-ch         (let [num (count content)]
+                                  (when (pos? num)
+                                    (nth content (dec num))))
+                newline-prefix? (some-> first-ch #{"\r" "\n"} boolean)
+                newline-suffix? (some-> last-ch #{"\n"} boolean)
+                space-prefix?   (some-> first-ch #{" "} boolean)
+                space-suffix?   (some-> last-ch #{" "} boolean)]
+            (cond
+              empty-content?            ;drop this raw-text
+              (recur r last-ast last-raw-text-space-suffix? last-raw-text-newline-suffix? other-ast-coll)
+              newline-prefix?
+              (case last-ast-type
+                (:space :indent :newline) ;drop last-ast
+                (recur r simple-ast space-suffix? newline-suffix? other-ast-coll)
+                :raw-text
+                (recur (if last-ast (conj! r last-ast) r) simple-ast space-suffix? newline-suffix? other-ast-coll)
+                ;; no-last-ast
+                (recur r simple-ast space-suffix? newline-suffix? other-ast-coll))
+              space-prefix?
+              (case last-ast-type
+                (:space :indent)        ;drop last-ast
+                (recur r simple-ast space-suffix? newline-suffix? other-ast-coll)
+                (:newline :raw-text)
+                (recur (if last-ast (conj! r last-ast) r) simple-ast space-suffix? newline-suffix? other-ast-coll)
+                ;; no-last-ast
+                (recur r simple-ast space-suffix? newline-suffix? other-ast-coll))
+              :else
+              (recur (if last-ast (conj! r last-ast) r) simple-ast space-suffix? newline-suffix? other-ast-coll))))))))
+
+(defn simple-asts->string
+  [simple-ast-coll]
+  (->> simple-ast-coll
+       merge-adjacent-spaces&newlines
+       merge-adjacent-spaces&newlines
+       (mapv simple-ast->string)
+       string/join))
+
+;;; simple ast (ends)
+
+;;; TODO: walk the hiccup tree,
+;;; and call escape-html on all its contents
+;;;
+
+;;; walk the hiccup tree,
+;;; and call escape-html on all its contents (ends)

+ 493 - 0
deps/cli/src/logseq/cli/common/export/text.cljs

@@ -0,0 +1,493 @@
+(ns logseq.cli.common.export.text
+  "Common fns between frontend and CLI for exporting as markdown"
+  (:require [clojure.string :as string]
+            [logseq.cli.common.export.common :as cli-export-common :refer
+             [*state* newline* indent raw-text space simple-asts->string]]
+            [logseq.cli.common.util :refer-macros [removev concatv mapcatv]]
+            [logseq.graph-parser.mldoc :as gp-mldoc]
+            [logseq.graph-parser.schema.mldoc :as mldoc-schema]))
+
+;;; block-ast, inline-ast -> simple-ast
+
+(defn ^:api indent-with-2-spaces
+  "also consider (get-in *state* [:export-options :indent-style])"
+  [level]
+  (let [indent-style (get-in *state* [:export-options :indent-style])]
+    (case indent-style
+      "dashes"               (indent level 2)
+      ("spaces" "no-indent") (indent level 0)
+      (assert false (print-str "unknown indent-style:" indent-style)))))
+
+(declare inline-ast->simple-ast
+         block-ast->simple-ast)
+
+(defn- block-heading
+  [{:keys [title _tags marker level _numbering priority _anchor _meta _unordered size]}]
+  (let [indent-style (get-in *state* [:export-options :indent-style])
+        priority* (and priority (raw-text (cli-export-common/priority->string priority)))
+        heading* (if (= indent-style "dashes")
+                   [(indent (dec level) 0) (raw-text "-")]
+                   [(indent (dec level) 0)])
+        size* (and size [space (raw-text (reduce str (repeat size "#")))])
+        marker* (and marker (raw-text marker))]
+    (set! *state* (assoc *state* :current-level level))
+    (let [simple-asts
+          (removev nil? (concatv
+                         (when (and (get-in *state* [:export-options :newline-after-block])
+                                    (not (get-in *state* [:newline-after-block :current-block-is-first-heading-block?])))
+                           [(newline* 2)])
+                         heading* size*
+                         [space marker* space priority* space]
+                         (mapcatv inline-ast->simple-ast title)
+                         [(newline* 1)]))]
+      (set! *state* (assoc-in *state* [:newline-after-block :current-block-is-first-heading-block?] false))
+      simple-asts)))
+
+(declare block-list)
+(defn- block-list-item
+  [{:keys [content items number _name checkbox]}]
+  (let [content* (mapcatv block-ast->simple-ast content)
+        number* (raw-text
+                 (if number
+                   (str number ". ")
+                   "* "))
+        checkbox* (raw-text
+                   (if (some? checkbox)
+                     (if (boolean checkbox)
+                       "[X]" "[ ]")
+                     ""))
+        current-level (get *state* :current-level 1)
+        indent' (when (> current-level 1)
+                  (indent (dec current-level) 0))
+        items* (block-list items :in-list? true)]
+    (concatv [indent' number* checkbox* space]
+             content*
+             [(newline* 1)]
+             items*
+             [(newline* 1)])))
+
+(defn- block-list
+  [l & {:keys [in-list?]}]
+  (binding [*state* (update *state* :current-level inc)]
+    (concatv (mapcatv block-list-item l)
+             (when (and (pos? (count l))
+                        (not in-list?))
+               [(newline* 2)]))))
+
+(defn- block-property-drawer
+  [properties]
+  (when-not (get-in *state* [:export-options :remove-properties?])
+    (let [level (dec (get *state* :current-level 1))
+          indent' (indent-with-2-spaces level)]
+      (reduce
+       (fn [r [k v]]
+         (conj r indent' (raw-text k "::") space (raw-text v) (newline* 1)))
+       [] properties))))
+
+(defn- block-example
+  [l]
+  (let [level (dec (get *state* :current-level 1))]
+    (mapcatv
+     (fn [line]
+       [(indent-with-2-spaces level)
+        (raw-text "    ")
+        (raw-text line)
+        (newline* 1)])
+     l)))
+
+(defn- remove-max-prefix-spaces
+  [lines]
+  (let [common-prefix-spaces
+        (reduce
+         (fn [r line]
+           (if (string/blank? line)
+             r
+             (let [leading-spaces (re-find #"^\s+" line)]
+               (if (nil? r)
+                 leading-spaces
+                 (if (string/starts-with? r leading-spaces)
+                   leading-spaces
+                   r)))))
+         nil
+         lines)
+        pattern (re-pattern (str "^" common-prefix-spaces))]
+    (mapv (fn [line] (string/replace-first line pattern "")) lines)))
+
+(defn- block-src
+  [{:keys [lines language]}]
+  (let [level (dec (get *state* :current-level 1))
+        lines* (if (= "no-indent" (get-in *state* [:export-options :indent-style]))
+                 (remove-max-prefix-spaces lines)
+                 lines)]
+    (concatv
+     [(indent-with-2-spaces level) (raw-text "```")]
+     (when language [(raw-text language)])
+     [(newline* 1)]
+     (mapv raw-text lines*)
+     [(indent-with-2-spaces level) (raw-text "```") (newline* 1)])))
+
+(defn- block-quote
+  [block-coll]
+  (let [level (dec (get *state* :current-level 1))]
+    (binding [*state* (assoc *state* :indent-after-break-line? true)]
+      (concatv (mapcatv (fn [block]
+                          (let [block-simple-ast (block-ast->simple-ast block)]
+                            (when (seq block-simple-ast)
+                              (concatv [(indent-with-2-spaces level) (raw-text ">") space]
+                                       block-simple-ast))))
+                        block-coll)
+               [(newline* 2)]))))
+
+(declare inline-latex-fragment)
+(defn- block-latex-fragment
+  [ast-content]
+  (inline-latex-fragment ast-content))
+
+(defn- block-latex-env
+  [[name options content]]
+  (let [level (dec (get *state* :current-level 1))]
+    [(indent-with-2-spaces level) (raw-text "\\begin{" name "}" options)
+     (newline* 1)
+     (indent-with-2-spaces level) (raw-text content)
+     (newline* 1)
+     (indent-with-2-spaces level) (raw-text "\\end{" name "}")
+     (newline* 1)]))
+
+(defn- block-displayed-math
+  [ast-content]
+  [space (raw-text "$$" ast-content "$$") space])
+
+(defn- block-drawer
+  [[name lines]]
+  (let [level (dec (get *state* :current-level))]
+    (concatv
+     [(raw-text ":" name ":")
+      (newline* 1)]
+     (mapcatv (fn [line] [(indent-with-2-spaces level) (raw-text line)]) lines)
+     [(newline* 1) (raw-text ":END:") (newline* 1)])))
+
+(defn- block-footnote-definition
+  [[name content]]
+  (concatv
+   [(raw-text "[^" name "]:") space]
+   (mapcatv inline-ast->simple-ast content)
+   [(newline* 1)]))
+
+(def ^:private block-horizontal-rule [(newline* 1) (raw-text "---") (newline* 1)])
+
+(defn- block-table
+  [{:keys [header groups]}]
+  (let [level    (dec (get *state* :current-level 1))
+        sep-line (raw-text "|" (string/join "|" (repeat (count header) "---")) "|")
+        header-line
+        (concatv (mapcatv
+                  (fn [h] (concatv [space (raw-text "|") space] (mapcatv inline-ast->simple-ast h)))
+                  header)
+                 [space (raw-text "|")])
+        group-lines
+        (mapcatv
+         (fn [group]
+           (mapcatv
+            (fn [row]
+              (concatv [(indent-with-2-spaces level)]
+                       (mapcatv
+                        (fn [col]
+                          (concatv [(raw-text "|") space]
+                                   (mapcatv inline-ast->simple-ast col)
+                                   [space]))
+                        row)
+                       [(raw-text "|") (newline* 1)]))
+            group))
+         groups)]
+    (concatv [(newline* 1) (indent-with-2-spaces level)]
+             (when (seq header) header-line)
+             (when (seq header) [(newline* 1) (indent-with-2-spaces level) sep-line (newline* 1)])
+             group-lines)))
+
+(defn- block-comment
+  [s]
+  (let [level (dec (get *state* :current-level 1))]
+    [(indent-with-2-spaces level) (raw-text "<!---") (newline* 1)
+     (indent-with-2-spaces level) (raw-text s) (newline* 1)
+     (indent-with-2-spaces level) (raw-text "-->") (newline* 1)]))
+
+(defn- block-raw-html
+  [s]
+  (let [level (dec (get *state* :current-level 1))]
+    [(indent-with-2-spaces level) (raw-text s) (newline* 1)]))
+
+(defn- block-hiccup
+  [s]
+  (let [level (dec (get *state* :current-level 1))]
+    [(indent-with-2-spaces level) (raw-text s) space]))
+
+(defn- inline-link
+  [{full-text :full_text}]
+  [(raw-text full-text)])
+
+(defn- inline-nested-link
+  [{content :content}]
+  [(raw-text content)])
+
+(defn- inline-subscript
+  [inline-coll]
+  (concatv [(raw-text "_{")]
+           (mapcatv (fn [inline] (cons space (inline-ast->simple-ast inline))) inline-coll)
+           [(raw-text "}")]))
+
+(defn- inline-superscript
+  [inline-coll]
+  (concatv [(raw-text "^{")]
+           (mapcatv (fn [inline] (cons space (inline-ast->simple-ast inline))) inline-coll)
+           [(raw-text "}")]))
+
+(defn- inline-footnote-reference
+  [{name :name}]
+  [(raw-text  "[" name "]")])
+
+(defn- inline-cookie
+  [ast-content]
+  [(raw-text
+    (case (first ast-content)
+      "Absolute"
+      (let [[_ current total] ast-content]
+        (str "[" current "/" total "]"))
+      "Percent"
+      (str "[" (second ast-content) "%]")))])
+
+(defn- inline-latex-fragment
+  [ast-content]
+  (let [[type content] ast-content
+        wrapper (case type
+                  "Inline" "$"
+                  "Displayed" "$$")]
+    [space (raw-text (str wrapper content wrapper)) space]))
+
+(defn- inline-macro
+  [{:keys [name arguments]}]
+  (->
+   (if (= name "cloze")
+     (string/join "," arguments)
+     (let [l (cond-> ["{{" name]
+               (pos? (count arguments)) (conj "(" (string/join "," arguments) ")")
+               true (conj "}}"))]
+       (string/join l)))
+   raw-text
+   vector))
+
+(defn- inline-entity
+  [{unicode :unicode}]
+  [(raw-text unicode)])
+
+(defn- inline-timestamp
+  [ast-content]
+  (let [[type timestamp-content] ast-content]
+    (-> (case type
+          "Scheduled" ["SCHEDULED: " (cli-export-common/timestamp-to-string timestamp-content)]
+          "Deadline" ["DEADLINE: " (cli-export-common/timestamp-to-string timestamp-content)]
+          "Date" [(cli-export-common/timestamp-to-string timestamp-content)]
+          "Closed" ["CLOSED: " (cli-export-common/timestamp-to-string timestamp-content)]
+          "Clock" ["CLOCK: " (cli-export-common/timestamp-to-string (second timestamp-content))]
+          "Range" (let [{:keys [start stop]} timestamp-content]
+                    [(str (cli-export-common/timestamp-to-string start) "--" (cli-export-common/timestamp-to-string stop))]))
+        string/join
+        raw-text
+        vector)))
+
+(defn- inline-email
+  [{:keys [local_part domain]}]
+  [(raw-text (str "<" local_part "@" domain ">"))])
+
+(defn- emphasis-wrap-with
+  [inline-coll em-symbol]
+  (binding [*state* (assoc *state* :outside-em-symbol (first em-symbol))]
+    (concatv [(raw-text em-symbol)]
+             (mapcatv inline-ast->simple-ast inline-coll)
+             [(raw-text em-symbol)])))
+
+(defn- inline-emphasis
+  [emphasis]
+  (let [[[type] inline-coll] emphasis
+        outside-em-symbol (:outside-em-symbol *state*)]
+    (case type
+      "Bold"
+      (emphasis-wrap-with inline-coll (if (= outside-em-symbol "*") "__" "**"))
+      "Italic"
+      (emphasis-wrap-with inline-coll (if (= outside-em-symbol "*") "_" "*"))
+      "Underline"
+      (binding [*state* (assoc *state* :outside-em-symbol outside-em-symbol)]
+        (mapcatv (fn [inline] (cons space (inline-ast->simple-ast inline))) inline-coll))
+      "Strike_through"
+      (emphasis-wrap-with inline-coll "~~")
+      "Highlight"
+      (emphasis-wrap-with inline-coll "^^")
+      ;; else
+      (assert false (print-str :inline-emphasis emphasis "is invalid")))))
+
+(defn- inline-break-line
+  []
+  [(if (= "no-indent" (get-in *state* [:export-options :indent-style]))
+     (raw-text "\n")
+     (raw-text "  \n"))
+   (when (:indent-after-break-line? *state*)
+     (let [current-level (get *state* :current-level 1)]
+       (when (> current-level 1)
+         (indent-with-2-spaces (dec current-level)))))])
+
+;; {:malli/schema ...} only works on public vars so make this public
+(defn ^:large-vars/cleanup-todo ^:api block-ast->simple-ast
+  {:malli/schema [:=> [:cat mldoc-schema/block-ast-schema] [:sequential cli-export-common/simple-ast-malli-schema]]}
+  [block]
+  (let [newline-after-block? (get-in *state* [:export-options :newline-after-block])]
+    (removev
+     nil?
+     (let [[ast-type ast-content] block]
+       (case ast-type
+         "Paragraph"
+         (let [{:keys [origin-ast]} (meta block)
+               current-block-is-first-heading-block? (get-in *state* [:newline-after-block :current-block-is-first-heading-block?])]
+           (set! *state* (assoc-in *state* [:newline-after-block :current-block-is-first-heading-block?] false))
+           (concatv
+            (when (and origin-ast newline-after-block? (not current-block-is-first-heading-block?))
+              [(newline* 2)])
+            (mapcatv inline-ast->simple-ast ast-content)
+            (let [last-element (last ast-content)
+                  [last-element-type] last-element]
+              (when (and newline-after-block? (= "Break_Line" last-element-type))
+                (inline-break-line)))
+            [(newline* 1)]))
+         "Paragraph_line"
+         (assert false "Paragraph_line is mldoc internal ast")
+         "Paragraph_Sep"
+         [(newline* ast-content)]
+         "Heading"
+         (block-heading ast-content)
+         "List"
+         (block-list ast-content)
+         ("Directive" "Results" "Export" "CommentBlock" "Custom")
+         nil
+         "Example"
+         (block-example ast-content)
+         "Src"
+         (block-src ast-content)
+         "Quote"
+         (block-quote ast-content)
+         "Latex_Fragment"
+         (block-latex-fragment ast-content)
+         "Latex_Environment"
+         (block-latex-env (rest block))
+         "Displayed_Math"
+         (block-displayed-math ast-content)
+         "Drawer"
+         (block-drawer (rest block))
+         "Property_Drawer"
+         (block-property-drawer ast-content)
+         "Footnote_Definition"
+         (block-footnote-definition (rest block))
+         "Horizontal_Rule"
+         block-horizontal-rule
+         "Table"
+         (block-table ast-content)
+         "Comment"
+         (block-comment ast-content)
+         "Raw_Html"
+         (block-raw-html ast-content)
+         "Hiccup"
+         (block-hiccup ast-content)
+         (assert false (print-str :block-ast->simple-ast ast-type "not implemented yet")))))))
+
+(defn- ^:large-vars/cleanup-todo inline-ast->simple-ast
+  [inline]
+  (let [[ast-type ast-content] inline]
+    (case ast-type
+      "Emphasis"
+      (inline-emphasis ast-content)
+      ("Break_Line" "Hard_Break_Line")
+      (inline-break-line)
+      "Verbatim"
+      [(raw-text ast-content)]
+      "Code"
+      [(raw-text "`" ast-content "`")]
+      "Tag"
+      [(raw-text (str "#" (cli-export-common/hashtag-value->string ast-content)))]
+      "Spaces"                          ; what's this ast-type for ?
+      nil
+      "Plain"
+      [(raw-text ast-content)]
+      "Link"
+      (inline-link ast-content)
+      "Nested_link"
+      (inline-nested-link ast-content)
+      "Target"
+      [(raw-text (str "<<" ast-content ">>"))]
+      "Subscript"
+      (inline-subscript ast-content)
+      "Superscript"
+      (inline-superscript ast-content)
+      "Footnote_Reference"
+      (inline-footnote-reference ast-content)
+      "Cookie"
+      (inline-cookie ast-content)
+      "Latex_Fragment"
+      (inline-latex-fragment ast-content)
+      "Macro"
+      (inline-macro ast-content)
+      "Entity"
+      (inline-entity ast-content)
+      "Timestamp"
+      (inline-timestamp ast-content)
+      "Radio_Target"
+      [(raw-text (str "<<<" ast-content ">>>"))]
+      "Email"
+      (inline-email ast-content)
+      "Inline_Hiccup"
+      [(raw-text ast-content)]
+      "Inline_Html"
+      [(raw-text ast-content)]
+      ("Export_Snippet" "Inline_Source_Block")
+      nil
+      (assert false (print-str :inline-ast->simple-ast ast-type "not implemented yet")))))
+
+;;; block-ast, inline-ast -> simple-ast (ends)
+
+(defn ^:large-vars/cleanup-todo export-helper
+  [repo content format options]
+  (let [remove-options (set (:remove-options options))
+        other-options (:other-options options)]
+    (binding [*state* (merge *state*
+                             {:export-options
+                              {:indent-style (or (:indent-style options) "dashes")
+                               :remove-emphasis? (contains? remove-options :emphasis)
+                               :remove-page-ref-brackets? (contains? remove-options :page-ref)
+                               :remove-tags? (contains? remove-options :tag)
+                               :remove-properties? (contains? remove-options :property)
+                               :keep-only-level<=N (:keep-only-level<=N other-options)
+                               :newline-after-block (:newline-after-block other-options)}})]
+      (let [ast (gp-mldoc/->edn repo content format)
+            ast (mapv cli-export-common/remove-block-ast-pos ast)
+            ast (removev cli-export-common/Properties-block-ast? ast)
+            ast* (cli-export-common/replace-block&page-reference&embed ast)
+            keep-level<=n (get-in *state* [:export-options :keep-only-level<=N])
+            ast* (if (pos? keep-level<=n)
+                   (cli-export-common/keep-only-level<=n ast* keep-level<=n)
+                   ast*)
+            ast** (if (= "no-indent" (get-in *state* [:export-options :indent-style]))
+                    (mapv cli-export-common/replace-Heading-with-Paragraph ast*)
+                    ast*)
+            config-for-walk-block-ast (cond-> {}
+                                        (get-in *state* [:export-options :remove-emphasis?])
+                                        (update :mapcat-fns-on-inline-ast conj cli-export-common/remove-emphasis)
+
+                                        (get-in *state* [:export-options :remove-page-ref-brackets?])
+                                        (update :map-fns-on-inline-ast conj cli-export-common/remove-page-ref-brackets)
+
+                                        (get-in *state* [:export-options :remove-tags?])
+                                        (update :mapcat-fns-on-inline-ast conj cli-export-common/remove-tags)
+
+                                        (= "no-indent" (get-in *state* [:export-options :indent-style]))
+                                        (update :fns-on-inline-coll conj cli-export-common/remove-prefix-spaces-in-Plain))
+            ast*** (if-not (empty? config-for-walk-block-ast)
+                     (mapv (partial cli-export-common/walk-block-ast config-for-walk-block-ast) ast**)
+                     ast**)
+            simple-asts (mapcatv block-ast->simple-ast ast***)]
+        (simple-asts->string simple-asts)))))

+ 21 - 4
src/main/frontend/common/file/core.cljs → deps/cli/src/logseq/cli/common/file.cljs

@@ -1,11 +1,12 @@
-(ns frontend.common.file.core
-  "Convert blocks to file content. Used for exports and saving file to disk. Shared
-  by worker and frontend namespaces"
+(ns logseq.cli.common.file
+  "Convert blocks to file content for file and DB graphs. Used for exports and
+  saving file to disk. Shared by CLI, worker and frontend namespaces"
   (:require [clojure.string :as string]
   (:require [clojure.string :as string]
             [datascript.core :as d]
             [datascript.core :as d]
             [logseq.db :as ldb]
             [logseq.db :as ldb]
             [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.frontend.content :as db-content]
             [logseq.db.frontend.content :as db-content]
+            [logseq.db.sqlite.create-graph :as sqlite-create-graph]
             [logseq.db.sqlite.util :as sqlite-util]
             [logseq.db.sqlite.util :as sqlite-util]
             [logseq.graph-parser.property :as gp-property]
             [logseq.graph-parser.property :as gp-property]
             [logseq.outliner.tree :as otree]))
             [logseq.outliner.tree :as otree]))
@@ -29,7 +30,7 @@
     :else
     :else
     content))
     content))
 
 
-(defn- transform-content
+(defn- ^:large-vars/cleanup-todo transform-content
   [repo db {:block/keys [collapsed? format pre-block? properties] :as b} level {:keys [heading-to-list?]} context {:keys [db-based?]}]
   [repo db {:block/keys [collapsed? format pre-block? properties] :as b} level {:keys [heading-to-list?]} context {:keys [db-based?]}]
   (let [title (or (:block/raw-title b) (:block/title b))
   (let [title (or (:block/raw-title b) (:block/title b))
         block-ref-not-saved? (and (not db-based?)
         block-ref-not-saved? (and (not db-based?)
@@ -91,6 +92,7 @@
               (if-let [children (seq (:block/children f))]
               (if-let [children (seq (:block/children f))]
                 (cons content (tree->file-content-aux repo db children {:init-level (inc level)} context))
                 (cons content (tree->file-content-aux repo db children {:init-level (inc level)} context))
                 [content])]
                 [content])]
+          #_:clj-kondo/ignore
           (conj! block-contents new-content)
           (conj! block-contents new-content)
           (recur r level))))))
           (recur r level))))))
 
 
@@ -120,3 +122,18 @@
     (tree->file-content repo db tree
     (tree->file-content repo db tree
                         (assoc tree->file-opts :init-level init-level)
                         (assoc tree->file-opts :init-level init-level)
                         context)))
                         context)))
+
+(defn get-all-page->content
+  "Exports a graph's pages as tuples of page name and page content"
+  [repo db options]
+  (let [filter-fn (if (ldb/db-based-graph? db)
+                    (fn [ent]
+                      (or (not (:logseq.property/built-in? ent))
+                          (contains? sqlite-create-graph/built-in-pages-names (:block/title ent))))
+                    (constantly true))]
+    (->> (d/datoms db :avet :block/name)
+         (map #(d/entity db (:e %)))
+         (filter filter-fn)
+         (map (fn [e]
+                [(:block/title e)
+                 (block->content repo db (:block/uuid e) {} options)])))))

+ 1 - 1
deps/cli/src/logseq/cli/common/graph.cljs

@@ -7,7 +7,7 @@
             [logseq.common.config :as common-config]
             [logseq.common.config :as common-config]
             [logseq.common.graph :as common-graph]))
             [logseq.common.graph :as common-graph]))
 
 
-(defn- graph-name->path
+(defn ^:api graph-name->path
   [graph-name]
   [graph-name]
   (when graph-name
   (when graph-name
     (-> graph-name
     (-> graph-name

+ 36 - 0
deps/cli/src/logseq/cli/common/util.cljc

@@ -0,0 +1,36 @@
+(ns logseq.cli.common.util
+  "Common util fns between CLI and frontend"
+  (:require #?(:org.babashka/nbb ["jszip$default" :as JSZip]
+               :cljs ["jszip" :as JSZip])
+            #_:clj-kondo/ignore
+            [clojure.string :as string]))
+
+#?(:cljs
+   (defn make-export-zip
+     "Makes a zipfile for an exported version of graph. Removes files with blank content"
+     [zip-filename file-name-content]
+     (let [zip (JSZip.)
+           folder (.folder zip zip-filename)]
+       (doseq [[file-name content] file-name-content]
+         (when-not (string/blank? content)
+           (.file folder (-> file-name
+                             (string/replace #"^/+" ""))
+                  content)))
+       zip)))
+
+;; Macros are defined at top-level for frontend and nbb
+
+(defmacro concatv
+  "Vector version of concat. non-lazy"
+  [& args]
+  `(vec (concat ~@args)))
+
+(defmacro mapcatv
+  "Vector version of mapcat. non-lazy"
+  [f coll & colls]
+  `(vec (mapcat ~f ~coll ~@colls)))
+
+(defmacro removev
+  "Vector version of remove. non-lazy"
+  [pred coll]
+  `(vec (remove ~pred ~coll)))

+ 17 - 9
deps/cli/src/logseq/cli/spec.cljs

@@ -2,11 +2,15 @@
   "Babashka.cli specs for commands. Normally these would live alongside their
   "Babashka.cli specs for commands. Normally these would live alongside their
   commands but are separate because command namespaces are lazy loaded")
   commands but are separate because command namespaces are lazy loaded")
 
 
+(def export
+  {:file {:alias :f
+          :desc "File to save export"}})
+
 (def export-edn
 (def export-edn
   {:include-timestamps? {:alias :T
   {:include-timestamps? {:alias :T
                          :desc "Include timestamps in export"}
                          :desc "Include timestamps in export"}
    :file {:alias :f
    :file {:alias :f
-          :desc "Saves edn to file"}
+          :desc "File to save export"}
    :catch-validation-errors? {:alias :c
    :catch-validation-errors? {:alias :c
                               :desc "Catch validation errors for dev"}
                               :desc "Catch validation errors for dev"}
    :exclude-namespaces {:alias :e
    :exclude-namespaces {:alias :e
@@ -24,20 +28,24 @@
 (def query
 (def query
   {:graphs {:alias :g
   {:graphs {:alias :g
             :coerce []
             :coerce []
-            :desc "Additional graphs to query"}
+            :desc "Additional graphs to local query"}
    :properties-readable {:alias :p
    :properties-readable {:alias :p
                          :coerce :boolean
                          :coerce :boolean
-                         :desc "Make properties on entity queries show property values instead of ids"}
+                         :desc "Make properties on local, entity queries show property values instead of ids"}
    :title-query {:alias :t
    :title-query {:alias :t
-                 :desc "Invokes local query on :block/title"}
-   :api-query-token {:alias :a
-                     :desc "Query current graph with api server token"}})
+                 :desc "Invoke local query on :block/title"}
+   :api-server-token {:alias :a
+                      :desc "API server token to query current graph"}})
 
 
 (def search
 (def search
-  {:api-query-token {:alias :a
-                     :desc "Api server token"}
+  {:api-server-token {:alias :a
+                      :desc "API server token to search current graph"}
    :raw {:alias :r
    :raw {:alias :r
          :desc "Print raw response"}
          :desc "Print raw response"}
    :limit {:alias :l
    :limit {:alias :l
            :default 100
            :default 100
-           :desc "Limit max number of results"}})
+           :desc "Limit max number of results"}})
+
+(def append
+  {:api-server-token {:alias :a
+                      :desc "API server token to modify current graph"}})

+ 21 - 1
deps/cli/src/logseq/cli/text_util.cljs

@@ -20,4 +20,24 @@
                 m-cut (subs m-cut 0 e-pos)]
                 m-cut (subs m-cut 0 e-pos)]
             [b-cut m-cut e-cut])
             [b-cut m-cut e-cut])
           [b-cut m-cut nil]))
           [b-cut m-cut nil]))
-      [value nil nil])))
+      [value nil nil])))
+
+(defn wrap-text
+  "Wraps a string to a given width without breaking words. Returns a single string with newlines."
+  [s width]
+  (->> (loop [remaining (string/trim s)
+              acc []]
+         (if (empty? remaining)
+           acc
+           (if (<= (count remaining) width)
+             (conj acc remaining)
+             (let [substring (subs remaining 0 width)
+                   split-idx (or (string/last-index-of substring \space)
+                                 (string/last-index-of substring \tab)
+                                 ;; fallback: hard split
+                                 80)
+                   line (subs remaining 0 split-idx)
+                   rest' (subs remaining split-idx)]
+               (recur (string/triml rest')
+                      (conj acc line))))))
+       (string/join "\n")))

+ 14 - 6
deps/cli/src/logseq/cli/util.cljs

@@ -1,9 +1,10 @@
 (ns ^:node-only logseq.cli.util
 (ns ^:node-only logseq.cli.util
-  "Util fns"
+  "CLI only util fns"
   (:require ["path" :as node-path]
   (:require ["path" :as node-path]
             [clojure.string :as string]
             [clojure.string :as string]
             [logseq.cli.common.graph :as cli-common-graph]
             [logseq.cli.common.graph :as cli-common-graph]
             [logseq.db.common.sqlite :as common-sqlite]
             [logseq.db.common.sqlite :as common-sqlite]
+            [promesa.core :as p]
             [nbb.error]))
             [nbb.error]))
 
 
 (defn get-graph-dir
 (defn get-graph-dir
@@ -18,18 +19,25 @@
 (defn api-fetch [token method args]
 (defn api-fetch [token method args]
   (js/fetch "http://127.0.0.1:12315/api"
   (js/fetch "http://127.0.0.1:12315/api"
             (clj->js {:method "POST"
             (clj->js {:method "POST"
-                      :headers {"Authorization" (str "Bearer " token)
+                      :headers {"Authorization"
+                                (str "Bearer " (or token js/process.env.LOGSEQ_API_SERVER_TOKEN))
                                 "Content-Type" "application/json"}
                                 "Content-Type" "application/json"}
                       :body (js/JSON.stringify
                       :body (js/JSON.stringify
                              (clj->js {:method method
                              (clj->js {:method method
                                        :args args}))})))
                                        :args args}))})))
 
 
 (defn api-handle-error-response
 (defn api-handle-error-response
-  "Handles a non 200 response"
+  "Handles a non 200 response. For 500 return full response to provide more detail"
   [resp]
   [resp]
-  (js/console.error "Error: API Server responded with status" (.-status resp)
-                    (when (.-statusText resp) (str "and body " (pr-str (.-statusText resp)))))
-  (js/process.exit 1))
+  (if (= 500 (.-status resp))
+    (p/let [body (.text resp)]
+      (js/console.error "Error: API Server responded with status" (.-status resp)
+                        "\nAPI Response:" (pr-str body))
+      (js/process.exit 1))
+    (do
+      (js/console.error "Error: API Server responded with status" (.-status resp)
+                        (when (.-statusText resp) (str "and body " (pr-str (.-statusText resp)))))
+      (js/process.exit 1))))
 
 
 (defn command-catch-handler
 (defn command-catch-handler
   "Default p/catch handler for commands which handles sci errors and HTTP API Server connections gracefully"
   "Default p/catch handler for commands which handles sci errors and HTTP API Server connections gracefully"

+ 418 - 5
deps/cli/yarn.lock

@@ -2,12 +2,22 @@
 # yarn lockfile v1
 # yarn lockfile v1
 
 
 
 
-"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v26":
-  version "1.2.173-feat-db-v25"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/b26d290944234b20762ff109e5328b87ea240692"
+"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v28":
+  version "1.2.173-feat-db-v28"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/c0410ff81a8b0d510705581cccd39788d862dc91"
   dependencies:
   dependencies:
     import-meta-resolve "^4.1.0"
     import-meta-resolve "^4.1.0"
 
 
+ansi-regex@^2.0.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+  integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==
+
+ansi-regex@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1"
+  integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==
+
 base64-js@^1.3.1:
 base64-js@^1.3.1:
   version "1.5.1"
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@@ -45,11 +55,51 @@ buffer@^5.5.0:
     base64-js "^1.3.1"
     base64-js "^1.3.1"
     ieee754 "^1.1.13"
     ieee754 "^1.1.13"
 
 
+camelcase@^5.0.0:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+  integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+
 chownr@^1.1.1:
 chownr@^1.1.1:
   version "1.1.4"
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
   integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
   integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
 
 
+cliui@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
+  integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==
+  dependencies:
+    string-width "^2.1.1"
+    strip-ansi "^4.0.0"
+    wrap-ansi "^2.0.0"
+
+code-point-at@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+  integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==
+
+core-util-is@~1.0.0:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
+  integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
+
+cross-spawn@^6.0.0:
+  version "6.0.6"
+  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57"
+  integrity sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==
+  dependencies:
+    nice-try "^1.0.4"
+    path-key "^2.0.1"
+    semver "^5.5.0"
+    shebang-command "^1.2.0"
+    which "^1.2.9"
+
+decamelize@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+  integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
+
 decompress-response@^6.0.0:
 decompress-response@^6.0.0:
   version "6.0.0"
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
   resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
@@ -74,6 +124,19 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1:
   dependencies:
   dependencies:
     once "^1.4.0"
     once "^1.4.0"
 
 
+execa@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
+  integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
+  dependencies:
+    cross-spawn "^6.0.0"
+    get-stream "^4.0.0"
+    is-stream "^1.1.0"
+    npm-run-path "^2.0.0"
+    p-finally "^1.0.0"
+    signal-exit "^3.0.0"
+    strip-eof "^1.0.0"
+
 expand-template@^2.0.3:
 expand-template@^2.0.3:
   version "2.0.3"
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
   resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
@@ -84,6 +147,13 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
   resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
   integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
   integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
 
 
+find-up@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
+  integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
+  dependencies:
+    locate-path "^3.0.0"
+
 fs-constants@^1.0.0:
 fs-constants@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
   resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
@@ -98,6 +168,18 @@ fs-extra@^11.3.0:
     jsonfile "^6.0.1"
     jsonfile "^6.0.1"
     universalify "^2.0.0"
     universalify "^2.0.0"
 
 
+get-caller-file@^1.0.1:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
+  integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
+
+get-stream@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
+  integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
+  dependencies:
+    pump "^3.0.0"
+
 [email protected]:
 [email protected]:
   version "0.0.0"
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
   resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
@@ -113,12 +195,17 @@ ieee754@^1.1.13:
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
 
 
+immediate@~3.0.5:
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
+  integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
+
 import-meta-resolve@^4.1.0:
 import-meta-resolve@^4.1.0:
   version "4.1.0"
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#f9db8bead9fafa61adb811db77a2bf22c5399706"
   resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#f9db8bead9fafa61adb811db77a2bf22c5399706"
   integrity sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==
   integrity sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==
 
 
-inherits@^2.0.3, inherits@^2.0.4:
+inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
   version "2.0.4"
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -128,6 +215,38 @@ ini@~1.3.0:
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
   integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
   integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
 
 
+invert-kv@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
+  integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
+
+is-fullwidth-code-point@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
+  integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==
+  dependencies:
+    number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+  integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==
+
+is-stream@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+  integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==
+
+isarray@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+  integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
+
+isexe@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+  integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
 jsonfile@^6.0.1:
 jsonfile@^6.0.1:
   version "6.1.0"
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
   resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
@@ -137,6 +256,59 @@ jsonfile@^6.0.1:
   optionalDependencies:
   optionalDependencies:
     graceful-fs "^4.1.6"
     graceful-fs "^4.1.6"
 
 
[email protected]:
+  version "3.8.0"
+  resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.8.0.tgz#a2ac3c33fe96a76489765168213655850254d51b"
+  integrity sha512-cnpQrXvFSLdsR9KR5/x7zdf6c3m8IhZfZzSblFEHSqBaVwD2nvJ4CuCKLyvKvwBgZm08CgfSoiTBQLm5WW9hGw==
+  dependencies:
+    lie "~3.3.0"
+    pako "~1.0.2"
+    readable-stream "~2.3.6"
+    set-immediate-shim "~1.0.1"
+
+lcid@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
+  integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==
+  dependencies:
+    invert-kv "^2.0.0"
+
+lie@~3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
+  integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
+  dependencies:
+    immediate "~3.0.5"
+
+locate-path@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
+  integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
+  dependencies:
+    p-locate "^3.0.0"
+    path-exists "^3.0.0"
+
+map-age-cleaner@^0.1.1:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
+  integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==
+  dependencies:
+    p-defer "^1.0.0"
+
+mem@^4.0.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
+  integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==
+  dependencies:
+    map-age-cleaner "^0.1.1"
+    mimic-fn "^2.0.0"
+    p-is-promise "^2.0.0"
+
+mimic-fn@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+  integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+
 mimic-response@^3.1.0:
 mimic-response@^3.1.0:
   version "3.1.0"
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
   resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
@@ -152,11 +324,23 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
   resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
   resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
   integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
   integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
 
 
+mldoc@^1.5.9:
+  version "1.5.9"
+  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-1.5.9.tgz#43d740351c64285f0f4988ac9497922d54ae66fc"
+  integrity sha512-87FQ7hseS87tsk+VdpIigpu8LH+GwmbbFgpxgFwvnbH5oOjmIrc47laH4Dyggzqiy8/vMjDHkl7vsId0eXhCDQ==
+  dependencies:
+    yargs "^12.0.2"
+
 napi-build-utils@^2.0.0:
 napi-build-utils@^2.0.0:
   version "2.0.0"
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
   resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
   integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==
   integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==
 
 
+nice-try@^1.0.4:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
+  integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
+
 node-abi@^3.3.0:
 node-abi@^3.3.0:
   version "3.75.0"
   version "3.75.0"
   resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.75.0.tgz#2f929a91a90a0d02b325c43731314802357ed764"
   resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.75.0.tgz#2f929a91a90a0d02b325c43731314802357ed764"
@@ -164,6 +348,18 @@ node-abi@^3.3.0:
   dependencies:
   dependencies:
     semver "^7.3.5"
     semver "^7.3.5"
 
 
+npm-run-path@^2.0.0:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
+  integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==
+  dependencies:
+    path-key "^2.0.0"
+
+number-is-nan@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+  integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==
+
 once@^1.3.1, once@^1.4.0:
 once@^1.3.1, once@^1.4.0:
   version "1.4.0"
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
   resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -171,6 +367,64 @@ once@^1.3.1, once@^1.4.0:
   dependencies:
   dependencies:
     wrappy "1"
     wrappy "1"
 
 
+os-locale@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
+  integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
+  dependencies:
+    execa "^1.0.0"
+    lcid "^2.0.0"
+    mem "^4.0.0"
+
+p-defer@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
+  integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==
+
+p-finally@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+  integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
+
+p-is-promise@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
+  integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
+
+p-limit@^2.0.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+  integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
+  dependencies:
+    p-try "^2.0.0"
+
+p-locate@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
+  integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
+  dependencies:
+    p-limit "^2.0.0"
+
+p-try@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+  integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+
+pako@~1.0.2:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
+  integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
+
+path-exists@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
+  integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==
+
+path-key@^2.0.0, path-key@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
+  integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==
+
 prebuild-install@^7.1.1:
 prebuild-install@^7.1.1:
   version "7.1.3"
   version "7.1.3"
   resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec"
   resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec"
@@ -189,6 +443,11 @@ prebuild-install@^7.1.1:
     tar-fs "^2.0.0"
     tar-fs "^2.0.0"
     tunnel-agent "^0.6.0"
     tunnel-agent "^0.6.0"
 
 
+process-nextick-args@~2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
 pump@^3.0.0:
 pump@^3.0.0:
   version "3.0.3"
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d"
   resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d"
@@ -216,16 +475,76 @@ readable-stream@^3.1.1, readable-stream@^3.4.0:
     string_decoder "^1.1.1"
     string_decoder "^1.1.1"
     util-deprecate "^1.0.1"
     util-deprecate "^1.0.1"
 
 
+readable-stream@~2.3.6:
+  version "2.3.8"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
+  integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.3"
+    isarray "~1.0.0"
+    process-nextick-args "~2.0.0"
+    safe-buffer "~5.1.1"
+    string_decoder "~1.1.1"
+    util-deprecate "~1.0.1"
+
+require-directory@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+  integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
+require-main-filename@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
+  integrity sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==
+
 safe-buffer@^5.0.1, safe-buffer@~5.2.0:
 safe-buffer@^5.0.1, safe-buffer@~5.2.0:
   version "5.2.1"
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
 
 
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+semver@^5.5.0:
+  version "5.7.2"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
+  integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
+
 semver@^7.3.5:
 semver@^7.3.5:
   version "7.7.2"
   version "7.7.2"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
   integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
   integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
 
 
+set-blocking@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+  integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
+
+set-immediate-shim@~1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
+  integrity sha512-Li5AOqrZWCVA2n5kryzEmqai6bKSIvpz5oUJHPVj6+dsbD3X1ixtsY5tEnsaNpH3pFAHmG8eIHUrtEtohrg+UQ==
+
+shebang-command@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+  integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==
+  dependencies:
+    shebang-regex "^1.0.0"
+
+shebang-regex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+  integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==
+
+signal-exit@^3.0.0:
+  version "3.0.7"
+  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
+  integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+
 simple-concat@^1.0.0:
 simple-concat@^1.0.0:
   version "1.0.1"
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
   resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
@@ -240,6 +559,23 @@ simple-get@^4.0.0:
     once "^1.3.1"
     once "^1.3.1"
     simple-concat "^1.0.0"
     simple-concat "^1.0.0"
 
 
+string-width@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
+  integrity sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==
+  dependencies:
+    code-point-at "^1.0.0"
+    is-fullwidth-code-point "^1.0.0"
+    strip-ansi "^3.0.0"
+
+string-width@^2.0.0, string-width@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
+  integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
+  dependencies:
+    is-fullwidth-code-point "^2.0.0"
+    strip-ansi "^4.0.0"
+
 string_decoder@^1.1.1:
 string_decoder@^1.1.1:
   version "1.3.0"
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@@ -247,6 +583,32 @@ string_decoder@^1.1.1:
   dependencies:
   dependencies:
     safe-buffer "~5.2.0"
     safe-buffer "~5.2.0"
 
 
+string_decoder@~1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+  dependencies:
+    safe-buffer "~5.1.0"
+
+strip-ansi@^3.0.0, strip-ansi@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
+  integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==
+  dependencies:
+    ansi-regex "^2.0.0"
+
+strip-ansi@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+  integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==
+  dependencies:
+    ansi-regex "^3.0.0"
+
+strip-eof@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
+  integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==
+
 strip-json-comments@~2.0.1:
 strip-json-comments@~2.0.1:
   version "2.0.1"
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
@@ -285,12 +647,63 @@ universalify@^2.0.0:
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
   integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
   integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
 
 
-util-deprecate@^1.0.1:
+util-deprecate@^1.0.1, util-deprecate@~1.0.1:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
   integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
 
 
+which-module@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409"
+  integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
+
+which@^1.2.9:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+  integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
+  dependencies:
+    isexe "^2.0.0"
+
+wrap-ansi@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
+  integrity sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==
+  dependencies:
+    string-width "^1.0.1"
+    strip-ansi "^3.0.1"
+
 wrappy@1:
 wrappy@1:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
   integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+"y18n@^3.2.1 || ^4.0.0":
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
+  integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
+
+yargs-parser@^11.1.1:
+  version "11.1.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
+  integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
+yargs@^12.0.2:
+  version "12.0.5"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
+  integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
+  dependencies:
+    cliui "^4.0.0"
+    decamelize "^1.2.0"
+    find-up "^3.0.0"
+    get-caller-file "^1.0.1"
+    os-locale "^3.0.0"
+    require-directory "^2.1.1"
+    require-main-filename "^1.0.1"
+    set-blocking "^2.0.0"
+    string-width "^2.0.0"
+    which-module "^2.0.0"
+    y18n "^3.2.1 || ^4.0.0"
+    yargs-parser "^11.1.1"

+ 1 - 1
deps/common/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "version": "1.0.0",
   "private": true,
   "private": true,
   "devDependencies": {
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v26"
+    "@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v28"
   },
   },
   "scripts": {
   "scripts": {
     "test": "yarn nbb-logseq -cp test -m nextjournal.test-runner"
     "test": "yarn nbb-logseq -cp test -m nextjournal.test-runner"

+ 4 - 13
deps/common/src/logseq/common/defkeywords.cljc

@@ -3,7 +3,6 @@
   #?(:cljs (:require-macros [logseq.common.defkeywords])))
   #?(:cljs (:require-macros [logseq.common.defkeywords])))
 
 
 (def ^:private *defined-kws (volatile! {}))
 (def ^:private *defined-kws (volatile! {}))
-(def ^:private *defined-kw->config (volatile! {}))
 
 
 #_:clj-kondo/ignore
 #_:clj-kondo/ignore
 (defmacro defkeyword
 (defmacro defkeyword
@@ -25,16 +24,8 @@
           (vswap! *defined-kws assoc kw current-meta)
           (vswap! *defined-kws assoc kw current-meta)
           (throw (ex-info "keyword already defined somewhere else" {:kw kw :info info}))))
           (throw (ex-info "keyword already defined somewhere else" {:kw kw :info info}))))
       (vswap! *defined-kws assoc kw current-meta))
       (vswap! *defined-kws assoc kw current-meta))
-    (let [kw->config (partition 2 keyvals)]
-      (doseq [[kw config] kw->config]
-        (vswap! *defined-kw->config assoc kw config))))
+    ;; (let [kw->config (partition 2 keyvals)]
+    ;;   (doseq [[kw config] kw->config]
+    ;;     (vswap! *defined-kw->config assoc kw config)))
+    )
   `(vector ~@keyvals))
   `(vector ~@keyvals))
-
-(defmacro get-all-defined-kw->config
-  []
-  `'~(deref *defined-kw->config))
-
-(comment
-  "update anything here to trigger this ns to be recompiled,
-so macro get-all-defined-kw->config's result will be updated."
-  1)

+ 0 - 1
deps/common/src/logseq/common/util.cljs

@@ -113,7 +113,6 @@
       (js->clj :keywordize-keys true)))
       (js->clj :keywordize-keys true)))
 
 
 (defn zero-pad
 (defn zero-pad
-  "Copy of frontend.util/zero-pad. Too basic to couple to main app"
   [n]
   [n]
   (if (< n 10)
   (if (< n 10)
     (str "0" n)
     (str "0" n)

+ 7 - 5
deps/common/src/logseq/common/util/date_time.cljs

@@ -6,6 +6,8 @@
             [clojure.string :as string]
             [clojure.string :as string]
             [logseq.common.util :as common-util]))
             [logseq.common.util :as common-util]))
 
 
+(def ^:private yyyyMMdd-formatter (tf/formatter "yyyyMMdd"))
+
 ;; (tf/parse (tf/formatter "dd.MM.yyyy") "2021Q4") => 20040120T000000
 ;; (tf/parse (tf/formatter "dd.MM.yyyy") "2021Q4") => 20040120T000000
 (defn safe-journal-title-formatters
 (defn safe-journal-title-formatters
   [date-formatter]
   [date-formatter]
@@ -32,7 +34,7 @@
   (when journal-title
   (when journal-title
     (let [journal-title (common-util/capitalize-all journal-title)]
     (let [journal-title (common-util/capitalize-all journal-title)]
       (journal-title-> journal-title
       (journal-title-> journal-title
-                       #(parse-long (tf/unparse (tf/formatter "yyyyMMdd") %))
+                       #(parse-long (tf/unparse yyyyMMdd-formatter %))
                        formatters))))
                        formatters))))
 
 
 (defn format
 (defn format
@@ -51,7 +53,7 @@
 (defn int->journal-title
 (defn int->journal-title
   [day date-formatter]
   [day date-formatter]
   (when day
   (when day
-    (format (tf/parse (tf/formatter "yyyyMMdd") (str day)) date-formatter)))
+    (format (tf/parse yyyyMMdd-formatter (str day)) date-formatter)))
 
 
 (defn- get-weekday
 (defn- get-weekday
   [date]
   [date]
@@ -94,7 +96,7 @@
   "Converts a journal's :block/journal-day integer into milliseconds"
   "Converts a journal's :block/journal-day integer into milliseconds"
   [day]
   [day]
   (when day
   (when day
-    (-> (tf/parse (tf/formatter "yyyyMMdd") (str day))
+    (-> (tf/parse yyyyMMdd-formatter (str day))
         (tc/to-long))))
         (tc/to-long))))
 
 
 (defn ms->journal-day
 (defn ms->journal-day
@@ -103,5 +105,5 @@
   (some->> ms
   (some->> ms
            tc/from-long
            tc/from-long
            t/to-default-time-zone
            t/to-default-time-zone
-           (tf/unparse (tf/formatter "yyyyMMdd"))
-           parse-long))
+           (tf/unparse yyyyMMdd-formatter)
+           parse-long))

+ 3 - 3
deps/common/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 # yarn lockfile v1
 
 
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v26":
-  version "1.2.173-feat-db-v25"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/b26d290944234b20762ff109e5328b87ea240692"
+"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v28":
+  version "1.2.173-feat-db-v28"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/c0410ff81a8b0d510705581cccd39788d862dc91"
   dependencies:
   dependencies:
     import-meta-resolve "^4.1.0"
     import-meta-resolve "^4.1.0"
 
 

+ 2 - 0
deps/db/.carve/ignore

@@ -44,3 +44,5 @@ logseq.db.sqlite.gc/gc-kvs-table!
 logseq.db.sqlite.gc/gc-kvs-table-node-version!
 logseq.db.sqlite.gc/gc-kvs-table-node-version!
 ;; API
 ;; API
 logseq.db.sqlite.gc/ensure-no-garbage
 logseq.db.sqlite.gc/ensure-no-garbage
+;; documenting keywords
+logseq.db.frontend.kv-entity/kv-entities

+ 1 - 1
deps/db/bb.edn

@@ -29,7 +29,7 @@
    :doc "Lint datalog rules for parsability and unbound variables"
    :doc "Lint datalog rules for parsability and unbound variables"
    :task (datalog/lint-rules
    :task (datalog/lint-rules
           (set
           (set
-           (concat (mapcat val (merge file-rules/rules rules/rules))
+           (concat (mapcat val (merge file-rules/rules (dissoc rules/rules :self-ref)))
                    ;; TODO: Update linter to handle false positive on ?str-val for :property
                    ;; TODO: Update linter to handle false positive on ?str-val for :property
                    (rules/extract-rules (dissoc file-rules/query-dsl-rules :property))
                    (rules/extract-rules (dissoc file-rules/query-dsl-rules :property))
                    ;; TODO: Update linter to handle false positive on :task, :priority, :*property* rules
                    ;; TODO: Update linter to handle false positive on :task, :priority, :*property* rules

+ 1 - 1
deps/db/deps.edn

@@ -1,7 +1,7 @@
 {:deps
 {:deps
  ;; These nbb-logseq deps are kept in sync with https://github.com/logseq/nbb-logseq/blob/main/bb.edn
  ;; These nbb-logseq deps are kept in sync with https://github.com/logseq/nbb-logseq/blob/main/bb.edn
  {datascript/datascript {:git/url "https://github.com/logseq/datascript" ;; fork
  {datascript/datascript {:git/url "https://github.com/logseq/datascript" ;; fork
-                         :sha     "b28f6574b9447bba9ccaa5d2b0cfd79308acf0e3"}
+                         :sha     "45f6721bf2038c24eb9fe3afb422322ab3f473b5"}
   datascript-transit/datascript-transit {:mvn/version "0.3.0"
   datascript-transit/datascript-transit {:mvn/version "0.3.0"
                                          :exclusions [datascript/datascript]}
                                          :exclusions [datascript/datascript]}
   cljs-bean/cljs-bean         {:mvn/version "1.5.0"}
   cljs-bean/cljs-bean         {:mvn/version "1.5.0"}

+ 1 - 1
deps/db/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "version": "1.0.0",
   "private": true,
   "private": true,
   "devDependencies": {
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v26"
+    "@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v28"
   },
   },
   "dependencies": {
   "dependencies": {
     "better-sqlite3": "11.10.0"
     "better-sqlite3": "11.10.0"

+ 5 - 4
deps/db/src/logseq/db.cljs

@@ -136,8 +136,8 @@
              :as opts}]
              :as opts}]
   (if-let [children (sort-by-order
   (if-let [children (sort-by-order
                      (if include-property-block?
                      (if include-property-block?
-                       (:block/_raw-parent entity)
-                       (:block/_parent entity)))]
+                       (entity-plus/lookup-kv-then-entity entity :block/_raw-parent)
+                       (entity-plus/lookup-kv-then-entity entity :block/_parent)))]
     (cons entity (mapcat #(get-block-and-children-aux % opts) children))
     (cons entity (mapcat #(get-block-and-children-aux % opts) children))
     [entity]))
     [entity]))
 
 
@@ -218,7 +218,7 @@
 (defn page-exists?
 (defn page-exists?
   "Returns truthy value if page exists.
   "Returns truthy value if page exists.
    For db graphs, returns all page db ids that given title and one of the given `tags`.
    For db graphs, returns all page db ids that given title and one of the given `tags`.
-   For file graphs, returns page entity if it exists"
+   For file graphs, returns page db/id vector if it exists"
   [db page-name tags]
   [db page-name tags]
   (when page-name
   (when page-name
     (if (db-based-graph? db)
     (if (db-based-graph? db)
@@ -249,7 +249,8 @@
             db
             db
             (common-util/page-name-sanity-lc page-name)
             (common-util/page-name-sanity-lc page-name)
             tags'))))
             tags'))))
-      (d/entity db [:block/name (common-util/page-name-sanity-lc page-name)]))))
+      (when-let [id (:db/id (d/entity db [:block/name (common-util/page-name-sanity-lc page-name)]))]
+        [id]))))
 
 
 (defn get-page
 (defn get-page
   "Get a page given its unsanitized name or uuid"
   "Get a page given its unsanitized name or uuid"

+ 1 - 2
deps/db/src/logseq/db/common/delete_blocks.cljs

@@ -39,8 +39,7 @@
              tx (cond->
              tx (cond->
                  (mapcat
                  (mapcat
                   (fn [block]
                   (fn [block]
-                    [[:db/retract (:db/id ref) :block/refs (:db/id block)]
-                     [:db/retract (:db/id ref) :block/path-refs (:db/id block)]]) retracted-blocks)
+                    [[:db/retract (:db/id ref) :block/refs (:db/id block)]]) retracted-blocks)
                   replaced-title
                   replaced-title
                   (conj [:db/add id :block/title replaced-title]))]
                   (conj [:db/add id :block/title replaced-title]))]
          tx))
          tx))

+ 1 - 1
deps/db/src/logseq/db/common/entity_plus.cljc

@@ -33,7 +33,7 @@
   it means `(db/entity :block/title)` always return same result"
   it means `(db/entity :block/title)` always return same result"
   #{:block/link :block/updated-at :block/refs :block/closed-value-property
   #{:block/link :block/updated-at :block/refs :block/closed-value-property
     :block/created-at :block/collapsed? :block/tags :block/title
     :block/created-at :block/collapsed? :block/tags :block/title
-    :block/path-refs :block/parent :block/order :block/page
+    :block/parent :block/order :block/page
 
 
     :logseq.property/created-from-property
     :logseq.property/created-from-property
     :logseq.property/icon
     :logseq.property/icon

+ 10 - 4
deps/db/src/logseq/db/common/initial_data.cljs

@@ -141,8 +141,7 @@
             identity
             identity
             (fn [e]
             (fn [e]
               (keep (fn [[k v]]
               (keep (fn [[k v]]
-                      (when (and (not (contains? #{:block/path-refs} k))
-                                 (or (empty? properties) (properties k)))
+                      (when (or (empty? properties) (properties k))
                         (let [v' (cond
                         (let [v' (cond
                                    (= k :block/parent)
                                    (= k :block/parent)
                                    (:db/id v)
                                    (:db/id v)
@@ -216,7 +215,8 @@
                   nil))
                   nil))
         block-refs-count? (some #{:block.temp/refs-count} properties)]
         block-refs-count? (some #{:block.temp/refs-count} properties)]
     (when block
     (when block
-      ;; (prn :debug :get-block (:db/id block) (:block/title block) :children? children?)
+      ;; (prn :debug :get-block (:db/id block) (:block/title block) :children? children?
+      ;;      :include-collapsed-children? include-collapsed-children?)
       (let [children (when children?
       (let [children (when children?
                        (let [children-blocks (get-block-children db (:block/uuid block) {:include-collapsed-children? include-collapsed-children?})
                        (let [children-blocks (get-block-children db (:block/uuid block) {:include-collapsed-children? include-collapsed-children?})
                              large-page? (>= (count children-blocks) 100)
                              large-page? (>= (count children-blocks) 100)
@@ -243,7 +243,13 @@
                      block-refs-count?
                      block-refs-count?
                      (assoc :block.temp/refs-count (get-block-refs-count db (:db/id block)))
                      (assoc :block.temp/refs-count (get-block-refs-count db (:db/id block)))
                      true
                      true
-                     (assoc :block.temp/load-status (if (and children? (empty? properties)) :full :self)))]
+                     (assoc :block.temp/load-status (cond
+                                                      (and children? include-collapsed-children? (empty? properties))
+                                                      :full
+                                                      (and children? (empty? properties))
+                                                      :children
+                                                      :else
+                                                      :self)))]
         (cond->
         (cond->
          {:block block'}
          {:block block'}
           children?
           children?

+ 24 - 11
deps/db/src/logseq/db/common/reference.cljs

@@ -8,7 +8,8 @@
             [logseq.db :as ldb]
             [logseq.db :as ldb]
             [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.common.initial-data :as common-initial-data]
             [logseq.db.common.initial-data :as common-initial-data]
-            [logseq.db.frontend.class :as db-class]))
+            [logseq.db.frontend.class :as db-class]
+            [logseq.db.frontend.rules :as rules]))
 
 
 (defn get-filters
 (defn get-filters
   [db page]
   [db page]
@@ -36,32 +37,39 @@
                (log/error :syntax/filters e)))))))
                (log/error :syntax/filters e)))))))
 
 
 (defn- build-include-exclude-query
 (defn- build-include-exclude-query
-  [variable includes excludes]
+  [includes excludes]
   (concat
   (concat
    (for [include includes]
    (for [include includes]
-     [variable :block/path-refs include])
+     (list 'has-ref '?b include))
    (for [exclude excludes]
    (for [exclude excludes]
-     (list 'not [variable :block/path-refs exclude]))))
+     (list 'not (list 'has-ref '?b exclude)))))
 
 
 (defn- filter-refs-query
 (defn- filter-refs-query
-  [attribute includes excludes class-ids]
+  [includes excludes class-ids]
   (let [clauses (concat
   (let [clauses (concat
-                 (build-include-exclude-query '?b includes excludes)
+                 (build-include-exclude-query includes excludes)
                  (for [class-id class-ids]
                  (for [class-id class-ids]
                    (list 'not ['?b :block/tags class-id])))]
                    (list 'not ['?b :block/tags class-id])))]
     (into [:find '[?b ...]
     (into [:find '[?b ...]
-           :in '$ '[?id ...]
+           :in '$ '% '[?id ...]
            :where
            :where
-           ['?b attribute '?id]]
+           (list 'has-ref '?b '?id)]
           clauses)))
           clauses)))
 
 
+(defn- get-path-refs
+  [db entity]
+  (let [refs (mapcat :block/refs (ldb/get-block-parents db (:block/uuid entity)))
+        block-page (:block/page entity)]
+    (->> (cond->> refs (some? block-page) (cons block-page))
+         distinct)))
+
 (defn- get-ref-pages-count
 (defn- get-ref-pages-count
   [db id ref-blocks children-ids]
   [db id ref-blocks children-ids]
   (when (seq ref-blocks)
   (when (seq ref-blocks)
     (let [children (->> children-ids
     (let [children (->> children-ids
                         (map (fn [id] (d/entity db id))))]
                         (map (fn [id] (d/entity db id))))]
-      (->> (concat (mapcat :block/path-refs ref-blocks)
-                   (mapcat :block/refs children))
+      (->> (concat (mapcat #(get-path-refs db %) ref-blocks)
+                   (mapcat :block/refs (concat ref-blocks children)))
            frequencies
            frequencies
            (keep (fn [[ref size]]
            (keep (fn [[ref size]]
                    (when (and (ldb/page? ref)
                    (when (and (ldb/page? ref)
@@ -99,7 +107,12 @@
                       (set (conj class-children id))))
                       (set (conj class-children id))))
         full-ref-block-ids (->> (mapcat (fn [id] (map :db/id (:block/_refs (d/entity db id)))) ids)
         full-ref-block-ids (->> (mapcat (fn [id] (map :db/id (:block/_refs (d/entity db id)))) ids)
                                 set)
                                 set)
-        matched-ref-block-ids (set (d/q (filter-refs-query :block/path-refs includes excludes class-ids) db ids))
+        matched-ref-block-ids (set (d/q (filter-refs-query includes excludes class-ids)
+                                        db
+                                        (rules/extract-rules rules/db-query-dsl-rules
+                                                             [:has-ref]
+                                                             {:deps rules/rules-dependencies})
+                                        ids))
         matched-refs-with-children-ids (let [*result (atom #{})]
         matched-refs-with-children-ids (let [*result (atom #{})]
                                          (doseq [ref-id matched-ref-block-ids]
                                          (doseq [ref-id matched-ref-block-ids]
                                            (get-block-parents-until-top-ref db id ref-id full-ref-block-ids *result))
                                            (get-block-parents-until-top-ref db id ref-id full-ref-block-ids *result))

+ 8 - 2
deps/db/src/logseq/db/common/view.cljs

@@ -7,6 +7,7 @@
             [logseq.common.util :as common-util]
             [logseq.common.util :as common-util]
             [logseq.db :as ldb]
             [logseq.db :as ldb]
             [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.common.entity-plus :as entity-plus]
+            [logseq.db.common.initial-data :as common-initial-data]
             [logseq.db.common.reference :as db-reference]
             [logseq.db.common.reference :as db-reference]
             [logseq.db.frontend.class :as db-class]
             [logseq.db.frontend.class :as db-class]
             [logseq.db.frontend.entity-util :as entity-util]
             [logseq.db.frontend.entity-util :as entity-util]
@@ -301,7 +302,7 @@
                               (entity-util/built-in? e)))
                               (entity-util/built-in? e)))
                 (cond-> e
                 (cond-> e
                   refs-count?
                   refs-count?
-                  (assoc :block.temp/refs-count (count (:block/_refs e)))))))
+                  (assoc :block.temp/refs-count (common-initial-data/get-block-refs-count db (:e d)))))))
           (d/datoms db :avet property-ident))))
           (d/datoms db :avet property-ident))))
 
 
 (defn- get-entities
 (defn- get-entities
@@ -450,8 +451,13 @@
           filters (or (:logseq.property.table/filters view) filters)
           filters (or (:logseq.property.table/filters view) filters)
           feat-type (or view-feature-type (:logseq.property.view/feature-type view))
           feat-type (or view-feature-type (:logseq.property.view/feature-type view))
           query? (= feat-type :query-result)
           query? (= feat-type :query-result)
+          query-entity-ids (when (seq query-entity-ids) (set query-entity-ids))
           entities-result (if query?
           entities-result (if query?
-                            (keep #(d/entity db %) query-entity-ids)
+                            (keep (fn [id]
+                                    (let [e (d/entity db id)]
+                                      (when-not (= :logseq.property/query (:db/ident (:logseq.property/created-from-property e)))
+                                        e)))
+                                  query-entity-ids)
                             (get-view-entities db view-id opts))
                             (get-view-entities db view-id opts))
           entities (if (= feat-type :linked-references)
           entities (if (= feat-type :linked-references)
                      (:ref-blocks entities-result)
                      (:ref-blocks entities-result)

+ 3 - 9
deps/db/src/logseq/db/file_based/rules.cljc

@@ -14,13 +14,7 @@
   "Rules used by frontend.db.query-dsl for file graphs. The symbols ?b and ?p
   "Rules used by frontend.db.query-dsl for file graphs. The symbols ?b and ?p
   respectively refer to block and page. Do not alter them as they are
   respectively refer to block and page. Do not alter them as they are
   programmatically built by the query-dsl ns"
   programmatically built by the query-dsl ns"
-  {:block-parent
-   '[[(block-parent ?p ?c)
-      [?c :block/parent ?p]]
-     [(block-parent ?p ?c)
-      [?t :block/parent ?p]
-      (block-parent ?t ?c)]]
-   :page-property
+  {:page-property
    '[(page-property ?p ?key ?val)
    '[(page-property ?p ?key ?val)
      [?p :block/name]
      [?p :block/name]
      [?p :block/properties ?prop]
      [?p :block/properties ?prop]
@@ -95,5 +89,5 @@
 
 
    :page-ref
    :page-ref
    '[(page-ref ?b ?page-name)
    '[(page-ref ?b ?page-name)
-     [?b :block/path-refs ?br]
-     [?br :block/name ?page-name]]})
+     [?br :block/name ?page-name]
+     (has-ref ?b ?br)]})

+ 0 - 4
deps/db/src/logseq/db/file_based/schema.cljs

@@ -28,10 +28,6 @@
    ;; reference blocks
    ;; reference blocks
    :block/refs {:db/valueType :db.type/ref
    :block/refs {:db/valueType :db.type/ref
                 :db/cardinality :db.cardinality/many}
                 :db/cardinality :db.cardinality/many}
-   ;; referenced pages inherited from the parents
-   :block/path-refs {:db/valueType   :db.type/ref
-                     :db/cardinality :db.cardinality/many}
-
    :block/tags {:db/valueType :db.type/ref
    :block/tags {:db/valueType :db.type/ref
                 :db/cardinality :db.cardinality/many}
                 :db/cardinality :db.cardinality/many}
 
 

+ 3 - 3
deps/db/src/logseq/db/frontend/class.cljs

@@ -136,9 +136,9 @@
 
 
 (defn get-class-extends
 (defn get-class-extends
   "Returns all extends of a class"
   "Returns all extends of a class"
-  [node]
-  (assert (de/entity? node) "get-class-extends `node` should be an entity")
-  (loop [extends (:logseq.property.class/extends node)
+  [class]
+  (assert (de/entity? class) "get-class-extends `class` should be an entity")
+  (loop [extends (:logseq.property.class/extends class)
          result #{}]
          result #{}]
     (if (seq extends)
     (if (seq extends)
       (recur (set (mapcat :logseq.property.class/extends extends))
       (recur (set (mapcat :logseq.property.class/extends extends))

+ 8 - 9
deps/db/src/logseq/db/frontend/content.cljs

@@ -113,12 +113,12 @@
                  :or {replace-tag? true}}]
                  :or {replace-tag? true}}]
   (assert (string? title))
   (assert (string? title))
   (let [refs' (->> refs
   (let [refs' (->> refs
-                   (remove (fn [ref]
-                             ;; remove uuid references since they're introduced to detect multiple pages
-                             ;; that have the same name
-                             (and (map? ref)
-                                  (:block.temp/original-page-name ref)
-                                  (common-util/uuid-string? (:block.temp/original-page-name ref)))))
+                   (map (fn [ref]
+                          ;; remove uuid references since they're introduced to detect multiple pages
+                          ;; that have the same name
+                          (if (and (map? ref) (some-> (:block.temp/original-page-name ref) common-util/uuid-string?))
+                            (dissoc ref :block.temp/original-page-name)
+                            ref)))
                    (map
                    (map
                     (fn [ref]
                     (fn [ref]
                       (if (and (vector? ref) (= :block/uuid (first ref)))
                       (if (and (vector? ref) (= :block/uuid (first ref)))
@@ -127,9 +127,8 @@
                         ref)))
                         ref)))
                    sort-refs)]
                    sort-refs)]
     (reduce
     (reduce
-     (fn [content {uuid' :block/uuid :block/keys [title] :as block}]
-       (let [title' (or (:block.temp/original-page-name block) title)]
-         (replace-page-ref-with-id content title' uuid' replace-tag?)))
+     (fn [content {uuid' :block/uuid :block/keys [title]}]
+       (replace-page-ref-with-id content title uuid' replace-tag?))
      title
      title
      (filter :block/title refs'))))
      (filter :block/title refs'))))
 
 

+ 31 - 28
deps/db/src/logseq/db/frontend/kv_entity.cljs

@@ -2,35 +2,38 @@
   "kv entities used by logseq db"
   "kv entities used by logseq db"
   (:require [logseq.common.defkeywords :refer [defkeywords]]))
   (:require [logseq.common.defkeywords :refer [defkeywords]]))
 
 
-(defkeywords
-  :logseq.kv/db-type                      {:doc "Set to \"db\" if it's a db-graph"}
-  :logseq.kv/graph-uuid                   {:doc "Store graph-uuid if it's a rtc enabled graph"
-                                           :rtc {:rtc/ignore-entity-when-init-upload true
-                                                 :rtc/ignore-entity-when-init-download true}}
-  :logseq.kv/import-type                  {:doc "If graph is imported, identifies how a graph is imported including which UI or CLI import process. CLI scripts can set this to a custom value.
+(def kv-entities
+  (apply
+   hash-map
+   (defkeywords
+     :logseq.kv/db-type                      {:doc "Set to \"db\" if it's a db-graph"}
+     :logseq.kv/graph-uuid                   {:doc "Store graph-uuid if it's a rtc enabled graph"
+                                              :rtc {:rtc/ignore-entity-when-init-upload true
+                                                    :rtc/ignore-entity-when-init-download true}}
+     :logseq.kv/import-type                  {:doc "If graph is imported, identifies how a graph is imported including which UI or CLI import process. CLI scripts can set this to a custom value.
                                                  UI values include :file-graph and :sqlite-db and CLI values start with :cli e.g. :cli/default."}
                                                  UI values include :file-graph and :sqlite-db and CLI values start with :cli e.g. :cli/default."}
-  :logseq.kv/imported-at                  {:doc "Time if graph is imported"}
-  :logseq.kv/graph-local-tx               {:doc "local rtc tx-id"
-                                           :rtc {:rtc/ignore-entity-when-init-upload true
-                                                 :rtc/ignore-entity-when-init-download true}}
-  :logseq.kv/schema-version               {:doc "Graph's current schema version"}
-  :logseq.kv/remote-schema-version        {:doc "Graph's remote schema version.
+     :logseq.kv/imported-at                  {:doc "Time if graph is imported"}
+     :logseq.kv/graph-local-tx               {:doc "local rtc tx-id"
+                                              :rtc {:rtc/ignore-entity-when-init-upload true
+                                                    :rtc/ignore-entity-when-init-download true}}
+     :logseq.kv/schema-version               {:doc "Graph's current schema version"}
+     :logseq.kv/remote-schema-version        {:doc "Graph's remote schema version.
 RTC won't start when major-schema-versions don't match"
 RTC won't start when major-schema-versions don't match"
-                                           :rtc {:rtc/ignore-entity-when-init-upload true
-                                                 :rtc/ignore-entity-when-init-download true}}
-  :logseq.kv/graph-created-at             {:doc "Graph's created at time"}
-  :logseq.kv/latest-code-lang             {:doc "Latest lang used by a #Code-block"
-                                           :rtc {:rtc/ignore-entity-when-init-upload true
-                                                 :rtc/ignore-entity-when-init-download true}}
-  :logseq.kv/graph-backup-folder          {:doc "Backup folder for automated backup feature"
-                                           :rtc {:rtc/ignore-entity-when-init-upload true
-                                                 :rtc/ignore-entity-when-init-download true}}
-  :logseq.kv/graph-initial-schema-version {:doc "Graph's schema version when created"}
+                                              :rtc {:rtc/ignore-entity-when-init-upload true
+                                                    :rtc/ignore-entity-when-init-download true}}
+     :logseq.kv/graph-created-at             {:doc "Graph's created at time"}
+     :logseq.kv/latest-code-lang             {:doc "Latest lang used by a #Code-block"
+                                              :rtc {:rtc/ignore-entity-when-init-upload true
+                                                    :rtc/ignore-entity-when-init-download true}}
+     :logseq.kv/graph-backup-folder          {:doc "Backup folder for automated backup feature"
+                                              :rtc {:rtc/ignore-entity-when-init-upload true
+                                                    :rtc/ignore-entity-when-init-download true}}
+     :logseq.kv/graph-initial-schema-version {:doc "Graph's schema version when created"}
 
 
-  :logseq.kv/graph-last-gc-at             {:doc "Last time graph gc at"
-                                           :rtc {:rtc/ignore-entity-when-init-upload true
-                                                 :rtc/ignore-entity-when-init-download true}}
+     :logseq.kv/graph-last-gc-at             {:doc "Last time graph gc at"
+                                              :rtc {:rtc/ignore-entity-when-init-upload true
+                                                    :rtc/ignore-entity-when-init-download true}}
 
 
-  :logseq.kv/graph-text-embedding-model-name   {:doc "Graph's text-embedding model name"
-                                                :rtc {:rtc/ignore-entity-when-init-upload true
-                                                      :rtc/ignore-entity-when-init-download true}})
+     :logseq.kv/graph-text-embedding-model-name   {:doc "Graph's text-embedding model name"
+                                                   :rtc {:rtc/ignore-entity-when-init-upload true
+                                                         :rtc/ignore-entity-when-init-download true}})))

+ 4 - 6
deps/db/src/logseq/db/frontend/malli_schema.cljs

@@ -273,8 +273,7 @@
 (def page-attrs
 (def page-attrs
   "Common attributes for pages"
   "Common attributes for pages"
   [[:block/name :string]
   [[:block/name :string]
-   [:block/title :string]
-   [:block/path-refs {:optional true} [:set :int]]])
+   [:block/title :string]])
 
 
 (def property-attrs
 (def property-attrs
   "Common attributes for properties"
   "Common attributes for properties"
@@ -335,7 +334,8 @@
    (concat
    (concat
     [:map
     [:map
      [:db/ident user-property-ident]
      [:db/ident user-property-ident]
-     [:logseq.property/type (apply vector :enum db-property-type/user-built-in-property-types)]]
+     [:logseq.property/type (apply vector :enum (into db-property-type/user-allowed-internal-property-types
+                                                      db-property-type/user-built-in-property-types))]]
     property-common-schema-attrs
     property-common-schema-attrs
     property-attrs
     property-attrs
     page-attrs
     page-attrs
@@ -387,7 +387,6 @@
    [:block/order block-order]
    [:block/order block-order]
    ;; refs
    ;; refs
    [:block/page :int]
    [:block/page :int]
-   [:block/path-refs {:optional true} [:set :int]]
    [:block/link {:optional true} :int]
    [:block/link {:optional true} :int]
    [:logseq.property/created-from-property {:optional true} :int]])
    [:logseq.property/created-from-property {:optional true} :int]])
 
 
@@ -399,8 +398,7 @@
     [[:block/title :string]
     [[:block/title :string]
      [:block/parent :int]
      [:block/parent :int]
      ;; These blocks only associate with pages of type "whiteboard"
      ;; These blocks only associate with pages of type "whiteboard"
-     [:block/page :int]
-     [:block/path-refs {:optional true} [:set :int]]]
+     [:block/page :int]]
     page-or-block-attrs)))
     page-or-block-attrs)))
 
 
 (def property-value-block
 (def property-value-block

+ 12 - 20
deps/db/src/logseq/db/frontend/property.cljs

@@ -9,17 +9,14 @@
             [logseq.db.frontend.db-ident :as db-ident]
             [logseq.db.frontend.db-ident :as db-ident]
             [logseq.db.frontend.property.type :as db-property-type]))
             [logseq.db.frontend.property.type :as db-property-type]))
 
 
-;; Main property vars
-;; ==================
-
-;; Ignore this property when rtc,
-;; since users frequently click the sort button to view table content temporarily,
-;; but this action does not need to be synchronized with other clients.
-(def property-ignore-rtc
+(def ^:private property-ignore-rtc
   {:rtc/ignore-attr-when-init-upload true
   {:rtc/ignore-attr-when-init-upload true
    :rtc/ignore-attr-when-init-download true
    :rtc/ignore-attr-when-init-download true
    :rtc/ignore-attr-when-syncing true})
    :rtc/ignore-attr-when-syncing true})
 
 
+;; Main property vars
+;; ==================
+
 (def ^:large-vars/data-var built-in-properties
 (def ^:large-vars/data-var built-in-properties
   "Map of built in properties for db graphs with their :db/ident as keys.
   "Map of built in properties for db graphs with their :db/ident as keys.
    Each property has a config map with the following keys:
    Each property has a config map with the following keys:
@@ -109,12 +106,6 @@
                                      :cardinality :many
                                      :cardinality :many
                                      :public? false
                                      :public? false
                                      :hide? true}}
                                      :hide? true}}
-     :block/path-refs      {:title "Node path references"
-                            :attribute :block/path-refs
-                            :schema {:type :entity
-                                     :cardinality :many
-                                     :public? false
-                                     :hide? true}}
      :block/link           {:title "Node links to"
      :block/link           {:title "Node links to"
                             :attribute :block/link
                             :attribute :block/link
                             :schema {:type :entity
                             :schema {:type :entity
@@ -209,7 +200,7 @@
                                  {:logseq.property/description "Provides a way for a page to associate to another page i.e. backward compatible tagging."}}
                                  {:logseq.property/description "Provides a way for a page to associate to another page i.e. backward compatible tagging."}}
      :logseq.property/background-color {:title "Background color"
      :logseq.property/background-color {:title "Background color"
                                         :schema {:type :default :hide? true}}
                                         :schema {:type :default :hide? true}}
-   ;; number (1-6) or boolean for auto heading
+     ;; number (1-6) or boolean for auto heading
      :logseq.property/heading {:title "Heading"
      :logseq.property/heading {:title "Heading"
                                :schema {:type :any :hide? true}
                                :schema {:type :any :hide? true}
                                :queryable? true}
                                :queryable? true}
@@ -222,8 +213,8 @@
      :logseq.property/asset   {:title "Asset"
      :logseq.property/asset   {:title "Asset"
                                :schema {:type :entity
                                :schema {:type :entity
                                         :hide? true}}
                                         :hide? true}}
-   ;; used by pdf and whiteboard
-   ;; TODO: remove ls-type
+     ;; used by pdf and whiteboard
+     ;; TODO: remove ls-type
      :logseq.property/ls-type {:schema {:type :keyword
      :logseq.property/ls-type {:schema {:type :keyword
                                         :hide? true}}
                                         :hide? true}}
 
 
@@ -248,7 +239,7 @@
                                     :schema {:type :entity :hide? true}}
                                     :schema {:type :entity :hide? true}}
      :logseq.property.pdf/hl-value {:title "Annotation data"
      :logseq.property.pdf/hl-value {:title "Annotation data"
                                     :schema {:type :map :hide? true}}
                                     :schema {:type :map :hide? true}}
-   ;; FIXME: :logseq.property/order-list-type should updated to closed values
+     ;; FIXME: :logseq.property/order-list-type should updated to closed values
      :logseq.property/order-list-type {:title "List type"
      :logseq.property/order-list-type {:title "List type"
                                        :schema {:type :default
                                        :schema {:type :default
                                                 :hide? true}}
                                                 :hide? true}}
@@ -268,7 +259,7 @@
                                     :schema {:type :map
                                     :schema {:type :map
                                              :hide? true}}
                                              :hide? true}}
 
 
-   ;; Journal props
+     ;; Journal props
      :logseq.property.journal/title-format {:title "Title Format"
      :logseq.property.journal/title-format {:title "Title Format"
                                             :schema
                                             :schema
                                             {:type :string
                                             {:type :string
@@ -383,7 +374,7 @@
       :schema {:type :property
       :schema {:type :property
                :hide? true}}
                :hide? true}}
 
 
-;; TODO: Add more props :Assignee, :Estimate, :Cycle, :Project
+     ;; TODO: Add more props :Assignee, :Estimate, :Cycle, :Project
 
 
      :logseq.property/icon {:title "Icon"
      :logseq.property/icon {:title "Icon"
                             :schema {:type :map}}
                             :schema {:type :map}}
@@ -580,13 +571,14 @@
                                                        :schema {:type :datetime
                                                        :schema {:type :datetime
                                                                 :public? false
                                                                 :public? false
                                                                 :hide? true}
                                                                 :hide? true}
+                                                       :queryable? false
                                                        :rtc property-ignore-rtc})))
                                                        :rtc property-ignore-rtc})))
 
 
 (def db-attribute-properties
 (def db-attribute-properties
   "Internal properties that are also db schema attributes"
   "Internal properties that are also db schema attributes"
   #{:block/alias :block/tags :block/parent
   #{:block/alias :block/tags :block/parent
     :block/order :block/collapsed? :block/page
     :block/order :block/collapsed? :block/page
-    :block/refs :block/path-refs :block/link
+    :block/refs :block/link
     :block/title :block/closed-value-property :block/journal-day
     :block/title :block/closed-value-property :block/journal-day
     :block/created-at :block/updated-at})
     :block/created-at :block/updated-at})
 
 

+ 7 - 0
deps/db/src/logseq/db/frontend/property/type.cljs

@@ -20,6 +20,13 @@
   "Valid property types for users in order they appear in the UI"
   "Valid property types for users in order they appear in the UI"
   [:default :number :date :datetime :checkbox :url :node])
   [:default :number :date :datetime :checkbox :url :node])
 
 
+(def user-allowed-internal-property-types
+  "Internal property types that users are allowed to store. These aren't available in the UI
+   so these would normally be created via EDN or the API."
+  #{:map})
+
+(assert (set/subset? user-allowed-internal-property-types internal-built-in-property-types))
+
 (def closed-value-property-types
 (def closed-value-property-types
   "Valid property :type for closed values"
   "Valid property :type for closed values"
   #{:default :number :url})
   #{:default :number :url})

+ 16 - 2
deps/db/src/logseq/db/frontend/rules.cljc

@@ -30,7 +30,19 @@
       [?e2 :block/alias ?e3]]
       [?e2 :block/alias ?e3]]
      [(alias ?e3 ?e1)
      [(alias ?e3 ?e1)
       [?e1 :block/alias ?e2]
       [?e1 :block/alias ?e2]
-      [?e2 :block/alias ?e3]]]})
+      [?e2 :block/alias ?e3]]]
+
+   :self-ref
+   '[(self-ref ?b ?page-name)
+     [?br :block/name ?page-name]
+     [?b :block/refs ?br]]
+
+   :has-ref
+   '[[(has-ref ?b ?r)
+      [?b :block/refs ?r]]
+     [(has-ref ?b ?r)
+      (parent ?p ?b)
+      [?p :block/refs ?r]]]})
 
 
 ;; Rules writing advice
 ;; Rules writing advice
 ;; ====================
 ;; ====================
@@ -239,7 +251,9 @@
   "For db graphs, a map of rule names and the rules they depend on. If this map
   "For db graphs, a map of rule names and the rules they depend on. If this map
   becomes long or brittle, we could do scan rules for their deps with something
   becomes long or brittle, we could do scan rules for their deps with something
   like find-rules-in-where"
   like find-rules-in-where"
-  {:task #{:simple-query-property}
+  {:has-ref #{:parent}
+   :page-ref #{:has-ref}
+   :task #{:simple-query-property}
    :priority #{:simple-query-property}
    :priority #{:simple-query-property}
    :property-missing-value #{:object-has-class-property}
    :property-missing-value #{:object-has-class-property}
    :has-property-or-object-property #{:object-has-class-property}
    :has-property-or-object-property #{:object-has-class-property}

+ 1 - 1
deps/db/src/logseq/db/frontend/schema.cljs

@@ -37,7 +37,7 @@
          (map (juxt :major :minor)
          (map (juxt :major :minor)
               [(parse-schema-version x) (parse-schema-version y)])))
               [(parse-schema-version x) (parse-schema-version y)])))
 
 
-(def version (parse-schema-version "65.10"))
+(def version (parse-schema-version "65.11"))
 
 
 (defn major-version
 (defn major-version
   "Return a number.
   "Return a number.

+ 5 - 2
deps/db/test/logseq/db/sqlite/export_test.cljs

@@ -428,7 +428,8 @@
                       :user.property/node {:logseq.property/type :node
                       :user.property/node {:logseq.property/type :node
                                            :db/cardinality :db.cardinality/many
                                            :db/cardinality :db.cardinality/many
                                            :build/property-classes [:user.class/MyClass]}
                                            :build/property-classes [:user.class/MyClass]}
-                      :user.property/p1 {:logseq.property/type :default}}
+                      :user.property/p1 {:logseq.property/type :default}
+                      :user.property/map {:logseq.property/type :map}}
          :classes {:user.class/MyClass {:build/class-properties [:user.property/p1]}}
          :classes {:user.class/MyClass {:build/class-properties [:user.property/p1]}}
          :pages-and-blocks
          :pages-and-blocks
          [{:page {:block/title "page1"}
          [{:page {:block/title "page1"}
@@ -442,7 +443,9 @@
                      :build/properties {:user.property/node #{[:build/page {:block/title "page object"
                      :build/properties {:user.property/node #{[:build/page {:block/title "page object"
                                                                             :build/tags [:user.class/MyClass]}]
                                                                             :build/tags [:user.class/MyClass]}]
                                                               [:block/uuid block-object-uuid]
                                                               [:block/uuid block-object-uuid]
-                                                              :logseq.class/Task}}}]}
+                                                              :logseq.class/Task}}}
+                    {:block/title "map block"
+                     :build/properties {:user.property/map {:foo :bar :num 2}}}]}
           {:page {:block/title "Blocks"}
           {:page {:block/title "Blocks"}
            :blocks [{:block/title "myclass object"
            :blocks [{:block/title "myclass object"
                      :build/tags [:user.class/MyClass]
                      :build/tags [:user.class/MyClass]

+ 3 - 3
deps/db/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 # yarn lockfile v1
 
 
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v26":
-  version "1.2.173-feat-db-v25"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/b26d290944234b20762ff109e5328b87ea240692"
+"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v28":
+  version "1.2.173-feat-db-v28"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/c0410ff81a8b0d510705581cccd39788d862dc91"
   dependencies:
   dependencies:
     import-meta-resolve "^4.1.0"
     import-meta-resolve "^4.1.0"
 
 

+ 1 - 1
deps/graph-parser/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "version": "1.0.0",
   "private": true,
   "private": true,
   "devDependencies": {
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v26",
+    "@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v28",
     "better-sqlite3": "11.10.0"
     "better-sqlite3": "11.10.0"
   },
   },
   "dependencies": {
   "dependencies": {

+ 34 - 0
deps/graph-parser/src/logseq/graph_parser/exporter.cljs

@@ -800,6 +800,32 @@
         (pr-str (dissoc query-map :title :group-by-page? :collapsed?))
         (pr-str (dissoc query-map :title :group-by-page? :collapsed?))
         query-str))))
         query-str))))
 
 
+(declare extract-block-list ast->text)
+(defn- extract-block-list-item
+  [{:keys [content items number checkbox]}]
+  (let [content* (mapcat #(ast->text % {}) content)
+        number* (if number
+                  (str number ". ")
+                  "* ")
+        checkbox* (if (some? checkbox)
+                    (if (boolean checkbox)
+                      "[X]" "[ ]")
+                    "")
+        items* (extract-block-list items :in-list? true)]
+    (concat [number* checkbox* " "]
+            content*
+            ["\n"]
+            items*
+            (when (seq items*) ["\n"]))))
+
+(defn- extract-block-list
+  [l & {:keys [in-list?]}]
+  (vec (concat (when-not in-list? ["\n"])
+               (mapcat extract-block-list-item l)
+               (when (and (pos? (count l))
+                          (not in-list?))
+                 ["\n\n"]))))
+
 (defn- ast->text
 (defn- ast->text
   "Given an ast block, convert it to text for use as a block title. This is a
   "Given an ast block, convert it to text for use as a block title. This is a
   slimmer version of handler.export.text/export-blocks-as-markdown"
   slimmer version of handler.export.text/export-blocks-as-markdown"
@@ -841,6 +867,14 @@
               (:arguments (second node))
               (:arguments (second node))
               (and (vector? node) (= (first node) "Example"))
               (and (vector? node) (= (first node) "Example"))
               (second node)
               (second node)
+              (and (vector? node) (= (first node) "Latex_Fragment"))
+              (let [[type' content] (second node)
+                    wrapper (case type' "Inline" "$" "Displayed" "$$")]
+                [wrapper content wrapper])
+              (and (vector? node) (= (first node) "Displayed_Math"))
+              ["$$" (second node) "$$"]
+              (and (vector? node) (= (first node) "List"))
+              (extract-block-list (second node))
               :else
               :else
               (do
               (do
                 (log-fn :ast->text "Ignored ast node" :node node)
                 (log-fn :ast->text "Ignored ast node" :node node)

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

@@ -47,12 +47,9 @@
     (concat portal-refs shape-link-refs)))
     (concat portal-refs shape-link-refs)))
 
 
 (defn- with-whiteboard-block-refs
 (defn- with-whiteboard-block-refs
-  [shape page-id]
+  [shape]
   (let [refs (or (get-shape-refs shape) [])]
   (let [refs (or (get-shape-refs shape) [])]
-    (merge {:block/refs (if (seq refs) refs [])
-            :block/path-refs (if (seq refs)
-                               (conj refs page-id)
-                               [])})))
+    {:block/refs (if (seq refs) refs [])}))
 
 
 (defn- with-whiteboard-content
 (defn- with-whiteboard-content
   "Main purpose of this function is to populate contents when shapes are used as references in outliner."
   "Main purpose of this function is to populate contents when shapes are used as references in outliner."
@@ -72,7 +69,7 @@
     (merge (when shape?
     (merge (when shape?
              (merge
              (merge
               {:block/uuid (uuid (:id shape))}
               {:block/uuid (uuid (:id shape))}
-              (with-whiteboard-block-refs shape page-id)
+              (with-whiteboard-block-refs shape)
               (with-whiteboard-content shape)))
               (with-whiteboard-content shape)))
            (when (nil? (:block/parent block)) {:block/parent page-id})
            (when (nil? (:block/parent block)) {:block/parent page-id})
            (when (nil? (:block/format block)) {:block/format :markdown}) ;; TODO: read from config
            (when (nil? (:block/format block)) {:block/format :markdown}) ;; TODO: read from config

+ 5 - 16
deps/graph-parser/test/logseq/graph_parser/exporter_test.cljs

@@ -196,7 +196,7 @@
   ;; This graph will contain basic examples of different features to import
   ;; This graph will contain basic examples of different features to import
   (p/let [file-graph-dir "test/resources/exporter-test-graph"
   (p/let [file-graph-dir "test/resources/exporter-test-graph"
           conn (db-test/create-conn)
           conn (db-test/create-conn)
-          ;; Calculate refs and path-refs like frontend
+          ;; Calculate refs like frontend
           _ (db-pipeline/add-listener conn)
           _ (db-pipeline/add-listener conn)
           assets (atom [])
           assets (atom [])
           {:keys [import-state]} (import-file-graph-to-db file-graph-dir conn {:assets assets :convert-all-tags? true})]
           {:keys [import-state]} (import-file-graph-to-db file-graph-dir conn {:assets assets :convert-all-tags? true})]
@@ -214,7 +214,7 @@
       (is (= 4 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Task]] @conn))))
       (is (= 4 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Task]] @conn))))
       (is (= 4 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Query]] @conn))))
       (is (= 4 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Query]] @conn))))
       (is (= 2 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Card]] @conn))))
       (is (= 2 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Card]] @conn))))
-      (is (= 3 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Quote-block]] @conn))))
+      (is (= 4 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Quote-block]] @conn))))
       (is (= 2 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Pdf-annotation]] @conn))))
       (is (= 2 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Pdf-annotation]] @conn))))
 
 
       ;; Properties and tags aren't included in this count as they aren't a Page
       ;; Properties and tags aren't included in this count as they aren't a Page
@@ -587,16 +587,12 @@
       (is (= "multiline block\na 2nd\nand a 3rd" (:block/title (db-test/find-block-by-content @conn #"multiline block"))))
       (is (= "multiline block\na 2nd\nand a 3rd" (:block/title (db-test/find-block-by-content @conn #"multiline block"))))
       (is (= "logbook block" (:block/title (db-test/find-block-by-content @conn #"logbook block")))))
       (is (= "logbook block" (:block/title (db-test/find-block-by-content @conn #"logbook block")))))
 
 
-    (testing ":block/refs and :block/path-refs"
+    (testing ":block/refs"
       (let [page (db-test/find-page-by-title @conn "chat-gpt")]
       (let [page (db-test/find-page-by-title @conn "chat-gpt")]
         (is (set/subset?
         (is (set/subset?
              #{"type" "LargeLanguageModel"}
              #{"type" "LargeLanguageModel"}
              (->> page :block/refs (map #(:block/title (d/entity @conn (:db/id %)))) set))
              (->> page :block/refs (map #(:block/title (d/entity @conn (:db/id %)))) set))
-            "Page has correct property and property value :block/refs")
-        (is (set/subset?
-             #{"type" "LargeLanguageModel"}
-             (->> page :block/path-refs (map #(:block/title (d/entity @conn (:db/id %)))) set))
-            "Page has correct property and property value :block/path-refs"))
+            "Page has correct property and property value :block/refs"))
 
 
       (let [block (db-test/find-block-by-content @conn "old todo block")]
       (let [block (db-test/find-block-by-content @conn "old todo block")]
         (is (set/subset?
         (is (set/subset?
@@ -605,14 +601,7 @@
                   :block/refs
                   :block/refs
                   (map #(:db/ident (d/entity @conn (:db/id %))))
                   (map #(:db/ident (d/entity @conn (:db/id %))))
                   set))
                   set))
-            "Block has correct task tag and property :block/refs")
-        (is (set/subset?
-             #{:logseq.property/status :logseq.class/Task}
-             (->> block
-                  :block/path-refs
-                  (map #(:db/ident (d/entity @conn (:db/id %))))
-                  set))
-            "Block has correct task tag and property :block/path-refs")))
+            "Block has correct task tag and property :block/refs")))
 
 
     (testing "whiteboards"
     (testing "whiteboards"
       (let [block-with-props (db-test/find-block-by-content @conn #"block with props")]
       (let [block-with-props (db-test/find-block-by-content @conn #"block with props")]

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików