浏览代码

Merge pull request #1191 from logseq/feat/outliner-core

Core outliner refactor
Tienson Qin 4 年之前
父节点
当前提交
a0a423ceef
共有 100 个文件被更改,包括 3744 次插入4974 次删除
  1. 2 1
      .clj-kondo/config.edn
  2. 9 3
      .github/workflows/build-desktop-release.yml
  3. 1 1
      README.md
  4. 9 7
      deps.edn
  5. 2 0
      externs.js
  6. 6 5
      package.json
  7. 2 2
      resources/css/common.css
  8. 0 8
      resources/css/tooltip.css
  9. 0 1
      resources/electron.html
  10. 7 16
      resources/forge.config.js
  11. 二进制
      resources/icons/canary/logseq.ico
  12. 二进制
      resources/icons/canary/logseq.png
  13. 二进制
      resources/icons/canary/logseq_big_sur.icns
  14. 二进制
      resources/icons/canary/logseq_big_sur.ico
  15. 二进制
      resources/icons/canary/logseq_big_sur.png
  16. 8 2
      resources/js/preload.js
  17. 3 3
      resources/package.json
  18. 9 9
      resources/yarn.lock
  19. 2 1
      shadow-cljs.edn
  20. 58 19
      src/electron/electron/core.cljs
  21. 27 4
      src/electron/electron/handler.cljs
  22. 65 29
      src/electron/electron/search.cljs
  23. 84 46
      src/main/frontend/commands.cljs
  24. 325 262
      src/main/frontend/components/block.cljs
  25. 10 15
      src/main/frontend/components/block.css
  26. 14 0
      src/main/frontend/components/commit.cljs
  27. 12 74
      src/main/frontend/components/content.cljs
  28. 78 71
      src/main/frontend/components/diff.cljs
  29. 60 53
      src/main/frontend/components/editor.cljs
  30. 1 0
      src/main/frontend/components/editor.css
  31. 12 3
      src/main/frontend/components/export.cljs
  32. 2 6
      src/main/frontend/components/file.cljs
  33. 4 18
      src/main/frontend/components/header.cljs
  34. 9 8
      src/main/frontend/components/journal.cljs
  35. 2 2
      src/main/frontend/components/lazy_editor.cljs
  36. 16 68
      src/main/frontend/components/onboarding.cljs
  37. 221 253
      src/main/frontend/components/page.cljs
  38. 14 0
      src/main/frontend/components/page.css
  39. 0 51
      src/main/frontend/components/project.cljs
  40. 0 136
      src/main/frontend/components/publishing.cljs
  41. 0 49
      src/main/frontend/components/publishing.css
  42. 83 71
      src/main/frontend/components/reference.cljs
  43. 8 7
      src/main/frontend/components/right_sidebar.cljs
  44. 17 24
      src/main/frontend/components/search.cljs
  45. 40 0
      src/main/frontend/components/settings.cljs
  46. 65 0
      src/main/frontend/components/shortcut.cljs
  47. 14 33
      src/main/frontend/components/sidebar.cljs
  48. 13 5
      src/main/frontend/config.cljs
  49. 12 9
      src/main/frontend/context/i18n.cljs
  50. 2 1
      src/main/frontend/core.cljs
  51. 24 3
      src/main/frontend/date.cljs
  52. 35 52
      src/main/frontend/db.cljs
  53. 2 21
      src/main/frontend/db/conn.cljs
  54. 1 34
      src/main/frontend/db/debug.cljs
  55. 3 3
      src/main/frontend/db/default.cljs
  56. 269 362
      src/main/frontend/db/model.cljs
  57. 61 0
      src/main/frontend/db/outliner.cljs
  58. 18 17
      src/main/frontend/db/query_dsl.cljs
  59. 40 29
      src/main/frontend/db/query_react.cljs
  60. 70 61
      src/main/frontend/db/react.cljs
  61. 9 2
      src/main/frontend/db/utils.cljs
  62. 107 66
      src/main/frontend/db_schema.cljs
  63. 7 0
      src/main/frontend/debug.cljs
  64. 26 233
      src/main/frontend/dicts.cljs
  65. 50 58
      src/main/frontend/extensions/code.cljs
  66. 4 0
      src/main/frontend/extensions/excalidraw.cljs
  67. 11 4
      src/main/frontend/extensions/zip.cljs
  68. 1 1
      src/main/frontend/external/roam.cljc
  69. 3 7
      src/main/frontend/format.cljs
  70. 276 191
      src/main/frontend/format/block.cljs
  71. 36 20
      src/main/frontend/format/mldoc.cljs
  72. 1 41
      src/main/frontend/format/mldoc_test.cljs
  73. 13 15
      src/main/frontend/fs/nfs.cljs
  74. 12 11
      src/main/frontend/fs/node.cljs
  75. 5 15
      src/main/frontend/fs/watcher_handler.cljs
  76. 1 1
      src/main/frontend/graph.cljs
  77. 23 19
      src/main/frontend/handler.cljs
  78. 72 175
      src/main/frontend/handler/block.cljs
  79. 11 4
      src/main/frontend/handler/common.cljs
  80. 5 24
      src/main/frontend/handler/config.cljs
  81. 42 414
      src/main/frontend/handler/dnd.cljs
  82. 4 10
      src/main/frontend/handler/draw.cljs
  83. 382 628
      src/main/frontend/handler/editor.cljs
  84. 2 2
      src/main/frontend/handler/editor/keyboards.cljs
  85. 5 33
      src/main/frontend/handler/editor/lifecycle.cljs
  86. 80 0
      src/main/frontend/handler/events.cljs
  87. 0 102
      src/main/frontend/handler/expand.cljs
  88. 160 66
      src/main/frontend/handler/export.cljs
  89. 6 7
      src/main/frontend/handler/external.cljs
  90. 160 116
      src/main/frontend/handler/extract.cljs
  91. 41 52
      src/main/frontend/handler/file.cljs
  92. 1 18
      src/main/frontend/handler/git.cljs
  93. 17 12
      src/main/frontend/handler/graph.cljs
  94. 24 47
      src/main/frontend/handler/history.cljs
  95. 31 2
      src/main/frontend/handler/metadata.cljs
  96. 185 330
      src/main/frontend/handler/page.cljs
  97. 0 112
      src/main/frontend/handler/project.cljs
  98. 63 162
      src/main/frontend/handler/repo.cljs
  99. 3 3
      src/main/frontend/handler/route.cljs
  100. 14 13
      src/main/frontend/handler/search.cljs

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

@@ -6,4 +6,5 @@
            garden.def/defkeyframes clojure.core/def
            garden.def/defkeyframes clojure.core/def
            rum.core/defcc rum.core/defc
            rum.core/defcc rum.core/defc
            clojure.test.check.clojure-test/defspec clojure.core/def
            clojure.test.check.clojure-test/defspec clojure.core/def
-           clojure.test.check.properties/for-all clojure.core/for}}
+           clojure.test.check.properties/for-all clojure.core/for
+           frontend.modules.outliner.datascript/auto-transact! clojure.core/let}}

+ 9 - 3
.github/workflows/build-desktop-release.yml

@@ -111,10 +111,10 @@ jobs:
         working-directory: ./static
         working-directory: ./static
 
 
       - name: Change Artifact Name For ZIP File
       - name: Change Artifact Name For ZIP File
-        run: mv static/out/make/zip/linux/x64/Logseq-linux-x64-*.zip  static/out/make/zip/linux/x64/Logseq-linux.zip
+        run: mv static/out/make/zip/linux/x64/*-linux-x64-*.zip  static/out/make/zip/linux/x64/Logseq-linux.zip
 
 
       - name: Change Artifact Name For AppImage File
       - name: Change Artifact Name For AppImage File
-        run: mv static/out/make/Logseq-*.AppImage  static/out/make/Logseq-linux.AppImage
+        run: mv static/out/make/*-*.AppImage  static/out/make/Logseq-linux.AppImage
 
 
       - name: Cache Artifact With ZIP format
       - name: Cache Artifact With ZIP format
         uses: actions/upload-artifact@v1
         uses: actions/upload-artifact@v1
@@ -210,7 +210,7 @@ jobs:
         working-directory: ./static
         working-directory: ./static
 
 
       - name: Change DMG Name
       - name: Change DMG Name
-        run: mv static/out/make/Logseq.dmg static/out/make/logseq-darwin-x64-${{ github.event.inputs.tag-version }}.dmg
+        run: mv static/out/make/*.dmg static/out/make/logseq-darwin-x64-${{ github.event.inputs.tag-version }}.dmg
 
 
       - name: Cache Artifact DMG
       - name: Cache Artifact DMG
         uses: actions/upload-artifact@v1
         uses: actions/upload-artifact@v1
@@ -218,6 +218,12 @@ jobs:
           name: Logseq-x64.dmg
           name: Logseq-x64.dmg
           path: static/out/make/logseq-darwin-x64-${{ github.event.inputs.tag-version }}.dmg
           path: static/out/make/logseq-darwin-x64-${{ github.event.inputs.tag-version }}.dmg
 
 
+      - name: ls files
+        run: du -a static/out/
+
+      - name: Change zip Name
+        run: mv static/out/make/zip/darwin/x64/*.zip static/out/make/zip/darwin/x64/logseq-darwin-x64-${{ github.event.inputs.tag-version }}.zip
+
       - name: Cache Artifact ZIP
       - name: Cache Artifact ZIP
         uses: actions/upload-artifact@v1
         uses: actions/upload-artifact@v1
         with:
         with:

+ 1 - 1
README.md

@@ -15,7 +15,7 @@ Use it to organize your todo list, to write your journals, or to record your uni
 
 
 ## Why Logseq?
 ## Why Logseq?
 
 
-[Logseq](https://logseq.com) is a platform for knowledge sharing and management. It focuses on privacy, longevity, and user control.
+[Logseq](https://logseq.com) is a platform for knowledge management and collaboration. It focuses on privacy, longevity, and user control.
 Notice: the backend code will be open-sourced as soon as we’re sure that the backend service meets the security standards.
 Notice: the backend code will be open-sourced as soon as we’re sure that the backend service meets the security standards.
 
 
 The server will never store or analyze your private notes. Your data are plain text files and we currently support both Markdown and Emacs Org mode (more to be added soon).
 The server will never store or analyze your private notes. Your data are plain text files and we currently support both Markdown and Emacs Org mode (more to be added soon).

+ 9 - 7
deps.edn

@@ -1,4 +1,4 @@
-{:paths ["src/main"]
+{:paths ["src/main" "templates"]
  :deps
  :deps
  {org.clojure/clojure         {:mvn/version "1.10.0"}
  {org.clojure/clojure         {:mvn/version "1.10.0"}
   cheshire/cheshire {:mvn/version "5.10.0"}
   cheshire/cheshire {:mvn/version "5.10.0"}
@@ -31,20 +31,22 @@
   hiccups/hiccups             {:mvn/version "0.3.0"}
   hiccups/hiccups             {:mvn/version "0.3.0"}
   tongue/tongue               {:mvn/version "0.2.9"}
   tongue/tongue               {:mvn/version "0.2.9"}
   org.clojure/core.async      {:mvn/version "1.3.610"}
   org.clojure/core.async      {:mvn/version "1.3.610"}
-  thheller/shadow-cljs        {:mvn/version "2.11.14"}
+  thheller/shadow-cljs        {:mvn/version "2.12.5"}
   expound/expound             {:mvn/version "0.8.6"}
   expound/expound             {:mvn/version "0.8.6"}
-  lambdaisland/glogi          {:mvn/version "1.0.74"}
+  com.lambdaisland/glogi      {:mvn/version "1.0.116"}
   binaryage/devtools          {:mvn/version "1.0.2"}}
   binaryage/devtools          {:mvn/version "1.0.2"}}
 
 
  :aliases {:cljs {:extra-paths ["src/dev-cljs/" "src/test/" "src/electron/"]
  :aliases {:cljs {:extra-paths ["src/dev-cljs/" "src/test/" "src/electron/"]
-                  :extra-deps  {org.clojure/clojurescript   {:mvn/version "1.10.764"}
+                  :extra-deps  {org.clojure/clojurescript   {:mvn/version "1.10.844"}
                                 org.clojure/tools.namespace {:mvn/version "0.2.11"}
                                 org.clojure/tools.namespace {:mvn/version "0.2.11"}
-                                cider/cider-nrepl           {:mvn/version "0.25.5"}}
+                                cider/cider-nrepl           {:mvn/version "0.25.5"}
+                                org.clojars.knubie/cljs-run-test {:mvn/version "1.0.1"}}
                   :main-opts ["-m" "shadow.cljs.devtools.cli"]}
                   :main-opts ["-m" "shadow.cljs.devtools.cli"]}
            :test
            :test
            {:extra-paths ["src/test/"]
            {:extra-paths ["src/test/"]
-            :extra-deps  {org.clojure/clojurescript {:mvn/version "1.10.764"}
-                          org.clojure/test.check {:mvn/version "RELEASE"}}
+            :extra-deps  {org.clojure/clojurescript {:mvn/version "1.10.844"}
+                          org.clojure/test.check {:mvn/version "RELEASE"}
+                          org.clojars.knubie/cljs-run-test {:mvn/version "1.0.1"}}
             :main-opts   ["-m" "shadow.cljs.devtools.cli"]}
             :main-opts   ["-m" "shadow.cljs.devtools.cli"]}
 
 
            :test-clj
            :test-clj

+ 2 - 0
externs.js

@@ -63,6 +63,8 @@ dummy.run = function() {};
 dummy.all = function() {};
 dummy.all = function() {};
 dummy.transaction = function() {};
 dummy.transaction = function() {};
 dummy.getPath = function() {};
 dummy.getPath = function() {};
+dummy.getDoc = function() {};
+dummy.setValue = function() {};
 
 
 
 
 /**
 /**

+ 6 - 5
package.json

@@ -19,7 +19,7 @@
         "postcss-import-ext-glob": "^2.0.1",
         "postcss-import-ext-glob": "^2.0.1",
         "postcss-nested": "5.0.5",
         "postcss-nested": "5.0.5",
         "purgecss": "4.0.2",
         "purgecss": "4.0.2",
-        "shadow-cljs": "2.11.11",
+        "shadow-cljs": "^2.12.5",
         "stylelint": "^13.8.0",
         "stylelint": "^13.8.0",
         "stylelint-config-standard": "^20.0.0",
         "stylelint-config-standard": "^20.0.0",
         "tailwindcss": "2.0.3"
         "tailwindcss": "2.0.3"
@@ -60,6 +60,7 @@
     "dependencies": {
     "dependencies": {
         "@excalidraw/excalidraw": "^0.4.2",
         "@excalidraw/excalidraw": "^0.4.2",
         "@kanru/rage-wasm": "^0.2.1",
         "@kanru/rage-wasm": "^0.2.1",
+        "@tippyjs/react": "^4.2.5",
         "chokidar": "^3.5.1",
         "chokidar": "^3.5.1",
         "chrono-node": "^2.2.4",
         "chrono-node": "^2.2.4",
         "codemirror": "^5.58.1",
         "codemirror": "^5.58.1",
@@ -73,13 +74,13 @@
         "ignore": "^5.1.8",
         "ignore": "^5.1.8",
         "is-svg": "4.2.2",
         "is-svg": "4.2.2",
         "jszip": "^3.5.0",
         "jszip": "^3.5.0",
-        "mldoc": "0.6.9",
-        "mousetrap": "^1.6.5",
+        "mldoc": "0.6.16",
         "path": "^0.12.7",
         "path": "^0.12.7",
-        "react": "^17.0.1",
-        "react-dom": "^17.0.1",
+        "react": "^17.0.2",
+        "react-dom": "^17.0.2",
         "react-resize-context": "^3.0.0",
         "react-resize-context": "^3.0.0",
         "react-textarea-autosize": "^8.0.1",
         "react-textarea-autosize": "^8.0.1",
+        "react-tippy": "^1.4.0",
         "react-transition-group": "^4.3.0",
         "react-transition-group": "^4.3.0",
         "url": "^0.11.0",
         "url": "^0.11.0",
         "yargs-parser": "^20.2.4"
         "yargs-parser": "^20.2.4"

+ 2 - 2
resources/css/common.css

@@ -34,7 +34,7 @@ html[data-theme='dark'] {
   --ls-table-tr-even-background-color: #03333f;
   --ls-table-tr-even-background-color: #03333f;
   --ls-active-primary-color: #8ec2c2;
   --ls-active-primary-color: #8ec2c2;
   --ls-active-secondary-color: #d0e8e8;
   --ls-active-secondary-color: #d0e8e8;
-  --ls-block-properties-background-color: #02222a;
+  --ls-block-properties-background-color: #06323e;
   --ls-block-ref-link-text-color: #1a6376;
   --ls-block-ref-link-text-color: #1a6376;
   --ls-search-background-color: linear-gradient(
   --ls-search-background-color: linear-gradient(
     to right,
     to right,
@@ -396,7 +396,7 @@ li p:first-child,
 }
 }
 
 
 li p:last-child,
 li p:last-child,
-.block-body p:last-child {
+.block-body p:last-child, .block-body ul:last-child, .block-body ol:last-child, .block-body dl:last-child {
   margin-bottom: 0;
   margin-bottom: 0;
 }
 }
 
 

文件差异内容过多而无法显示
+ 0 - 8
resources/css/tooltip.css


+ 0 - 1
resources/electron.html

@@ -26,7 +26,6 @@
   <title>Logseq: A local-first knowledge base</title>
   <title>Logseq: A local-first knowledge base</title>
   <meta content="logseq" property="og:site_name">
   <meta content="logseq" property="og:site_name">
   <meta content="A local-first knowledge base which can be synced using Git." name="description">
   <meta content="A local-first knowledge base which can be synced using Git." name="description">
-  <script crossorigin="anonymous" defer onload="Sentry.init({dsn: 'https://[email protected]/5311485'});" src="https://asset.logseq.com/static/js/sentry.min.js">
   </script>
   </script>
 </head>
 </head>
 <body>
 <body>

+ 7 - 16
resources/forge.config.js

@@ -2,33 +2,24 @@ const path = require('path')
 
 
 module.exports = {
 module.exports = {
   packagerConfig: {
   packagerConfig: {
-    icon: './icons/logseq_big_sur.icns',
-    osxSign: {
-      identity: 'Developer ID Application: Tiansheng Qin',
-      'hardened-runtime': true,
-      entitlements: 'entitlements.plist',
-      'entitlements-inherit': 'entitlements.plist',
-      'signature-flags': 'library'
-    },
-    osxNotarize: {
-      appleId: "my-fake-apple-id",
-      appleIdPassword: "my-fake-apple-id-password",
-    },
+    icon: './icons/canary/logseq_big_sur.icns',
+    name: 'Logseq Canary',
   },
   },
   makers: [
   makers: [
     {
     {
       'name': '@electron-forge/maker-squirrel',
       'name': '@electron-forge/maker-squirrel',
       'config': {
       'config': {
-        'name': 'Logseq',
-        'setupIcon': './icons/logseq.ico'
+        title: 'Logseq Canary',
+        name: 'LogseqCanary', // ID name
+        setupIcon: './icons/canary/logseq.ico'
       }
       }
     },
     },
     {
     {
       name: '@electron-forge/maker-dmg',
       name: '@electron-forge/maker-dmg',
       config: {
       config: {
         format: 'ULFO',
         format: 'ULFO',
-        icon: './icons/logseq_big_sur.icns',
-        name: 'Logseq'
+        icon: './icons/canary/logseq_big_sur.icns',
+        name: 'Logseq Canary'
       }
       }
     },
     },
     {
     {

二进制
resources/icons/canary/logseq.ico


二进制
resources/icons/canary/logseq.png


二进制
resources/icons/canary/logseq_big_sur.icns


二进制
resources/icons/canary/logseq_big_sur.ico


二进制
resources/icons/canary/logseq_big_sur.png


+ 8 - 2
resources/js/preload.js

@@ -67,8 +67,14 @@ contextBridge.exposeInMainWorld('apis', {
    *
    *
    * @param {string} html html file with embedded state
    * @param {string} html html file with embedded state
    */
    */
-  exportPublishAssets(html, customCSSPath) {
-    ipcRenderer.invoke('export-publish-assets', html, customCSSPath)
+  exportPublishAssets(html, customCSSPath, repoPath, assetFilenames) {
+    ipcRenderer.invoke(
+      'export-publish-assets',
+      html,
+      customCSSPath,
+      repoPath,
+      assetFilenames
+    )
   },
   },
 
 
   /**
   /**

+ 3 - 3
resources/package.json

@@ -1,9 +1,9 @@
 {
 {
-  "name": "Logseq",
+  "name": "Logseq-Canary",
   "version": "0.0.1",
   "version": "0.0.1",
   "main": "electron.js",
   "main": "electron.js",
   "author": "Logseq",
   "author": "Logseq",
-  "description": "A privacy-first, open-source platform for knowledge sharing and management.",
+  "description": "A privacy-first, open-source platform for knowledge management and collaboration.",
   "repository": "https://github.com/logseq/logseq",
   "repository": "https://github.com/logseq/logseq",
   "scripts": {
   "scripts": {
     "electron:dev": "electron-forge start",
     "electron:dev": "electron-forge start",
@@ -34,7 +34,7 @@
     "@electron-forge/maker-rpm": "^6.0.0-beta.54",
     "@electron-forge/maker-rpm": "^6.0.0-beta.54",
     "@electron-forge/maker-squirrel": "^6.0.0-beta.54",
     "@electron-forge/maker-squirrel": "^6.0.0-beta.54",
     "@electron-forge/maker-zip": "^6.0.0-beta.54",
     "@electron-forge/maker-zip": "^6.0.0-beta.54",
-    "electron": "11.2.0",
+    "electron": "12.0.4",
     "electron-builder": "^22.10.5",
     "electron-builder": "^22.10.5",
     "electron-forge-maker-appimage": "trusktr/electron-forge-maker-appimage#patch-1"
     "electron-forge-maker-appimage": "trusktr/electron-forge-maker-appimage#patch-1"
   }
   }

+ 9 - 9
resources/yarn.lock

@@ -392,10 +392,10 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e"
   integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==
   integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==
 
 
-"@types/node@^12.0.12":
-  version "12.20.7"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.7.tgz#1cb61fd0c85cb87e728c43107b5fd82b69bc9ef8"
-  integrity sha512-gWL8VUkg8VRaCAUgG9WmhefMqHmMblxe2rVpMF86nZY/+ZysU+BkAp+3cz03AixWDSSz0ks5WX59yAhv/cDwFA==
+"@types/node@^14.6.2":
+  version "14.14.41"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.41.tgz#d0b939d94c1d7bd53d04824af45f1139b8c45615"
+  integrity sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g==
 
 
 "@types/plist@^3.0.1":
 "@types/plist@^3.0.1":
   version "3.0.2"
   version "3.0.2"
@@ -1683,13 +1683,13 @@ electron-winstaller@^4.0.1:
     lodash.template "^4.2.2"
     lodash.template "^4.2.2"
     temp "^0.9.0"
     temp "^0.9.0"
 
 
-electron@11.2.0:
-  version "11.2.0"
-  resolved "https://registry.yarnpkg.com/electron/-/electron-11.2.0.tgz#f8577ea4c9ba94068850256145be26b0b89a5dd7"
-  integrity sha512-weszOPAJPoPu6ozL7vR9enXmaDSqH+KE9iZODfbGdnFgtVfVdfyedjlvEGIUJkLMPXM1y/QWwCl2dINzr0Jq5Q==
[email protected].4:
+  version "12.0.4"
+  resolved "https://registry.yarnpkg.com/electron/-/electron-12.0.4.tgz#c2ca4710d0e4da7db6d31c4f55777b08bfcb08e5"
+  integrity sha512-A8Lq3YMZ1CaO1z5z5nsyFxIwkgwXLHUwL2pf9MVUHpq7fv3XUewCMD98EnLL3DdtiyCvw5KMkeT1WGsZh8qFug==
   dependencies:
   dependencies:
     "@electron/get" "^1.0.1"
     "@electron/get" "^1.0.1"
-    "@types/node" "^12.0.12"
+    "@types/node" "^14.6.2"
     extract-zip "^1.0.3"
     extract-zip "^1.0.3"
 
 
 emoji-regex@^7.0.1:
 emoji-regex@^7.0.1:

+ 2 - 1
shadow-cljs.edn

@@ -37,7 +37,8 @@
     :http-root    "public"
     :http-root    "public"
     :http-port    3001
     :http-port    3001
     :watch-path   "static"
     :watch-path   "static"
-    :preloads     [devtools.preload]}}
+    :preloads     [devtools.preload
+                   shadow.remote.runtime.cljs.browser]}}
 
 
   :electron {:target :node-script
   :electron {:target :node-script
              :output-to "static/electron.js"
              :output-to "static/electron.js"

+ 58 - 19
src/electron/electron/core.cljs

@@ -7,7 +7,7 @@
             [promesa.core :as p]
             [promesa.core :as p]
             ["fs-extra" :as fs]
             ["fs-extra" :as fs]
             ["path" :as path]
             ["path" :as path]
-            ["electron" :refer [BrowserWindow app protocol ipcMain dialog] :as electron]
+            ["electron" :refer [BrowserWindow app protocol ipcMain dialog Menu MenuItem] :as electron]
             ["electron-window-state" :as windowStateKeeper]
             ["electron-window-state" :as windowStateKeeper]
             [clojure.core.async :as async]
             [clojure.core.async :as async]
             [electron.state :as state]))
             [electron.state :as state]))
@@ -26,18 +26,18 @@
   []
   []
   (let [win-state (windowStateKeeper (clj->js {:defaultWidth 980 :defaultHeight 700}))
   (let [win-state (windowStateKeeper (clj->js {:defaultWidth 980 :defaultHeight 700}))
         win-opts (cond->
         win-opts (cond->
-                   {:width         (.-width win-state)
-                    :height        (.-height win-state)
-                    :frame         (not mac?)
-                    :autoHideMenuBar (not mac?)
-                    :titleBarStyle (if mac? "hidden" nil)
-                    :webPreferences
-                    {:plugins                 true ; pdf
-                     :nodeIntegration         false
-                     :nodeIntegrationInWorker false
-                     :contextIsolation        true
-                     :spellcheck              true
-                     :preload                 (path/join js/__dirname "js/preload.js")}}
+                  {:width         (.-width win-state)
+                   :height        (.-height win-state)
+                   :frame         (not mac?)
+                   :autoHideMenuBar (not mac?)
+                   :titleBarStyle (if mac? "hidden" nil)
+                   :webPreferences
+                   {:plugins                 true ; pdf
+                    :nodeIntegration         false
+                    :nodeIntegrationInWorker false
+                    :contextIsolation        true
+                    :spellcheck              true
+                    :preload                 (path/join js/__dirname "js/preload.js")}}
                    linux?
                    linux?
                    (assoc :icon (path/join js/__dirname "icons/logseq.png")))
                    (assoc :icon (path/join js/__dirname "icons/logseq.png")))
         url MAIN_WINDOW_ENTRY
         url MAIN_WINDOW_ENTRY
@@ -49,10 +49,11 @@
 
 
 (defn setup-updater! [^js win]
 (defn setup-updater! [^js win]
   ;; manual/auto updater
   ;; manual/auto updater
-  (when-not linux?
-    (init-updater {:repo   "logseq/logseq"
-                   :logger logger
-                   :win    win})))
+  ;;(when-not linux?
+  ;;  (init-updater {:repo   "logseq/logseq"
+  ;;                 :logger logger
+  ;;                 :win    win}))
+  )
 
 
 (defn setup-interceptor! []
 (defn setup-interceptor! []
   (.registerFileProtocol
   (.registerFileProtocol
@@ -64,19 +65,29 @@
        (callback #js {:path path}))))
        (callback #js {:path path}))))
   #(.unregisterProtocol protocol "assets"))
   #(.unregisterProtocol protocol "assets"))
 
 
-(defn- handle-export-publish-assets [_event html custom-css-path]
+(defn- handle-export-publish-assets [_event html custom-css-path repo-path asset-filenames]
   (let [app-path (. app getAppPath)
   (let [app-path (. app getAppPath)
+        asset-filenames (js->clj asset-filenames)
         paths (js->clj (. dialog showOpenDialogSync (clj->js {:properties ["openDirectory" "createDirectory" "promptToCreate", "multiSelections"]})))]
         paths (js->clj (. dialog showOpenDialogSync (clj->js {:properties ["openDirectory" "createDirectory" "promptToCreate", "multiSelections"]})))]
     (when (seq paths)
     (when (seq paths)
       (let [root-dir (first paths)
       (let [root-dir (first paths)
             static-dir (path/join root-dir "static")
             static-dir (path/join root-dir "static")
+            assets-from-dir (path/join repo-path "assets")
+            assets-to-dir (path/join root-dir "assets")
             index-html-path (path/join root-dir "index.html")]
             index-html-path (path/join root-dir "index.html")]
         (p/let [_ (. fs ensureDir static-dir)
         (p/let [_ (. fs ensureDir static-dir)
+                _ (. fs ensureDir assets-to-dir)
                 _ (p/all  (concat
                 _ (p/all  (concat
                            [(. fs writeFile index-html-path html)
                            [(. fs writeFile index-html-path html)
 
 
+
                             (. fs copy (path/join app-path "404.html") (path/join root-dir "404.html"))]
                             (. fs copy (path/join app-path "404.html") (path/join root-dir "404.html"))]
 
 
+                           (map
+                            (fn [filename] (. fs copy (path/join assets-from-dir filename) (path/join assets-to-dir filename)))
+
+                            asset-filenames)
+
                            (map
                            (map
                             (fn [part]
                             (fn [part]
                               (. fs copy (path/join app-path part) (path/join static-dir part)))
                               (. fs copy (path/join app-path part) (path/join static-dir part)))
@@ -98,7 +109,7 @@
                 _ (p/all (map (fn [file]
                 _ (p/all (map (fn [file]
                                 (. fs removeSync (path/join static-dir "js" (str file ".map"))))
                                 (. fs removeSync (path/join static-dir "js" (str file ".map"))))
                               ["main.js" "code-editor.js" "excalidraw.js" "age-encryption.js"]))]
                               ["main.js" "code-editor.js" "excalidraw.js" "age-encryption.js"]))]
-          (. dialog showMessageBox (clj->js {:message (str "Export publish assets to " root-dir " successfully")})))))))
+          (. dialog showMessageBox (clj->js {:message (str "Export public pages and publish assets to " root-dir " successfully")})))))))
 
 
 (defn setup-app-manager!
 (defn setup-app-manager!
   [^js win]
   [^js win]
@@ -127,6 +138,30 @@
                    (catch js/Error e
                    (catch js/Error e
                      (js/console.error e))))))
                      (js/console.error e))))))
 
 
+
+    (.on web-contents "context-menu"
+         (fn
+           [_event params]
+           (let [menu (Menu.)
+                 suggestions (.-dictionarySuggestions ^js params)]
+
+             (doseq [suggestion suggestions]
+               (. menu append
+                  (MenuItem. (clj->js {:label
+                                       suggestion
+                                       :click
+                                       (fn [] (. web-contents replaceMisspelling suggestion))}))))
+
+             (when-let [misspelled-word (.-misspelledWord ^js params)]
+               (. menu append
+                  (MenuItem. (clj->js {:label
+                                       "Add to dictionary"
+                                       :click
+                                       (fn [] (.. web-contents -session (addWordToSpellCheckerDictionary misspelled-word)))}))))
+
+             (. menu popup))))
+
+
     (.on web-contents  "new-window"
     (.on web-contents  "new-window"
          (fn [e url]
          (fn [e url]
            (let [url (if (string/starts-with? url "file:")
            (let [url (if (string/starts-with? url "file:")
@@ -173,7 +208,11 @@
                    *quitting? (atom false)]
                    *quitting? (atom false)]
                (.. logger (info (str "Logseq App(" (.getVersion app) ") Starting... ")))
                (.. logger (info (str "Logseq App(" (.getVersion app) ") Starting... ")))
 
 
+               (when (search/version-changed?)
+                 (search/rm-search-dir!))
+
                (search/ensure-search-dir!)
                (search/ensure-search-dir!)
+
                (search/open-dbs!)
                (search/open-dbs!)
 
 
                (vreset! *setup-fn
                (vreset! *setup-fn

+ 27 - 4
src/electron/electron/handler.cljs

@@ -2,6 +2,7 @@
   (:require ["electron" :refer [ipcMain dialog app]]
   (:require ["electron" :refer [ipcMain dialog app]]
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
             ["fs" :as fs]
             ["fs" :as fs]
+            ["fs-extra" :as fs-extra]
             ["path" :as path]
             ["path" :as path]
             ["chokidar" :as watcher]
             ["chokidar" :as watcher]
             [promesa.core :as p]
             [promesa.core :as p]
@@ -62,8 +63,8 @@
   (or
   (or
    (some #(string/starts-with? path (str dir "/" %))
    (some #(string/starts-with? path (str dir "/" %))
          ["." "assets" "node_modules"])
          ["." "assets" "node_modules"])
-   (some #(string/ends-with? path (str dir "/" %))
-         [".swap" ".crswap" ".tmp"])))
+   (some #(string/ends-with? path %)
+         [".swap" ".crswap" ".tmp" ".DS_Store"])))
 
 
 (defonce allowed-formats
 (defonce allowed-formats
   #{:org :markdown :md :edn :json :css :excalidraw})
   #{:org :markdown :md :edn :json :css :excalidraw})
@@ -105,8 +106,8 @@
   (async/put! state/persistent-dbs-chan true )
   (async/put! state/persistent-dbs-chan true )
   true)
   true)
 
 
-(defmethod handle :search-blocks [window [_ repo q limit]]
-  (search/search-blocks repo q limit))
+(defmethod handle :search-blocks [window [_ repo q opts]]
+  (search/search-blocks repo q opts))
 
 
 (defmethod handle :rebuild-blocks-indice [window [_ repo data]]
 (defmethod handle :rebuild-blocks-indice [window [_ repo data]]
   (search/truncate-blocks-table! repo)
   (search/truncate-blocks-table! repo)
@@ -127,6 +128,21 @@
 (defmethod handle :remove-db [window [_ repo]]
 (defmethod handle :remove-db [window [_ repo]]
   (search/delete-db! repo))
   (search/delete-db! repo))
 
 
+(defn clear-cache!
+  []
+  (let [path (.getPath ^object app "userData")]
+    (doseq [dir ["search" "IndexedDB" "Local Storage" "databases" "cache"]]
+      (let [path (path/join path dir)]
+        (try
+          (fs-extra/removeSync path)
+          (catch js/Error e
+            (js/console.error e)))))))
+
+(defmethod handle :clearCache [_window _]
+  (search/close!)
+  (clear-cache!)
+  (search/ensure-search-dir!))
+
 (defn- get-file-ext
 (defn- get-file-ext
   [file]
   [file]
   (last (string/split file #"\.")))
   (last (string/split file #"\.")))
@@ -137,6 +153,7 @@
       (send file-watcher-chan
       (send file-watcher-chan
             (bean/->js {:type type :payload payload}))))
             (bean/->js {:type type :payload payload}))))
 
 
+(defonce polling-interval 5000)
 (defn watch-dir!
 (defn watch-dir!
   [win dir]
   [win dir]
   (when (fs/existsSync dir)
   (when (fs/existsSync dir)
@@ -144,8 +161,14 @@
                           (clj->js
                           (clj->js
                            {:ignored (partial ignored-path? dir)
                            {:ignored (partial ignored-path? dir)
                             :ignoreInitial true
                             :ignoreInitial true
+                            :ignorePermissionErrors true
+                            :interval polling-interval
+                            :binaryInterval polling-interval
                             :persistent true
                             :persistent true
+                            :disableGlobbing true
+
                             :awaitWriteFinish true}))]
                             :awaitWriteFinish true}))]
+      ;; TODO: batch sender
       (.on watcher "add"
       (.on watcher "add"
            (fn [path]
            (fn [path]
              (send-file-watcher! win "add"
              (send-file-watcher! win "add"

+ 65 - 29
src/electron/electron/search.cljs

@@ -6,6 +6,8 @@
             [electron.utils :refer [logger] :as utils]
             [electron.utils :refer [logger] :as utils]
             ["electron" :refer [app]]))
             ["electron" :refer [app]]))
 
 
+(defonce version "0.0.1")
+
 (def error (partial (.-error logger) "[Search]"))
 (def error (partial (.-error logger) "[Search]"))
 
 
 (defonce databases (atom nil))
 (defonce databases (atom nil))
@@ -40,15 +42,15 @@
     END;"
     END;"
                   "CREATE TRIGGER IF NOT EXISTS blocks_ai AFTER INSERT ON blocks
                   "CREATE TRIGGER IF NOT EXISTS blocks_ai AFTER INSERT ON blocks
     BEGIN
     BEGIN
-        INSERT INTO blocks_fts (rowid, uuid, content)
-        VALUES (new.id, new.uuid, new.content);
+        INSERT INTO blocks_fts (rowid, uuid, content, page)
+        VALUES (new.id, new.uuid, new.content, new.page);
     END;
     END;
 "
 "
                   "CREATE TRIGGER IF NOT EXISTS blocks_au AFTER UPDATE ON blocks
                   "CREATE TRIGGER IF NOT EXISTS blocks_au AFTER UPDATE ON blocks
     BEGIN
     BEGIN
         DELETE from blocks_fts where rowid = old.id;
         DELETE from blocks_fts where rowid = old.id;
-        INSERT INTO blocks_fts (rowid, uuid, content)
-        VALUES (new.id, new.uuid, new.content);
+        INSERT INTO blocks_fts (rowid, uuid, content, page)
+        VALUES (new.id, new.uuid, new.content, new.page);
     END;"
     END;"
                   ]]
                   ]]
     (doseq [trigger triggers]
     (doseq [trigger triggers]
@@ -60,12 +62,13 @@
   (let [stmt (prepare db "CREATE TABLE IF NOT EXISTS blocks (
   (let [stmt (prepare db "CREATE TABLE IF NOT EXISTS blocks (
                         id INTEGER PRIMARY KEY,
                         id INTEGER PRIMARY KEY,
                         uuid TEXT NOT NULL,
                         uuid TEXT NOT NULL,
-                        content TEXT NOT NULL)")]
+                        content TEXT NOT NULL,
+                        page INTEGER)")]
     (.run ^object stmt)))
     (.run ^object stmt)))
 
 
 (defn create-blocks-fts-table!
 (defn create-blocks-fts-table!
   [db]
   [db]
-  (let [stmt (prepare db "CREATE VIRTUAL TABLE IF NOT EXISTS blocks_fts USING fts5(uuid, content)")]
+  (let [stmt (prepare db "CREATE VIRTUAL TABLE IF NOT EXISTS blocks_fts USING fts5(uuid, content, page)")]
     (.run ^object stmt)))
     (.run ^object stmt)))
 
 
 (defn get-search-dir
 (defn get-search-dir
@@ -73,10 +76,36 @@
   (let [path (.getPath ^object app "userData")]
   (let [path (.getPath ^object app "userData")]
     (path/join path "search")))
     (path/join path "search")))
 
 
+(defonce search-version "search.version")
+
+(defn get-search-version
+  []
+  (let [path (.getPath ^object app "userData")
+        path (path/join path search-version)]
+    (when (fs/existsSync path)
+      (.toString (fs/readFileSync path)))))
+
+(defn write-search-version!
+  []
+  (let [path (.getPath ^object app "userData")
+        path (path/join path search-version)]
+    (fs/writeFileSync path version)))
+
+(defn version-changed?
+  []
+  (not= version (get-search-version)))
+
 (defn ensure-search-dir!
 (defn ensure-search-dir!
   []
   []
+  (write-search-version!)
   (fs/ensureDirSync (get-search-dir)))
   (fs/ensureDirSync (get-search-dir)))
 
 
+(defn rm-search-dir!
+  []
+  (let [search-dir (get-search-dir)]
+    (when (fs/existsSync search-dir)
+      (fs/removeSync search-dir))))
+
 (defn get-db-full-path
 (defn get-db-full-path
   [db-name]
   [db-name]
   (let [db-name (normalize-db-name db-name)
   (let [db-name (normalize-db-name db-name)
@@ -89,8 +118,7 @@
         db (sqlite3 db-full-path nil)
         db (sqlite3 db-full-path nil)
         _ (create-blocks-table! db)
         _ (create-blocks-table! db)
         _ (create-blocks-fts-table! db)
         _ (create-blocks-fts-table! db)
-        _ (add-triggers! db)
-        ]
+        _ (add-triggers! db)]
     (swap! databases assoc db-name db)))
     (swap! databases assoc db-name db)))
 
 
 (defn open-dbs!
 (defn open-dbs!
@@ -105,7 +133,7 @@
   [repo blocks]
   [repo blocks]
   (if-let [db (get-db repo)]
   (if-let [db (get-db repo)]
     ;; TODO: what if a CONFLICT on uuid
     ;; TODO: what if a CONFLICT on uuid
-    (let [insert (prepare db "INSERT INTO blocks (id, uuid, content) VALUES (@id, @uuid, @content) ON CONFLICT (id) DO UPDATE SET content = @content")
+    (let [insert (prepare db "INSERT INTO blocks (id, uuid, content, page) VALUES (@id, @uuid, @content, @page) ON CONFLICT (id) DO UPDATE SET content = @content")
           insert-many (.transaction ^object db
           insert-many (.transaction ^object db
                                     (fn [blocks]
                                     (fn [blocks]
                                       (doseq [block blocks]
                                       (doseq [block blocks]
@@ -132,7 +160,7 @@
 ;;       (js->clj (.all ^object stmt q) :keywordize-keys true))))
 ;;       (js->clj (.all ^object stmt q) :keywordize-keys true))))
 
 
 (defn search-blocks
 (defn search-blocks
-  [repo q limit]
+  [repo q {:keys [limit page]}]
   (when-let [database (get-db repo)]
   (when-let [database (get-db repo)]
     (when-not (string/blank? q)
     (when-not (string/blank? q)
       (let [match? (or
       (let [match? (or
@@ -142,23 +170,30 @@
                     (string/includes? q " | ")
                     (string/includes? q " | ")
                     ;; (string/includes? q " not ")
                     ;; (string/includes? q " not ")
                     )
                     )
-            q (if match?
-                (-> q
-                    (string/replace " and " " AND ")
-                    (string/replace " & " " AND ")
-                    (string/replace " or " " OR ")
-                    (string/replace " | " " OR ")
-                    (string/replace " not " " NOT "))
-                q)
-            limit (or limit 20)
-            [sql input] (if match?
-                          ["select rowid, uuid, content from blocks_fts where content match ? order by rank limit ?"
-                           q]
-                          (let [q (string/replace q #"\s+" "%")]
-                            ["select rowid, uuid, content from blocks_fts where content like ? limit ?"
-                             (str "%" q "%")]))
-            stmt (prepare database sql)]
-        (js->clj (.all ^object stmt input limit) :keywordize-keys true)))))
+            input  (if match?
+                         (-> q
+                             (string/replace " and " " AND ")
+                             (string/replace " & " " AND ")
+                             (string/replace " or " " OR ")
+                             (string/replace " | " " OR ")
+                             (string/replace " not " " NOT "))
+                         (str "%" (string/replace q #"\s+" "%") "%"))
+            limit  (or limit 20)
+            select "select rowid, uuid, content, page from blocks_fts where "
+            pg-sql (if page "page = ? and" "")
+            sql    (if match?
+                     (str select
+                          pg-sql
+                          " content match ? order by rank limit ?")
+                     (str select
+                          pg-sql
+                          " content like ? limit ?"))
+            stmt   (prepare database sql)]
+        (js->clj
+         (if page
+           (.all ^object stmt (int page) input limit)
+           (.all ^object stmt  input limit))
+          :keywordize-keys true)))))
 
 
 (defn truncate-blocks-table!
 (defn truncate-blocks-table!
   [repo]
   [repo]
@@ -174,9 +209,10 @@
   [repo]
   [repo]
   (when-let [database (get-db repo)]
   (when-let [database (get-db repo)]
     (.close database)
     (.close database)
-    (let [[_ db-full-path] (get-db-full-path repo)]
+    (let [[db-name db-full-path] (get-db-full-path repo)]
       (println "Delete search indice: " db-full-path)
       (println "Delete search indice: " db-full-path)
-      (fs/unlinkSync db-full-path))))
+      (fs/unlinkSync db-full-path)
+      (swap! databases dissoc db-name))))
 
 
 (defn query
 (defn query
   [repo sql]
   [repo sql]

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

@@ -1,9 +1,12 @@
 (ns frontend.commands
 (ns frontend.commands
   (:require [frontend.util :as util]
   (:require [frontend.util :as util]
+            [frontend.util.marker :as marker]
+            [frontend.util.priority :as priority]
             [frontend.date :as date]
             [frontend.date :as date]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.search :as search]
             [frontend.search :as search]
             [frontend.config :as config]
             [frontend.config :as config]
+            [frontend.db :as db]
             [clojure.string :as string]
             [clojure.string :as string]
             [goog.dom :as gdom]
             [goog.dom :as gdom]
             [goog.object :as gobj]
             [goog.object :as gobj]
@@ -89,6 +92,20 @@
        ["NOW" (->marker "NOW")]])))
        ["NOW" (->marker "NOW")]])))
 
 
 ;; Credits to roamresearch.com
 ;; Credits to roamresearch.com
+
+(defn- ->heading
+  [heading]
+  [[:editor/clear-current-slash]
+   [:editor/set-heading heading]
+   [:editor/move-cursor-to-end]])
+
+(defn- markdown-headings
+  []
+  (let [format (state/get-preferred-format)]
+    (when (= (name format) "markdown")
+      (mapv (fn [level]
+              (let [heading (str "h" level)]
+                [heading (->heading (apply str (repeat level "#")))])) (range 1 7)))))
 (defn commands-map
 (defn commands-map
   [get-page-ref-text]
   [get-page-ref-text]
   (->>
   (->>
@@ -157,7 +174,8 @@
 
 
      ;; TODO:
      ;; TODO:
      ;; ["Upload a file" nil]
      ;; ["Upload a file" nil]
-]
+     ]
+    (markdown-headings)
     ;; Allow user to modify or extend, should specify how to extend.
     ;; Allow user to modify or extend, should specify how to extend.
     (state/get-commands))
     (state/get-commands))
    (remove nil?)
    (remove nil?)
@@ -176,9 +194,20 @@
   ([type]
   ([type]
    (->block type nil))
    (->block type nil))
   ([type optional]
   ([type optional]
-   (let [left (util/format "#+BEGIN_%s"
-                           (string/upper-case type))
-         right (util/format "\n#+END_%s" (string/upper-case type))
+   (let [format (get state/get-edit-block :block/format :markdown)
+         org? (= format :org)
+         t (string/lower-case type)
+         markdown-src? (and (= format :markdown) (= t "src"))
+         left (cond
+                markdown-src?
+                "```"
+
+                :else
+                (util/format "#+BEGIN_%s"
+                             (string/upper-case type)))
+         right (if markdown-src?
+                 (str "\n```")
+                 (util/format "\n#+END_%s" (string/upper-case type)))
          template (str
          template (str
                    left
                    left
                    (if optional (str " " optional) "")
                    (if optional (str " " optional) "")
@@ -207,7 +236,8 @@
      ["Src" (->block "src" "")]
      ["Src" (->block "src" "")]
      ["Query" (->block "query")]
      ["Query" (->block "query")]
      ["Latex export" (->block "export" "latex")]
      ["Latex export" (->block "export" "latex")]
-     ["Properties" (->properties)]
+     (when-not (= :markdown (state/get-preferred-format))
+       ["Properties" (->properties)])
      ["Note" (->block "note")]
      ["Note" (->block "note")]
      ["Tip" (->block "tip")]
      ["Tip" (->block "tip")]
      ["Important" (->block "important")]
      ["Important" (->block "important")]
@@ -243,34 +273,38 @@
    {:keys [last-pattern postfix-fn backward-pos forward-pos]
    {:keys [last-pattern postfix-fn backward-pos forward-pos]
     :or {last-pattern slash}
     :or {last-pattern slash}
     :as option}]
     :as option}]
-  (let [input (gdom/getElement id)
-        edit-content (gobj/get input "value")
-        current-pos (:pos (util/get-caret-pos input))
-        prefix (subs edit-content 0 current-pos)
-        space? (when last-pattern
-                 (let [s (when-let [last-index (string/last-index-of prefix last-pattern)]
-                           (util/safe-subs prefix 0 last-index))]
-                   (not (and (string/ends-with? s "(")
-                             (or (string/starts-with? last-pattern "((")
-                                 (string/starts-with? last-pattern "[["))))))
-        prefix (if (string/blank? last-pattern)
-                 (if space?
-                   (util/concat-without-spaces prefix value)
-                   (str prefix value))
-                 (util/replace-last last-pattern prefix value space?))
-        postfix (subs edit-content current-pos)
-        postfix (if postfix-fn (postfix-fn postfix) postfix)
-        new-value (if space?
-                    (util/concat-without-spaces prefix postfix)
-                    (str prefix postfix))
-        new-pos (- (+ (count prefix)
-                      (or forward-pos 0))
-                   (or backward-pos 0))]
-    (state/set-block-content-and-last-pos! id new-value new-pos)
-    (util/move-cursor-to input
-                         (if (or backward-pos forward-pos)
-                           new-pos
-                           (+ new-pos 1)))))
+  (when-let [input (gdom/getElement id)]
+    (let [edit-content (gobj/get input "value")
+          current-pos (:pos (util/get-caret-pos input))
+          prefix (subs edit-content 0 current-pos)
+          space? (when (and last-pattern prefix)
+                   (let [s (when-let [last-index (string/last-index-of prefix last-pattern)]
+                             (util/safe-subs prefix 0 last-index))]
+                     (not (and s
+                               (string/ends-with? s "(")
+                               (or (string/starts-with? last-pattern "((")
+                                   (string/starts-with? last-pattern "[["))))))
+          space? (if (and space? (string/starts-with? last-pattern "#[["))
+                   false
+                   space?)
+          prefix (if (string/blank? last-pattern)
+                   (if space?
+                     (util/concat-without-spaces prefix value)
+                     (str prefix value))
+                   (util/replace-last last-pattern prefix value space?))
+          postfix (subs edit-content current-pos)
+          postfix (if postfix-fn (postfix-fn postfix) postfix)
+          new-value (if space?
+                      (util/concat-without-spaces prefix postfix)
+                      (str prefix postfix))
+          new-pos (- (+ (count prefix)
+                        (or forward-pos 0))
+                     (or backward-pos 0))]
+      (state/set-block-content-and-last-pos! id new-value new-pos)
+      (util/move-cursor-to input
+                           (if (or backward-pos forward-pos)
+                             new-pos
+                             (+ new-pos 1))))))
 
 
 (defn simple-insert!
 (defn simple-insert!
   [id value
   [id value
@@ -402,7 +436,7 @@
 
 
 (defn compute-pos-delta-when-change-marker
 (defn compute-pos-delta-when-change-marker
   [current-input edit-content new-value marker pos]
   [current-input edit-content new-value marker pos]
-  (let [old-marker (some->> (first (re-find format/bare-marker-pattern edit-content))
+  (let [old-marker (some->> (first (re-find marker/bare-marker-pattern edit-content))
                             (string/trim))
                             (string/trim))
         old-marker (if old-marker old-marker "")
         old-marker (if old-marker old-marker "")
         pos-delta (- (count marker)
         pos-delta (- (count marker)
@@ -427,7 +461,7 @@
                     (count (re-find re-pattern prefix))))
                     (count (re-find re-pattern prefix))))
             new-value (str (subs edit-content 0 pos)
             new-value (str (subs edit-content 0 pos)
                            (string/replace-first (subs edit-content pos)
                            (string/replace-first (subs edit-content pos)
-                                                 format/marker-pattern
+                                                 marker/marker-pattern
                                                  (str marker " ")))]
                                                  (str marker " ")))]
         (state/set-edit-content! input-id new-value)
         (state/set-edit-content! input-id new-value)
         (let [new-pos (compute-pos-delta-when-change-marker
         (let [new-pos (compute-pos-delta-when-change-marker
@@ -436,26 +470,30 @@
           (js/setTimeout #(util/set-caret-pos! current-input new-pos) 10))))))
           (js/setTimeout #(util/set-caret-pos! current-input new-pos) 10))))))
 
 
 (defmethod handle-step :editor/set-priority [[_ priority] format]
 (defmethod handle-step :editor/set-priority [[_ priority] format]
+  (when-let [input-id (state/get-edit-input-id)]
+    (when-let [current-input (gdom/getElement input-id)]
+      (let [format (or (db/get-page-format (state/get-current-page)) (state/get-preferred-format))
+            edit-content (gobj/get current-input "value")
+            new-priority (util/format "[#%s]" priority)
+            new-value (string/trim (priority/add-or-update-priority edit-content format new-priority))]
+        (state/set-edit-content! input-id new-value)))))
+
+(defmethod handle-step :editor/set-heading [[_ heading]]
   (when-let [input-id (state/get-edit-input-id)]
   (when-let [input-id (state/get-edit-input-id)]
     (when-let [current-input (gdom/getElement input-id)]
     (when-let [current-input (gdom/getElement input-id)]
       (let [edit-content (gobj/get current-input "value")
       (let [edit-content (gobj/get current-input "value")
             slash-pos (:pos @*slash-caret-pos)
             slash-pos (:pos @*slash-caret-pos)
-            priority-pattern  #"\[#[A|B|C]{1}\]"
+            heading-pattern  #"^#\+"
             prefix (subs edit-content 0 (dec slash-pos))
             prefix (subs edit-content 0 (dec slash-pos))
-            pos (count (re-find priority-pattern prefix))
-            new-priority (util/format "[#%s]" priority)
+            pos (count (re-find heading-pattern prefix))
             new-value (cond
             new-value (cond
-                        (re-find priority-pattern prefix)
+                        (re-find heading-pattern prefix)
                         (str (subs edit-content 0 pos)
                         (str (subs edit-content 0 pos)
                              (string/replace-first (subs edit-content pos)
                              (string/replace-first (subs edit-content pos)
-                                                   priority-pattern
-                                                   new-priority))
-                        (re-find format/marker-pattern edit-content)
-                        (string/replace-first edit-content format/marker-pattern
-                                              (fn [marker] (str marker new-priority " ")))
-
+                                                   heading-pattern
+                                                   heading))
                         :else
                         :else
-                        (str new-priority " " (string/triml edit-content)))]
+                        (str heading " " (string/triml edit-content)))]
         (state/set-edit-content! input-id new-value)))))
         (state/set-edit-content! input-id new-value)))))
 
 
 (defmethod handle-step :editor/search-page [[_]]
 (defmethod handle-step :editor/search-page [[_]]

文件差异内容过多而无法显示
+ 325 - 262
src/main/frontend/components/block.cljs


+ 10 - 15
src/main/frontend/components/block.css

@@ -74,7 +74,7 @@
 
 
   .resize {
   .resize {
     display: inline-flex;
     display: inline-flex;
-    /* Fix chrome missing resize handle issue. Ref: https://github.com/logseq/logseq/pull/1692/files */
+    /* Fix chrome missing resize handle issue https://bugs.chromium.org/p/chromium/issues/detail?id=1135676&q=css%20resize%20type%3DBug&can=2.*/
     transform: translate3d(0, 0, 0);
     transform: translate3d(0, 0, 0);
   }
   }
 
 
@@ -101,6 +101,10 @@
   }
   }
 }
 }
 
 
+.block-body ul, .block-body ol, .block-body dl {
+    margin-bottom: 2em;
+}
+
 .block-children {
 .block-children {
   border-left: 1px solid;
   border-left: 1px solid;
   border-left-color: var(--ls-guideline-color, #ddd);
   border-left-color: var(--ls-guideline-color, #ddd);
@@ -157,25 +161,16 @@
 }
 }
 
 
 .blocks-properties {
 .blocks-properties {
+  padding: 4px 8px;
   background-color: var(--ls-block-properties-background-color, #f0f8ff);
   background-color: var(--ls-block-properties-background-color, #f0f8ff);
 }
 }
 
 
 .marker-switch {
 .marker-switch {
-  font-size: 85%;
-  margin-right: 6px;
-  margin-left: 2px;
-  border-radius: 3px;
-  font-weight: 500;
-  display: inline-block;
-  text-align: center;
-  width: 16px;
-  height: 18px;
+  padding: 2px 4px;
   opacity: 0.5;
   opacity: 0.5;
-  padding: 0 2px 0 2px;
-  border: 1px solid;
-  line-height: 1.3;
-  color: var(--ls-link-text-color, #045591);
-  cursor: pointer;
+  font-size: 85%;
+  margin: 0 2px 0 0px;
+  font-weight: 600;
 
 
   &:hover {
   &:hover {
     color: var(--ls-link-text-hover-color);
     color: var(--ls-link-text-hover-color);

+ 14 - 0
src/main/frontend/components/commit.cljs

@@ -1,6 +1,7 @@
 (ns frontend.components.commit
 (ns frontend.components.commit
   (:require [rum.core :as rum]
   (:require [rum.core :as rum]
             [frontend.util :as util :refer-macros [profile]]
             [frontend.util :as util :refer-macros [profile]]
+            [clojure.string :as string]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.mixins :as mixins]
             [frontend.mixins :as mixins]
@@ -53,3 +54,16 @@
         {:type "button"
         {:type "button"
          :on-click close-fn}
          :on-click close-fn}
         "Cancel"]]]]))
         "Cancel"]]]]))
+
+(defn show-commit-modal! [e]
+  (when (and
+         (string/starts-with? (state/get-current-repo) "https://")
+         (not (util/input? (gobj/get e "target")))
+         (not (gobj/get e "shiftKey"))
+         (not (gobj/get e "ctrlKey"))
+         (not (gobj/get e "altKey"))
+         (not (gobj/get e "metaKey")))
+    (when-let [repo-url (state/get-current-repo)]
+      (when-not (state/get-edit-input-id)
+        (util/stop e)
+        (state/set-modal! commit-and-push!)))))

+ 12 - 74
src/main/frontend/components/content.cljs

@@ -56,11 +56,7 @@
     (ui/menu-link
     (ui/menu-link
      {:key "copy"
      {:key "copy"
       :on-click editor-handler/copy-selection-blocks}
       :on-click editor-handler/copy-selection-blocks}
-     "Copy")
-    (ui/menu-link
-     {:key "make-todos"
-      :on-click editor-handler/bulk-make-todos}
-     (str "Make " (state/get-preferred-todo) "s"))]])
+     "Copy")]])
 
 
 ;; FIXME: Make it configurable
 ;; FIXME: Make it configurable
 (def block-background-colors
 (def block-background-colors
@@ -117,9 +113,9 @@
                                        [:p "Template already exists!"]
                                        [:p "Template already exists!"]
                                        :error)
                                        :error)
                                       (do
                                       (do
-                                        (editor-handler/set-block-property! block-id "template" title)
+                                        (editor-handler/set-block-property! block-id :template title)
                                         (when (false? including-parent?)
                                         (when (false? including-parent?)
-                                          (editor-handler/set-block-property! block-id "including-parent" false))
+                                          (editor-handler/set-block-property! block-id :including-parent false))
                                         (state/hide-custom-context-menu!)))))))])
                                         (state/hide-custom-context-menu!)))))))])
       (ui/menu-link
       (ui/menu-link
        {:key "Make template"
        {:key "Make template"
@@ -133,8 +129,7 @@
   (rum/with-context [[t] i18n/*tongue-context*]
   (rum/with-context [[t] i18n/*tongue-context*]
     (when-let [block (db/entity [:block/uuid block-id])]
     (when-let [block (db/entity [:block/uuid block-id])]
       (let [properties (:block/properties block)
       (let [properties (:block/properties block)
-            heading (get properties "heading")
-            heading? (= heading "true")]
+            heading? (true? (:heading properties))]
         [:div#custom-context-menu
         [:div#custom-context-menu
          [:div.py-1.rounded-md.bg-base-3.shadow-xs
          [:div.py-1.rounded-md.bg-base-3.shadow-xs
           [:div.flex-row.flex.justify-between.py-4.pl-2
           [:div.flex-row.flex.justify-between.py-4.pl-2
@@ -142,58 +137,26 @@
             (for [color block-background-colors]
             (for [color block-background-colors]
               [:a.m-2.shadow-sm
               [:a.m-2.shadow-sm
                {:on-click (fn [_e]
                {:on-click (fn [_e]
-                            (editor-handler/set-block-property! block-id "background_color" color))}
+                            (editor-handler/set-block-property! block-id "background-color" color))}
                [:div.heading-bg {:style {:background-color color}}]])]
                [:div.heading-bg {:style {:background-color color}}]])]
            [:a.text-sm
            [:a.text-sm
             {:title (t :remove-background)
             {:title (t :remove-background)
              :style {:margin-right 14
              :style {:margin-right 14
                      :margin-top 4}
                      :margin-top 4}
              :on-click (fn [_e]
              :on-click (fn [_e]
-                         (editor-handler/remove-block-property! block-id "background_color"))}
+                         (editor-handler/remove-block-property! block-id "background-color"))}
             "Clear"]]
             "Clear"]]
+
           (ui/menu-link
           (ui/menu-link
            {:key "Convert heading"
            {:key "Convert heading"
             :on-click (fn [_e]
             :on-click (fn [_e]
                         (if heading?
                         (if heading?
-                          (editor-handler/remove-block-property! block-id "heading")
-                          (editor-handler/set-block-as-a-heading! block-id true)))}
+                          (editor-handler/remove-block-property! block-id :heading)
+                          (editor-handler/set-block-property! block-id :heading true)))}
            (if heading?
            (if heading?
              "Convert back to a block"
              "Convert back to a block"
              "Convert to a heading"))
              "Convert to a heading"))
 
 
-          (let [empty-properties? (not (text/contains-properties? (:block/content block)))
-                all-hidden? (text/properties-hidden? (:block/properties block))]
-            (when (or empty-properties? all-hidden?)
-              (ui/menu-link
-               {:key "Add a property"
-                :on-click (fn [_e]
-                            (when-let [block-node (util/rec-get-block-node target)]
-                              (let [block-dom-id (gobj/get block-node "id")
-                                    edit-input-id (string/replace block-dom-id "ls-block" "edit-block")
-                                    content (:block/content block)
-                                    content (cond
-                                              empty-properties?
-                                              (text/rejoin-properties content {"" ""} {:remove-blank? false})
-                                              all-hidden?
-                                              (let [idx (string/index-of content "\n:END:")]
-                                                (str
-                                                 (subs content 0 idx)
-                                                 "\n:: "
-                                                 (subs content idx)))
-                                              :else
-                                              content)
-                                    content-without-level (text/remove-level-spaces content (:block/format block))
-                                    pos (string/index-of content-without-level ": \n:END:")]
-                                (editor-handler/edit-block! block
-                                                            pos
-                                                            (:block/format block)
-                                                            edit-input-id
-                                                            (cond-> {:custom-content content}
-                                                              all-hidden?
-                                                              (assoc :custom-properties
-                                                                     (assoc (:block/properties block) "" "")))))))}
-               "Add a property")))
-
           (ui/menu-link
           (ui/menu-link
            {:key "Open in sidebar"
            {:key "Open in sidebar"
             :on-click (fn [_e]
             :on-click (fn [_e]
@@ -251,27 +214,6 @@
 ;; TODO: content could be changed
 ;; TODO: content could be changed
 ;; Also, keyboard bindings should only be activated after
 ;; Also, keyboard bindings should only be activated after
 ;; blocks were already selected.
 ;; blocks were already selected.
-
-
-(defn- cut-blocks-and-clear-selections!
-  [copy?]
-  (editor-handler/cut-selection-blocks copy?)
-  (editor-handler/clear-selection! nil))
-
-(rum/defc hidden-selection < rum/reactive
-  (mixins/keyboard-mixin (util/->system-modifier "ctrl+c")
-                         (fn [_]
-                           (editor-handler/copy-selection-blocks)
-                           (editor-handler/clear-selection! nil)))
-  (mixins/keyboard-mixin (util/->system-modifier "ctrl+x")
-                         (fn [] (cut-blocks-and-clear-selections! true)))
-  (mixins/keyboard-mixin "backspace"
-                         (fn [] (cut-blocks-and-clear-selections! false)))
-  (mixins/keyboard-mixin "delete"
-                         (fn [] (cut-blocks-and-clear-selections! false)))
-  []
-  [:div#selection.hidden])
-
 (rum/defc hiccup-content < rum/static
 (rum/defc hiccup-content < rum/static
   (mixins/event-mixin
   (mixins/event-mixin
    (fn [state]
    (fn [state]
@@ -282,8 +224,7 @@
                           (let [blocks (remove nil? blocks)
                           (let [blocks (remove nil? blocks)
                                 blocks (remove #(d/has-class? % "dummy") blocks)]
                                 blocks (remove #(d/has-class? % "dummy") blocks)]
                             (when (seq blocks)
                             (when (seq blocks)
-                              (doseq [block blocks]
-                                (d/add-class! block "selected noselect"))
+                              (util/select-highlight! blocks)
                               ;; TODO: We delay this so the following "click" event won't clear the selections.
                               ;; TODO: We delay this so the following "click" event won't clear the selections.
                               ;; Needs more thinking.
                               ;; Needs more thinking.
                               (js/setTimeout #(state/set-selection-blocks! blocks)
                               (js/setTimeout #(state/set-selection-blocks! blocks)
@@ -306,8 +247,7 @@
                                               :left (str client-x "px")
                                               :left (str client-x "px")
                                               :top (str (+ scroll-y client-y) "px")))))
                                               :top (str (+ scroll-y client-y) "px")))))
 
 
-                          (and (state/in-selection-mode?)
-                               (seq (state/get-selection-blocks)))
+                          (state/selection?)
                           (do
                           (do
                             (util/stop e)
                             (util/stop e)
                             (let [client-x (gobj/get e "clientX")
                             (let [client-x (gobj/get e "clientX")
@@ -395,8 +335,6 @@
         selected-blocks (state/sub :selection/blocks)]
         selected-blocks (state/sub :selection/blocks)]
     (if hiccup
     (if hiccup
       [:div
       [:div
-       (hiccup-content id option)
-       (when (and in-selection-mode? (seq selected-blocks))
-         (hidden-selection))]
+       (hiccup-content id option)]
       (let [format (format/normalize format)]
       (let [format (format/normalize format)]
         (non-hiccup-content id content on-click on-hide config format)))))
         (non-hiccup-content id content on-click on-hide config format)))))

+ 78 - 71
src/main/frontend/components/diff.cljs

@@ -6,6 +6,7 @@
             [frontend.handler.file :as file]
             [frontend.handler.file :as file]
             [frontend.handler.notification :as notification]
             [frontend.handler.notification :as notification]
             [frontend.handler.common :as common-handler]
             [frontend.handler.common :as common-handler]
+            [frontend.handler.file :as file-handler]
             [frontend.state :as state]
             [frontend.state :as state]
             [clojure.string :as string]
             [clojure.string :as string]
             [frontend.db :as db]
             [frontend.db :as db]
@@ -49,9 +50,16 @@
                     :style {:background-color bg-color}}
                     :style {:background-color bg-color}}
         value]))])
         value]))])
 
 
-(rum/defc file < rum/reactive
-  [repo type path contents remote-oid]
-  (let [{:keys [collapse? resolved?]} (util/react (rum/cursor diff-state path))
+(rum/defcs file < rum/reactive
+  {:will-mount (fn [state]
+                 (let [*local-content (atom "")
+                       [repo _ path & _others] (:rum/args state)]
+                   (p/let [content (file-handler/load-file repo path )]
+                     (reset! *local-content content))
+                   (assoc state ::local-content *local-content)))}
+  [state repo type path contents remote-oid]
+  (let [local-content (rum/react (get state ::local-content))
+        {:keys [collapse? resolved?]} (util/react (rum/cursor diff-state path))
         edit? (util/react *edit?)
         edit? (util/react *edit?)
         delete? (= type "remove")]
         delete? (= type "remove")]
     [:div.cp__diff-file
     [:div.cp__diff-file
@@ -70,74 +78,73 @@
      (let [content (get contents path)]
      (let [content (get contents path)]
        (if (or (and delete? (nil? content))
        (if (or (and delete? (nil? content))
                content)
                content)
-         (let [local-content (db/get-file path)]
-           (if (not= content local-content)
-             (let [local-content (or local-content "")
-                   content (or content "")
-                   diff (medley/indexed (diff/diff local-content content))
-                   diff? (some (fn [[_idx {:keys [added removed]}]]
-                                 (or added removed))
-                               diff)]
-               [:div.pre-line-white-space.p-2 {:class (if collapse? "hidden")
-                                               :style {:overflow "auto"}}
-                (if edit?
-                  [:div.grid.grid-cols-2.gap-1
-                   (diff-cp diff)
-                   (ui/textarea
-                    {:default-value local-content
-                     :on-change (fn [e]
-                                  (reset! *edit-content (util/evalue e)))})]
-                  (diff-cp diff))
-
-                (cond
-                  edit?
-                  [:div.mt-2
-                   (ui/button "Save"
-                              :on-click
-                              (fn []
-                                (reset! *edit? false)
-                                (let [new-content @*edit-content]
-                                  (file/alter-file repo path new-content
-                                                   {:commit? false
-                                                    :re-render-root? true})
-                                  (swap! state/state
-                                         assoc-in [:github/contents repo remote-oid path] new-content)
-                                  (mark-as-resolved path))))]
-
-                  diff?
-                  [:div.mt-2
-                   (ui/button "Use remote"
-                              :on-click
-                              (fn []
-                                ;; overwrite the file
-                                (if delete?
-                                  (file/remove-file! repo path)
-                                  (file/alter-file repo path content
-                                                   {:commit? false
-                                                    :re-render-root? true}))
-                                (mark-as-resolved path))
-                              :background "green")
-
-                   [:span.pl-2.pr-2 "or"]
-
-                   (ui/button "Keep local"
-                              :on-click
-                              (fn []
-                                ;; overwrite the file
-                                (swap! state/state
-                                       assoc-in [:github/contents repo remote-oid path] local-content)
-                                (mark-as-resolved path))
-                              :background "pink")
-
-                   [:span.pl-2.pr-2 "or"]
-
-                   (ui/button "Edit"
-                              :on-click
-                              (fn []
-                                (reset! *edit? true)))]
-
-                  :else
-                  nil)])))
+         (if (not= content local-content)
+           (let [local-content (or local-content "")
+                 content (or content "")
+                 diff (medley/indexed (diff/diff local-content content))
+                 diff? (some (fn [[_idx {:keys [added removed]}]]
+                               (or added removed))
+                             diff)]
+             [:div.pre-line-white-space.p-2 {:class (if collapse? "hidden")
+                                             :style {:overflow "auto"}}
+              (if edit?
+                [:div.grid.grid-cols-2.gap-1
+                 (diff-cp diff)
+                 (ui/textarea
+                  {:default-value local-content
+                   :on-change (fn [e]
+                                (reset! *edit-content (util/evalue e)))})]
+                (diff-cp diff))
+
+              (cond
+                edit?
+                [:div.mt-2
+                 (ui/button "Save"
+                   :on-click
+                   (fn []
+                     (reset! *edit? false)
+                     (let [new-content @*edit-content]
+                       (file/alter-file repo path new-content
+                                        {:commit? false
+                                         :re-render-root? true})
+                       (swap! state/state
+                              assoc-in [:github/contents repo remote-oid path] new-content)
+                       (mark-as-resolved path))))]
+
+                diff?
+                [:div.mt-2
+                 (ui/button "Use remote"
+                   :on-click
+                   (fn []
+                     ;; overwrite the file
+                     (if delete?
+                       (file/remove-file! repo path)
+                       (file/alter-file repo path content
+                                        {:commit? false
+                                         :re-render-root? true}))
+                     (mark-as-resolved path))
+                   :background "green")
+
+                 [:span.pl-2.pr-2 "or"]
+
+                 (ui/button "Keep local"
+                   :on-click
+                   (fn []
+                     ;; overwrite the file
+                     (swap! state/state
+                            assoc-in [:github/contents repo remote-oid path] local-content)
+                     (mark-as-resolved path))
+                   :background "pink")
+
+                 [:span.pl-2.pr-2 "or"]
+
+                 (ui/button "Edit"
+                   :on-click
+                   (fn []
+                     (reset! *edit? true)))]
+
+                :else
+                nil)]))
          [:div "loading..."]))]))
          [:div "loading..."]))]))
 
 
 ;; TODO: `n` shortcut for next diff, `p` for previous diff
 ;; TODO: `n` shortcut for next diff, `p` for previous diff

+ 60 - 53
src/main/frontend/components/editor.cljs

@@ -26,33 +26,35 @@
                      *angle-bracket-caret-pos
                      *angle-bracket-caret-pos
                      *matched-block-commands
                      *matched-block-commands
                      *show-block-commands]]
                      *show-block-commands]]
-            ["/frontend/utils" :as utils]))
+            ["/frontend/utils" :as utils]
+            [frontend.modules.shortcut.core :as shortcut]))
 
 
 (rum/defc commands < rum/reactive
 (rum/defc commands < rum/reactive
   [id format]
   [id format]
-  (when (and (util/react *show-commands)
-             @*slash-caret-pos
-             (not (state/sub :editor/show-page-search?))
-             (not (state/sub :editor/show-block-search?))
-             (not (state/sub :editor/show-template-search?))
-             (not (state/sub :editor/show-input))
-             (not (state/sub :editor/show-date-picker?)))
-    (let [matched (util/react *matched-commands)]
-      (ui/auto-complete
-       (map first matched)
-       {:on-chosen (fn [chosen]
-                     (reset! commands/*current-command chosen)
-                     (let [command-steps (get (into {} matched) chosen)
-                           restore-slash? (or
-                                           (contains? #{"Today" "Yesterday" "Tomorrow"} chosen)
-                                           (and
-                                            (not (fn? command-steps))
-                                            (not (contains? (set (map first command-steps)) :editor/input))
-                                            (not (contains? #{"Date Picker" "Template" "Deadline" "Scheduled" "Upload an image"} chosen))))]
-                       (editor-handler/insert-command! id command-steps
-                                                       format
-                                                       {:restore? restore-slash?})))
-        :class     "black"}))))
+  (let [show-commands? (util/react *show-commands)]
+    (when (and show-commands?
+              @*slash-caret-pos
+              (not (state/sub :editor/show-page-search?))
+              (not (state/sub :editor/show-block-search?))
+              (not (state/sub :editor/show-template-search?))
+              (not (state/sub :editor/show-input))
+              (not (state/sub :editor/show-date-picker?)))
+     (let [matched (util/react *matched-commands)]
+       (ui/auto-complete
+        (map first matched)
+        {:on-chosen (fn [chosen]
+                      (reset! commands/*current-command chosen)
+                      (let [command-steps (get (into {} matched) chosen)
+                            restore-slash? (or
+                                            (contains? #{"Today" "Yesterday" "Tomorrow"} chosen)
+                                            (and
+                                             (not (fn? command-steps))
+                                             (not (contains? (set (map first command-steps)) :editor/input))
+                                             (not (contains? #{"Date Picker" "Template" "Deadline" "Scheduled" "Upload an image"} chosen))))]
+                        (editor-handler/insert-command! id command-steps
+                                                        format
+                                                        {:restore? restore-slash?})))
+         :class     "black"})))))
 
 
 (rum/defc block-commands < rum/reactive
 (rum/defc block-commands < rum/reactive
   [id format]
   [id format]
@@ -75,7 +77,7 @@
           input (gdom/getElement id)]
           input (gdom/getElement id)]
       (when input
       (when input
         (let [current-pos (:pos (util/get-caret-pos input))
         (let [current-pos (:pos (util/get-caret-pos input))
-              edit-content (state/sub [:editor/content id])
+              edit-content (or (state/sub [:editor/content id]) "")
               edit-block (state/sub :editor/block)
               edit-block (state/sub :editor/block)
               q (or
               q (or
                  @editor-handler/*selected-text
                  @editor-handler/*selected-text
@@ -114,8 +116,8 @@
         :on-enter    non-exist-block-handler
         :on-enter    non-exist-block-handler
         :empty-div   [:div.text-gray-500.pl-4.pr-4 "Search for a block"]
         :empty-div   [:div.text-gray-500.pl-4.pr-4 "Search for a block"]
         :item-render (fn [{:block/keys [content page uuid] :as item}]
         :item-render (fn [{:block/keys [content page uuid] :as item}]
-                       (let [page (or (:page/original-name page)
-                                      (:page/name page))
+                       (let [page (or (:block/original-name page)
+                                      (:block/name page))
                              repo (state/sub :git/current-repo)
                              repo (state/sub :git/current-repo)
                              format (db/get-page-format page)]
                              format (db/get-page-format page)]
 
 
@@ -172,16 +174,16 @@
   [parent-state parent-id]
   [parent-state parent-id]
   [:div#mobile-editor-toolbar.bg-base-2.fix-ios-fixed-bottom
   [:div#mobile-editor-toolbar.bg-base-2.fix-ios-fixed-bottom
    [:button.bottom-action
    [:button.bottom-action
-    {:on-click #(editor-handler/adjust-block-level! parent-state :right)}
+    {:on-click #(editor-handler/indent-outdent parent-state true)}
     svg/indent-block]
     svg/indent-block]
    [:button.bottom-action
    [:button.bottom-action
-    {:on-click #(editor-handler/adjust-block-level! parent-state :left)}
+    {:on-click #(editor-handler/indent-outdent parent-state false)}
     svg/outdent-block]
     svg/outdent-block]
    [:button.bottom-action
    [:button.bottom-action
-    {:on-click #(editor-handler/move-up-down % true)}
+    {:on-click (editor-handler/move-up-down true)}
     svg/move-up-block]
     svg/move-up-block]
    [:button.bottom-action
    [:button.bottom-action
-    {:on-click #(editor-handler/move-up-down % false)}
+    {:on-click (editor-handler/move-up-down false)}
     svg/move-down-block]
     svg/move-down-block]
    [:button.bottom-action
    [:button.bottom-action
     {:on-click #(commands/simple-insert! parent-id "\n" {})}
     {:on-click #(commands/simple-insert! parent-id "\n" {})}
@@ -337,24 +339,11 @@
         *slash-caret-pos)))])
         *slash-caret-pos)))])
 
 
 (defn- set-up-key-down!
 (defn- set-up-key-down!
-  [repo state input input-id format]
+  [repo state format]
   (mixins/on-key-down
   (mixins/on-key-down
    state
    state
-   {;; enter
-    13 (editor-handler/keydown-enter-handler state input)
-    ;; up
-    38 (editor-handler/keydown-up-down-handler input true)
-    ;; down
-    40 (editor-handler/keydown-up-down-handler input false)
-    ;; left
-    37 (editor-handler/keydown-arrow-handler input :left)
-    ;; right
-    39 (editor-handler/keydown-arrow-handler input :right)
-    ;; backspace
-    8 (editor-handler/keydown-backspace-handler repo input input-id)
-    ;; tab
-    9 (editor-handler/keydown-tab-handler input input-id)}
-   {:not-matched-handler (editor-handler/keydown-not-matched-handler input input-id format)}))
+   {}
+   {:not-matched-handler (editor-handler/keydown-not-matched-handler format)}))
 
 
 (defn- set-up-key-up!
 (defn- set-up-key-up!
   [state input input-id search-timeout]
   [state input input-id search-timeout]
@@ -367,32 +356,50 @@
 
 
 (defn- setup-key-listener!
 (defn- setup-key-listener!
   [state]
   [state]
-  (let [{:keys [id format block]} (get-state state)
+  (let [{:keys [id format block]} (get-state)
         input-id id
         input-id id
         input (gdom/getElement input-id)
         input (gdom/getElement input-id)
         repo (:block/repo block)]
         repo (:block/repo block)]
-    (set-up-key-down! repo state input input-id format)
+    (set-up-key-down! repo state format)
     (set-up-key-up! state input input-id search-timeout)))
     (set-up-key-up! state input input-id search-timeout)))
 
 
+(defn- get-editor-style
+  [heading-level]
+  (case heading-level
+    1 {:font-size "2em" :font-weight "bold" :margin "0.67em 0"}
+    2 {:font-size "1.5em" :font-weight "bold" :margin "0.75em 0"}
+    3 {:font-size "1.17em" :font-weight "bold" :margin "0.83em 0"}
+    4 {:font-weight "bold" :margin "1.12em 0"}
+    5 {:font-size "0.83em" :font-weight "bold" :margin "1.5em 0"}
+    6 {:font-size "0.75em" :font-weight "bold" :margin "1.67em 0"}
+    nil))
+
 (rum/defcs box < rum/reactive
 (rum/defcs box < rum/reactive
+  {:init (fn [state]
+           (assoc state ::heading-level (:heading-level (first (:rum/args state)))))
+   :did-mount (fn [state]
+                (state/set-editor-args! (:rum/args state))
+                state)}
   (mixins/event-mixin setup-key-listener!)
   (mixins/event-mixin setup-key-listener!)
+  (shortcut/mixin :shortcut.handler/block-editing-only)
   lifecycle/lifecycle
   lifecycle/lifecycle
-  [state {:keys [on-hide dummy? node format block block-parent-id]
+  [state {:keys [on-hide dummy? node format block block-parent-id heading-level]
           :or   {dummy? false}
           :or   {dummy? false}
           :as   option} id config]
           :as   option} id config]
-  (let [content (state/get-edit-content)]
+  (let [content (state/get-edit-content)
+        heading-level (get state ::heading-level)]
     [:div.editor-inner {:class (if block "block-editor" "non-block-editor")}
     [:div.editor-inner {:class (if block "block-editor" "non-block-editor")}
      (when config/mobile? (mobile-bar state id))
      (when config/mobile? (mobile-bar state id))
      (ui/ls-textarea
      (ui/ls-textarea
       {:id                id
       {:id                id
-       :class             "mousetrap"
        :cacheMeasurements true
        :cacheMeasurements true
        :default-value     (or content "")
        :default-value     (or content "")
        :minRows           (if (state/enable-grammarly?) 2 1)
        :minRows           (if (state/enable-grammarly?) 2 1)
        :on-click          (editor-handler/editor-on-click! id)
        :on-click          (editor-handler/editor-on-click! id)
        :on-change         (editor-handler/editor-on-change! block id search-timeout)
        :on-change         (editor-handler/editor-on-change! block id search-timeout)
        :on-paste          (editor-handler/editor-on-paste! id)
        :on-paste          (editor-handler/editor-on-paste! id)
-       :auto-focus        false})
+       :auto-focus        false
+       :style             (get-editor-style heading-level)})
 
 
      ;; TODO: how to render the transitions asynchronously?
      ;; TODO: how to render the transitions asynchronously?
      (transition-cp
      (transition-cp

+ 1 - 0
src/main/frontend/components/editor.css

@@ -17,6 +17,7 @@
 }
 }
 
 
 .editor-wrapper {
 .editor-wrapper {
+  width: 100%;
   margin: 0 auto;
   margin: 0 auto;
 }
 }
 
 

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

@@ -21,13 +21,18 @@
         [:li.mb-4
         [:li.mb-4
          [:a.font-medium {:on-click #(export/export-repo-as-markdown! current-repo)}
          [:a.font-medium {:on-click #(export/export-repo-as-markdown! current-repo)}
           (t :export-markdown)]]
           (t :export-markdown)]]
+        [:li.mb-4
+         [:a.font-medium {:on-click #(export/convert-repo-markdown-v2! current-repo)}
+          (t :convert-markdown)]]
         [:li.mb-4
         [:li.mb-4
          [:a.font-medium {:on-click #(export/export-repo-as-edn! current-repo)}
          [:a.font-medium {:on-click #(export/export-repo-as-edn! current-repo)}
           (t :export-edn)]]]
           (t :export-edn)]]]
        [:a#download-as-edn.hidden]
        [:a#download-as-edn.hidden]
        [:a#download-as-html.hidden]
        [:a#download-as-html.hidden]
        [:a#download-as-zip.hidden]
        [:a#download-as-zip.hidden]
-       [:a#export-as-markdown.hidden]])))
+       [:a#export-as-markdown.hidden]
+       [:a#convert-markdown-to-unordered-list-or-heading.hidden]
+       ])))
 
 
 
 
 (rum/defc export-page
 (rum/defc export-page
@@ -40,5 +45,9 @@
          [:ul.mr-1
          [:ul.mr-1
           [:li.mb-4
           [:li.mb-4
            [:a.font-medium {:on-click #(export/export-page-as-markdown! page)}
            [:a.font-medium {:on-click #(export/export-page-as-markdown! page)}
-            (t :export-markdown)]]]
-         [:a#export-page-as-markdown.hidden]]))))
+            (t :export-markdown)]]
+          [:li.mb-4
+           [:a.font-medium {:on-click #(export/convert-page-markdown-unordered-list-or-heading! page)}
+            (t :convert-markdown)]]]
+         [:a#export-page-as-markdown.hidden]
+         [:a#convert-markdown-to-unordered-list-or-heading.hidden]]))))

+ 2 - 6
src/main/frontend/components/file.cljs

@@ -1,7 +1,6 @@
 (ns frontend.components.file
 (ns frontend.components.file
   (:require [rum.core :as rum]
   (:require [rum.core :as rum]
             [frontend.util :as util]
             [frontend.util :as util]
-            [frontend.handler.project :as project]
             [frontend.handler.export :as export-handler]
             [frontend.handler.export :as export-handler]
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.state :as state]
             [frontend.state :as state]
@@ -82,7 +81,7 @@
                                   :href (rfe/href :page {:name page})
                                   :href (rfe/href :page {:name page})
                                   :on-click (fn [e]
                                   :on-click (fn [e]
                                               (when (gobj/get e "shiftKey")
                                               (when (gobj/get e "shiftKey")
-                                                (when-let [page (db/entity [:page/name (string/lower-case page)])]
+                                                (when-let [page (db/entity [:block/name (string/lower-case page)])]
                                                   (state/sidebar-add-block!
                                                   (state/sidebar-add-block!
                                                    (state/get-current-repo)
                                                    (state/get-current-repo)
                                                    (:db/id page)
                                                    (:db/id page)
@@ -97,9 +96,6 @@
                                 :display "inline-block"}})
                                 :display "inline-block"}})
           [:span.ml-1 "Please don't remove the page's title property (you can still modify it)."]])
           [:span.ml-1 "Please don't remove the page's title property (you can still modify it)."]])
 
 
-       (when (and config? (state/logged?))
-         [:a.mb-8.block {:on-click (fn [_e] (project/sync-project-settings!))}
-          (tongue :project/sync-settings)])
        (cond
        (cond
          ;; image type
          ;; image type
          (and format (contains? (config/img-formats) format))
          (and format (contains? (config/img-formats) format))
@@ -119,7 +115,7 @@
                  mode (util/get-file-ext path)
                  mode (util/get-file-ext path)
                  mode (if (contains? #{"edn" "clj" "cljc" "cljs" "clojure"} mode) "text/x-clojure" mode)]
                  mode (if (contains? #{"edn" "clj" "cljc" "cljs" "clojure"} mode) "text/x-clojure" mode)]
              (lazy-editor/editor {:file? true
              (lazy-editor/editor {:file? true
-                                  :file-path path} path {:data-lang mode} content nil)))
+                                  :file-path path} path {:data-lang mode} content {})))
 
 
          :else
          :else
          [:div (tongue :file/format-not-supported (name format))])])))
          [:div (tongue :file/format-not-supported (name format))])])))

+ 4 - 18
src/main/frontend/components/header.cljs

@@ -16,7 +16,6 @@
             [frontend.components.search :as search]
             [frontend.components.search :as search]
             [frontend.components.export :as export]
             [frontend.components.export :as export]
             [frontend.components.right-sidebar :as sidebar]
             [frontend.components.right-sidebar :as sidebar]
-            [frontend.handler.project :as project-handler]
             [frontend.handler.page :as page-handler]
             [frontend.handler.page :as page-handler]
             [frontend.handler.web.nfs :as nfs]
             [frontend.handler.web.nfs :as nfs]
             [goog.dom :as gdom]
             [goog.dom :as gdom]
@@ -80,7 +79,7 @@
         (svg/horizontal-dots nil)])
         (svg/horizontal-dots nil)])
      (->>
      (->>
       [(when-not (util/mobile?)
       [(when-not (util/mobile?)
-         {:title (t :help/toggle-right-sidebar)
+         {:title (t :shortcut.ui/toggle-right-sidebar)
           :options {:on-click state/toggle-sidebar-open?!}})
           :options {:on-click state/toggle-sidebar-open?!}})
 
 
        (when current-repo
        (when current-repo
@@ -110,22 +109,9 @@
           :options {:href (rfe/href :all-journals)}
           :options {:href (rfe/href :all-journals)}
           :icon svg/calendar-sm})
           :icon svg/calendar-sm})
 
 
-       (when (project-handler/get-current-project current-repo projects)
-         {:title (t :my-publishing)
-          :options {:href (rfe/href :my-publishing)}})
-
-       (when-let [project (and current-repo
-                               (project-handler/get-current-project current-repo projects))]
-         (let [link (str config/website "/" project)]
-           {:title (str (t :go-to) "/" project)
-            :options {:href link
-                      :target "_blank"}
-            :icon svg/external-link}))
-
-       (when current-repo
-         {:title (t :settings)
-          :options {:on-click #(ui-handler/toggle-settings-modal!)}
-          :icon svg/settings-sm})
+       {:title (t :settings)
+        :options {:on-click #(ui-handler/toggle-settings-modal!)}
+        :icon svg/settings-sm}
 
 
        (when current-repo
        (when current-repo
          {:title (t :export)
          {:title (t :export)

+ 9 - 8
src/main/frontend/components/journal.cljs

@@ -46,9 +46,7 @@
                      false)))
                      false)))
                 state)}
                 state)}
   [blocks page document-mode?]
   [blocks page document-mode?]
-  (let [start-level (or (:block/level (first blocks)) 1)
-        config {:id page
-                :start-level 2
+  (let [config {:id page
                 :editor-box editor/box
                 :editor-box editor/box
                 :document/mode? document-mode?}]
                 :document/mode? document-mode?}]
     (content/content
     (content/content
@@ -61,7 +59,8 @@
   (let [raw-blocks (db/get-page-blocks repo page)
   (let [raw-blocks (db/get-page-blocks repo page)
         document-mode? (state/sub :document/mode?)
         document-mode? (state/sub :document/mode?)
         blocks (->>
         blocks (->>
-                (block-handler/with-dummy-block raw-blocks format nil {:journal? true})
+                (block-handler/with-dummy-block raw-blocks format nil {:journal? true
+                                                                       :page-name page})
                 (db/with-block-refs-count repo))]
                 (db/with-block-refs-count repo))]
     (blocks-inner blocks page document-mode?)))
     (blocks-inner blocks page document-mode?)))
 
 
@@ -76,9 +75,9 @@
                     (not (config/local-db? repo))
                     (not (config/local-db? repo))
                     (not config/publishing?)
                     (not config/publishing?)
                     today?)
                     today?)
-        page-entity (db/pull [:page/name (string/lower-case title)])
-        data-page-tags (when (seq (:page/tags page-entity))
-                         (let [page-names (model/get-page-names-by-ids (map :db/id (:page/tags page)))]
+        page-entity (db/pull [:block/name (string/lower-case title)])
+        data-page-tags (when (seq (:block/tags page-entity))
+                         (let [page-names (model/get-page-names-by-ids (map :db/id (:block/tags page)))]
                            (text/build-data-value page-names)))]
                            (text/build-data-value page-names)))]
     [:div.flex-1.journal.page (cond->
     [:div.flex-1.journal.page (cond->
                                {:class (if intro? "intro" "")}
                                {:class (if intro? "intro" "")}
@@ -106,7 +105,9 @@
 
 
      (page/today-queries repo today? false)
      (page/today-queries repo today? false)
 
 
-     (reference/references title false)
+     (rum/with-key
+       (reference/references title false)
+       (str title "-refs"))
 
 
      (when intro? (onboarding/intro))]))
      (when intro? (onboarding/intro))]))
 
 

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

@@ -13,8 +13,8 @@
                             (fn []
                             (fn []
                               (reset! loaded? true)))
                               (reset! loaded? true)))
                  state)}
                  state)}
-  [config id attr code pos_meta]
+  [config id attr code options]
   (let [loaded? (rum/react loaded?)]
   (let [loaded? (rum/react loaded?)]
     (if loaded?
     (if loaded?
-      (@lazy-editor config id attr code pos_meta)
+      (@lazy-editor config id attr code options)
       (ui/loading "CodeMirror"))))
       (ui/loading "CodeMirror"))))

+ 16 - 68
src/main/frontend/components/onboarding.cljs

@@ -1,10 +1,12 @@
 (ns frontend.components.onboarding
 (ns frontend.components.onboarding
-  (:require [rum.core :as rum]
+  (:require [frontend.components.shortcut :as shortcut]
             [frontend.components.svg :as svg]
             [frontend.components.svg :as svg]
-            [frontend.extensions.latex :as latex]
-            [frontend.extensions.highlight :as highlight]
             [frontend.context.i18n :as i18n]
             [frontend.context.i18n :as i18n]
-            [frontend.util :as util]))
+            [frontend.extensions.highlight :as highlight]
+            [frontend.extensions.latex :as latex]
+            [frontend.handler.route :as route-handler]
+            [frontend.ui :as ui]
+            [rum.core :as rum]))
 
 
 (rum/defc intro
 (rum/defc intro
   []
   []
@@ -216,70 +218,16 @@
          svg/discord]]]
          svg/discord]]]
       [:li
       [:li
        (t :help/shortcuts)
        (t :help/shortcuts)
-       [:table
-        [:thead
-         [:tr
-          [:th [:b (t :help/shortcuts-triggers)]]
-          [:th (t :help/shortcut)]]]
-        [:tbody
-         [:tr [:td (t :help/slash-autocomplete)] [:td "/"]]
-         [:tr [:td (t :help/block-content-autocomplete)] [:td "<"]]
-         [:tr [:td (t :help/reference-autocomplete)] [:td "[[]]"]]
-         [:tr [:td (t :help/block-reference)] [:td "(())"]]]]
-       [:table
-        [:thead
-         [:tr
-          [:th [:span [:b (t :help/key-commands)]
-                (t :help/working-with-lists)]]
-          [:th (t :help/shortcut)]]]
-        [:tbody
-         [:tr [:td (t :help/indent-block-tab)] [:td "Tab"]]
-         [:tr [:td (t :help/unindent-block)] [:td "Shift-Tab"]]
-         [:tr [:td (t :help/move-block-up)] [:td (util/->platform-shortcut "Alt-Shift-Up")]]
-         [:tr [:td (t :help/move-block-down)] [:td (util/->platform-shortcut "Alt-Shift-Down")]]
-         [:tr [:td (t :help/create-new-block)] [:td "Enter"]]
-         [:tr [:td (t :help/new-line-in-block)] [:td "Shift-Enter"]]
-         [:tr [:td (t :undo)] [:td (util/->platform-shortcut "Ctrl-z")]]
-         [:tr [:td (t :redo)] [:td (util/->platform-shortcut "Ctrl-y")]]
-         [:tr [:td (t :help/zoom-in)] [:td (util/->platform-shortcut (if util/mac? "Cmd-." "Alt-Right"))]]
-         [:tr [:td (t :help/zoom-out)] [:td (util/->platform-shortcut (if util/mac? "Cmd-," "Alt-left"))]]
-         [:tr [:td (t :help/follow-link-under-cursor)] [:td (util/->platform-shortcut "Ctrl-o")]]
-         [:tr [:td (t :help/open-link-in-sidebar)] [:td (util/->platform-shortcut "Ctrl-shift-o")]]
-         [:tr [:td (t :expand)] [:td (util/->platform-shortcut "Ctrl-Down")]]
-         [:tr [:td (t :collapse)] [:td (util/->platform-shortcut "Ctrl-Up")]]
-         [:tr [:td (t :select-block-above)] [:td "Shift-Up"]]
-         [:tr [:td (t :select-block-below)] [:td "Shift-Down"]]
-         [:tr [:td (t :select-all-blocks)] [:td (util/->platform-shortcut "Ctrl-Shift-a")]]]]
-       [:table
-        [:thead
-         [:tr
-          [:th [:b (t :general)]]
-          [:th (t :help/shortcut)]]]
-        [:tbody
-         [:tr [:td (t :help/toggle)] [:td "?"]]
-         [:tr [:td (t :help/git-commit-message)] [:td "c"]]
-         [:tr [:td (t :help/full-text-search)] [:td (util/->platform-shortcut "Ctrl-u")]]
-         [:tr [:td (t :help/page-search)] [:td (util/->platform-shortcut "Ctrl-Shift-u")]]
-         [:tr [:td (t :help/open-link-in-sidebar)] [:td "Shift-Click"]]
-         [:tr [:td (t :help/context-menu)] [:td "Right Click"]]
-         [:tr [:td (t :help/fold-unfold)] [:td "Tab"]]
-         [:tr [:td (t :help/toggle-contents)] [:td "t c"]]
-         [:tr [:td (t :help/toggle-doc-mode)] [:td "t d"]]
-         [:tr [:td (t :help/toggle-theme)] [:td "t t"]]
-         [:tr [:td (t :help/toggle-right-sidebar)] [:td "t r"]]
-         [:tr [:td (t :help/toggle-settings)] [:td "t s"]]
-         [:tr [:td (t :help/toggle-insert-new-block)] [:td "t e"]]
-         [:tr [:td (t :help/jump-to-journals)] [:td (if util/mac? "Cmd-j" "Alt-j")]]]]
-       [:table
-        [:thead
-         [:tr
-          [:th [:b (t :formatting)]]
-          [:th (t :help/shortcut)]]]
-        [:tbody
-         [:tr [:td (t :bold)] [:td (util/->platform-shortcut "Ctrl-b")]]
-         [:tr [:td (t :italics)] [:td (util/->platform-shortcut "Ctrl-i")]]
-         [:tr [:td (t :html-link)] [:td (util/->platform-shortcut "Ctrl-k")]]
-         [:tr [:td (t :highlight)] [:td (util/->platform-shortcut "Ctrl-h")]]]]]
+       [:br]
+       (ui/button
+        "Learn more"
+        :on-click
+        (fn []
+          (route-handler/redirect! {:to :shortcut})))
+       (shortcut/trigger-table)
+       (shortcut/shortcut-table :shortcut.category/basics)
+       (shortcut/shortcut-table :shortcut.category/block-editing)
+       (shortcut/shortcut-table :shortcut.category/formatting)]
 
 
       [:li
       [:li
        (t :help/markdown-syntax)
        (t :help/markdown-syntax)

+ 221 - 253
src/main/frontend/components/page.cljs

@@ -1,6 +1,7 @@
 (ns frontend.components.page
 (ns frontend.components.page
   (:require [rum.core :as rum]
   (:require [rum.core :as rum]
             [frontend.util :as util :refer-macros [profile]]
             [frontend.util :as util :refer-macros [profile]]
+            [frontend.util.marker :as marker]
             [frontend.tools.html-export :as html-export]
             [frontend.tools.html-export :as html-export]
             [frontend.handler.file :as file]
             [frontend.handler.file :as file]
             [frontend.handler.page :as page-handler]
             [frontend.handler.page :as page-handler]
@@ -20,7 +21,6 @@
             [frontend.extensions.graph-2d :as graph-2d]
             [frontend.extensions.graph-2d :as graph-2d]
             [frontend.ui :as ui]
             [frontend.ui :as ui]
             [frontend.components.content :as content]
             [frontend.components.content :as content]
-            [frontend.components.project :as project]
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.db.model :as model]
             [frontend.db.model :as model]
@@ -39,6 +39,7 @@
             [frontend.context.i18n :as i18n]
             [frontend.context.i18n :as i18n]
             [reitit.frontend.easy :as rfe]
             [reitit.frontend.easy :as rfe]
             [frontend.text :as text]
             [frontend.text :as text]
+            [frontend.modules.shortcut.core :as shortcut]
             [frontend.handler.block :as block-handler]))
             [frontend.handler.block :as block-handler]))
 
 
 (defn- get-page-name
 (defn- get-page-name
@@ -55,70 +56,52 @@
         (page-handler/add-page-to-recent! repo page-original-name)
         (page-handler/add-page-to-recent! repo page-original-name)
         (db/get-page-blocks repo page-name)))))
         (db/get-page-blocks repo page-name)))))
 
 
+(defn- open-first-block!
+  [state]
+  (let [blocks (nth (:rum/args state) 1)
+        block (first blocks)]
+    (when (:block/dummy? block)
+      (editor-handler/edit-block! block :max (:block/format block) (:block/uuid block))))
+  state)
+(rum/defc page-blocks-inner <
+  {:did-mount open-first-block!
+   :did-update open-first-block!}
+  [page-name page-blocks hiccup sidebar?]
+  [:div.page-blocks-inner
+   (rum/with-key
+     (content/content page-name
+                      {:hiccup   hiccup
+                       :sidebar? sidebar?})
+     (str page-name "-hiccup"))])
+
+(declare page)
+
 (rum/defc page-blocks-cp < rum/reactive
 (rum/defc page-blocks-cp < rum/reactive
   db-mixins/query
   db-mixins/query
-  [repo page file-path page-name page-original-name encoded-page-name sidebar? journal? block? block-id format]
+  [repo page-e file-path page-name page-original-name encoded-page-name sidebar? journal? block? block-id format]
   (let [raw-page-blocks (get-blocks repo page-name page-original-name block? block-id)
   (let [raw-page-blocks (get-blocks repo page-name page-original-name block? block-id)
-        grouped-blocks-by-file (into {} (for [[k v] (db-utils/group-by-file raw-page-blocks)]
-                                          [(:file/path (db-utils/entity (:db/id k))) v]))
-        raw-page-blocks (get grouped-blocks-by-file file-path raw-page-blocks)
         page-blocks (block-handler/with-dummy-block raw-page-blocks format
         page-blocks (block-handler/with-dummy-block raw-page-blocks format
                       (if (empty? raw-page-blocks)
                       (if (empty? raw-page-blocks)
-                        (let [content (db/get-file repo file-path)]
-                          {:block/page {:db/id (:db/id page)}
-                           :block/file {:db/id (:db/id (:page/file page))}
-                           :block/meta
-                           (let [file-id (:db/id (:page/file page))]
-                             {:start-pos (utf8/length (utf8/encode content))
-                              :end-pos nil})}))
+                        {:block/page {:db/id (:db/id page-e)}
+                         :block/file {:db/id (:db/id (:block/file page-e))}})
                       {:journal? journal?
                       {:journal? journal?
                        :page-name page-name})
                        :page-name page-name})
-        start-level (or (:block/level (first page-blocks)) 1)
         hiccup-config {:id (if block? (str block-id) page-name)
         hiccup-config {:id (if block? (str block-id) page-name)
-                       :start-level start-level
                        :sidebar? sidebar?
                        :sidebar? sidebar?
                        :block? block?
                        :block? block?
-                       :editor-box editor/box}
+                       :editor-box editor/box
+                       :page page}
         hiccup-config (common-handler/config-with-document-mode hiccup-config)
         hiccup-config (common-handler/config-with-document-mode hiccup-config)
         hiccup (block/->hiccup page-blocks hiccup-config {})]
         hiccup (block/->hiccup page-blocks hiccup-config {})]
-    [:div.page-blocks-inner
-     (when (and (seq grouped-blocks-by-file)
-                (> (count grouped-blocks-by-file) 1))
-       (ui/admonition
-        :warning
-        [:div.text-sm
-         [:p.font-medium "Those pages have the same title, you might want to only keep one file."]
-         [:ol
-          (for [[file-path blocks] (into (sorted-map) grouped-blocks-by-file)]
-            [:li [:a {:key file-path
-                      :href (rfe/href :file {:path file-path})} file-path]])]]))
-
-     (rum/with-key
-       (content/content page-name
-                        {:hiccup   hiccup
-                         :sidebar? sidebar?})
-       (str encoded-page-name "-hiccup"))]))
+    (page-blocks-inner page-name page-blocks hiccup sidebar?)))
 
 
 (defn contents-page
 (defn contents-page
-  [{:page/keys [name original-name file] :as contents}]
+  [{:block/keys [name original-name file] :as contents}]
   (when-let [repo (state/get-current-repo)]
   (when-let [repo (state/get-current-repo)]
     (let [format (db/get-page-format name)
     (let [format (db/get-page-format name)
           file-path (:file/path file)]
           file-path (:file/path file)]
       (page-blocks-cp repo contents file-path name original-name name true false false nil format))))
       (page-blocks-cp repo contents file-path name original-name name true false false nil format))))
 
 
-(defn presentation
-  [repo page]
-  [:a.opacity-30.hover:opacity-100.page-op
-   {:title "Presentation mode (Powered by Reveal.js)"
-    :style {:margin-top -2}
-    :on-click (fn []
-                (state/sidebar-add-block!
-                 repo
-                 (:db/id page)
-                 :page-presentation
-                 {:page page}))}
-   svg/slideshow])
-
 (rum/defc today-queries < rum/reactive
 (rum/defc today-queries < rum/reactive
   [repo today? sidebar?]
   [repo today? sidebar?]
   (when (and today? (not sidebar?))
   (when (and today? (not sidebar?))
@@ -127,9 +110,9 @@
         [:div#today-queries.mt-10
         [:div#today-queries.mt-10
          (for [{:keys [title] :as query} queries]
          (for [{:keys [title] :as query} queries]
            (rum/with-key
            (rum/with-key
-             (block/custom-query {:start-level 2
-                                  :attr {:class "mt-10"}
-                                  :editor-box editor/box} query)
+             (block/custom-query {:attr {:class "mt-10"}
+                                  :editor-box editor/box
+                                  :page page} query)
              (str repo "-custom-query-" (:query query))))]))))
              (str repo "-custom-query-" (:query query))))]))))
 
 
 (defn- delete-page!
 (defn- delete-page!
@@ -174,6 +157,7 @@
           (t :cancel)]]]])))
           (t :cancel)]]]])))
 
 
 (rum/defcs rename-page-dialog-inner <
 (rum/defcs rename-page-dialog-inner <
+  (shortcut/disable-all-shortcuts)
   (rum/local "" ::input)
   (rum/local "" ::input)
   [state title page-name close-fn]
   [state title page-name close-fn]
   (let [input (get state ::input)]
   (let [input (get state ::input)]
@@ -226,214 +210,198 @@
 
 
 ;; A page is just a logical block
 ;; A page is just a logical block
 (rum/defcs page < rum/reactive
 (rum/defcs page < rum/reactive
+  #_
   {:did-mount (fn [state]
   {:did-mount (fn [state]
                 (ui-handler/scroll-and-highlight! state)
                 (ui-handler/scroll-and-highlight! state)
                 state)
                 state)
    :did-update (fn [state]
    :did-update (fn [state]
                  (ui-handler/scroll-and-highlight! state)
                  (ui-handler/scroll-and-highlight! state)
                  state)}
                  state)}
-  [state {:keys [repo] :as option}]
-  (let [current-repo (state/sub :git/current-repo)
-        repo (or repo current-repo)
-        path-page-name (or (get-page-name state)
-                           (state/get-current-page))
-        page-name (string/lower-case path-page-name)
-        marker-page? (util/marker? page-name)
-        priority-page? (contains? #{"a" "b" "c"} page-name)
-        block? (util/uuid-string? page-name)
-        block-id (and block? (uuid page-name))
-        format (let [page (if block-id
-                            (:page/name (:block/page (db/entity [:block/uuid block-id])))
-                            page-name)]
-                 (db/get-page-format page))
-        journal? (db/journal-page? page-name)
-        sidebar? (:sidebar? option)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      (cond
-        priority-page?
-        [:div.page
-         [:h1.title
-          (t :page/priority (string/upper-case page-name))]
-         [:div.ml-2
-          (reference/references page-name false true)]]
-
-        marker-page?
-        [:div.page
-         [:h1.title
-          (string/upper-case page-name)]
-         [:div.ml-2
-          (reference/references page-name true false)]]
-
-        :else
-        (let [route-page-name page-name
-              page (if block?
-                     (->> (:db/id (:block/page (db/entity repo [:block/uuid block-id])))
-                          (db/entity repo))
-                     (db/entity repo [:page/name page-name]))
-              page (if page page (do
-                                   (db/transact! repo [{:page/name page-name
-                                                        :page/original-name path-page-name}])
-                                   (db/entity repo [:page/name page-name])))
-              {:keys [title] :as properties} (:page/properties page)
-              page-name (:page/name page)
-              page-original-name (:page/original-name page)
-              title (or title page-original-name page-name)
-              file (:page/file page)
-              file-path (and (:db/id file) (:file/path (db/entity repo (:db/id file))))
-              today? (and
-                      journal?
-                      (= page-name (string/lower-case (date/journal-name))))
-              developer-mode? (state/sub [:ui/developer-mode?])
-              published? (= "true" (:published properties))
-              public? (= "true" (:public properties))]
-          [:div.flex-1.page.relative (if (seq (:page/tags page))
-                                       (let [page-names (model/get-page-names-by-ids (map :db/id (:page/tags page)))]
-                                         {:data-page-tags (text/build-data-value page-names)})
-                                       {})
-           [:div.relative
-            (when (and (not block?)
-                       (not sidebar?)
-                       (not config/publishing?))
-
-              (let [contents? (= (string/lower-case (str page-name)) "contents")
-                    links (->>
-                           [(when-not contents?
-                              {:title (t :page/add-to-contents)
-                               :options {:on-click (fn [] (page-handler/handle-add-page-to-contents! page-original-name))}})
-
-                            (when-not contents?
-                              {:title (t :page/rename)
-                               :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}})
-
-                            (when (and file-path (util/electron?))
-                              [{:title   (t :page/open-in-finder)
-                                :options {:on-click #(js/window.apis.showItemInFolder file-path)}}
-                               {:title (t :page/open-with-default-app)
-                                :options {:on-click #(js/window.apis.openPath file-path)}}])
-
-                            (when-not contents?
-                              {:title (t :page/delete)
-                               :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}})
-
-                            (when (state/get-current-page)
-                              {:title (t :export)
-                               :options {:on-click #(state/set-modal! export/export-page)}})
-
-                            (when (util/electron?)
-                              {:title  (t (if public? :page/make-private :page/make-public))
-                               :options {:on-click
-                                         (fn []
-                                           (page-handler/update-public-attribute!
-                                            page-name
-                                            (if public? false true))
-                                           (state/close-modal!))}})
-
-                            (when file
-                              {:title (t :page/re-index)
-                               :options {:on-click (fn []
-                                                     (file/re-index! file))}})
-
-                            (when developer-mode?
-                              {:title "(Dev) Show page data"
-                               :options {:on-click (fn []
-                                                     (let [page-data (with-out-str (pprint/pprint (db/pull (:db/id page))))]
-                                                       (println page-data)
-                                                       (notification/show!
-                                                        [:div
-                                                         [:pre.code page-data]
-                                                         [:br]
-                                                         (ui/button "Copy to clipboard"
-                                                                    :on-click #(.writeText js/navigator.clipboard page-data))]
-                                                        :success
-                                                        false)))}})]
-                           (flatten)
-                           (remove nil?))]
-                [:div {:style {:position "absolute"
-                               :right 0
-                               :top 20}}
-                 [:div.flex.flex-row
-                  [:a.opacity-30.hover:opacity-100.page-op.mr-2
-                   {:title "Search in current page"
-                    :on-click #(route-handler/go-to-search! :page)}
-                   svg/search]
-                  (when (not config/mobile?)
-                    (presentation repo page))
-                  (when (seq links)
-                    (ui/dropdown-with-links
-                     (fn [{:keys [toggle-fn]}]
-                       [:a.opacity-30.hover:opacity-100
-                        {:title "More options"
-                         :on-click toggle-fn}
-                        (svg/vertical-dots {:class (util/hiccup->class "opacity-30.hover:opacity-100.h-5.w-5")})])
-                     links
-                     {:modal-class (util/hiccup->class
-                                    "origin-top-right.absolute.right-0.top-10.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.dropdown-overflow-auto.page-drop-options")
-                      :z-index 1}))]]))
-            (when (and (not sidebar?)
-                       (not block?))
-              [:a {:on-click (fn [e]
-                               (.preventDefault e)
-                               (when (gobj/get e "shiftKey")
-                                 (when-let [page (db/pull repo '[*] [:page/name page-name])]
-                                   (state/sidebar-add-block!
-                                    repo
-                                    (:db/id page)
-                                    :page
-                                    {:page page}))))}
-               [:h1.title {:style {:margin-left -2}}
-                (if page-original-name
-                  (if (and (string/includes? page-original-name "[[")
-                           (string/includes? page-original-name "]]"))
-                    (let [ast (mldoc/->edn page-original-name (mldoc/default-config format))]
-                      (block/markup-element-cp {} (ffirst ast)))
-                    page-original-name)
-                  (or
-                   page-name
-                   path-page-name))]])
-            [:div
-             [:div.content
-              (when (and file-path
-                         (not sidebar?)
-                         (not block?)
-                         (not (state/hide-file?))
-                         (not config/publishing?))
-                [:div.text-sm.ml-1.mb-4.flex-1.inline-flex
-                 {:key "page-file"}
-                 [:span.opacity-50 {:style {:margin-top 2}} (t :file/file)]
-                 [:a.bg-base-2.px-1.ml-1.mr-3 {:style {:border-radius 4
-                                                       :word-break    "break-word"}
-                                               :href  (rfe/href :file {:path file-path})}
-                  file-path]])]
-
-             (when (and repo (not block?))
-               (let [alias (db/get-page-alias-names repo page-name)]
-                 (when (seq alias)
-                   [:div.text-sm.ml-1.mb-4 {:key "page-file"}
-                    [:span.opacity-50 "Alias: "]
-                    (for [item alias]
-                      [:a.ml-1.mr-1 {:href (rfe/href :page {:name item})}
-                       item])])))
-
-             (when (and block? (not sidebar?))
-               (let [config {:id "block-parent"
-                             :block? true}]
-                 [:div.mb-4
-                  (block/block-parents config repo block-id format)]))
-
-             ;; blocks
-             (page-blocks-cp repo page file-path page-name page-original-name page-name sidebar? journal? block? block-id format)]]
-
-           (when-not block?
-             (today-queries repo today? sidebar?))
-
-           (tagged-pages repo page-name)
-
-           ;; referenced blocks
-           [:div {:key "page-references"}
-            (reference/references route-page-name false)]
-
-           [:div {:key "page-unlinked-references"}
-            (reference/unlinked-references route-page-name)]])))))
+  [state {:keys [repo page-name preview?] :as option}]
+  (when-let [path-page-name (or page-name
+                                (get-page-name state)
+                                (state/get-current-page))]
+    (let [current-repo (state/sub :git/current-repo)
+         repo (or repo current-repo)
+         page-name (string/lower-case path-page-name)
+         block? (util/uuid-string? page-name)
+         block-id (and block? (uuid page-name))
+         format (let [page (if block-id
+                             (:block/name (:block/page (db/entity [:block/uuid block-id])))
+                             page-name)]
+                  (db/get-page-format page))
+         journal? (db/journal-page? page-name)
+         sidebar? (:sidebar? option)]
+     (rum/with-context [[t] i18n/*tongue-context*]
+       (let [route-page-name path-page-name
+             page (if block?
+                    (->> (:db/id (:block/page (db/entity repo [:block/uuid block-id])))
+                         (db/entity repo))
+                    (db/entity repo [:block/name page-name]))
+             ;; TODO: replace page with frontend.format.block/page->map
+             page (if page page (do
+                                  (db/transact! repo [{:block/name page-name
+                                                       :block/original-name path-page-name
+                                                       :block/uuid (db/new-block-id)}])
+                                  (db/entity repo [:block/name page-name])))
+             {:keys [title] :as properties} (:block/properties page)
+             page-name (:block/name page)
+             page-original-name (:block/original-name page)
+             title (or title page-original-name page-name)
+             file (:block/file page)
+             file-path (and (:db/id file) (:file/path (db/entity repo (:db/id file))))
+             today? (and
+                     journal?
+                     (= page-name (string/lower-case (date/journal-name))))
+             developer-mode? (state/sub [:ui/developer-mode?])
+             public? (true? (:public properties))]
+         [:div.flex-1.page.relative (if (seq (:block/tags page))
+                                      (let [page-names (model/get-page-names-by-ids (map :db/id (:block/tags page)))]
+                                        {:data-page-tags (text/build-data-value page-names)})
+                                      {})
+          [:div.relative
+           (when (and (not sidebar?)
+                      (not block?))
+             [:div.flex.flex-row.space-between
+              [:div.flex-1.flex-row
+               [:a {:on-click (fn [e]
+                                (.preventDefault e)
+                                (when (gobj/get e "shiftKey")
+                                  (when-let [page (db/pull repo '[*] [:block/name page-name])]
+                                    (state/sidebar-add-block!
+                                     repo
+                                     (:db/id page)
+                                     :page
+                                     {:page page}))))}
+                [:h1.title {:style {:margin-left -2}}
+                 (if page-original-name
+                   (if (and (string/includes? page-original-name "[[")
+                            (string/includes? page-original-name "]]"))
+                     (let [ast (mldoc/->edn page-original-name (mldoc/default-config format))]
+                       (block/markup-element-cp {} (ffirst ast)))
+                     page-original-name)
+                   (or
+                    page-name
+                    path-page-name))]]]
+              (when (not config/publishing?)
+                (let [contents? (= (string/lower-case (str page-name)) "contents")
+                      links (fn [] (->>
+                                   [(when-not contents?
+                                      {:title   (t :page/add-to-contents)
+                                       :options {:on-click (fn [] (page-handler/handle-add-page-to-contents! page-original-name))}})
+
+                                    {:title "Go to presentation mode"
+                                     :options {:on-click (fn []
+                                                           (state/sidebar-add-block!
+                                                            repo
+                                                            (:db/id page)
+                                                            :page-presentation
+                                                            {:page page}))}}
+                                    (when-not contents?
+                                      {:title   (t :page/rename)
+                                       :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}})
+
+                                    (when-let [file-path (and (util/electron?) (page-handler/get-page-file-path))]
+                                      [{:title   (t :page/open-in-finder)
+                                        :options {:on-click #(js/window.apis.showItemInFolder file-path)}}
+                                       {:title   (t :page/open-with-default-app)
+                                        :options {:on-click #(js/window.apis.openPath file-path)}}])
+
+                                    (when-not contents?
+                                      {:title   (t :page/delete)
+                                       :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}})
+
+                                    (when (state/get-current-page)
+                                      {:title   (t :export)
+                                       :options {:on-click #(state/set-modal! export/export-page)}})
+
+                                    (when (util/electron?)
+                                      {:title   (t (if public? :page/make-private :page/make-public))
+                                       :options {:on-click
+                                                 (fn []
+                                                   (page-handler/update-public-attribute!
+                                                    page-name
+                                                    (if public? false true))
+                                                   (state/close-modal!))}})
+
+                                    (when developer-mode?
+                                      {:title   "(Dev) Show page data"
+                                       :options {:on-click (fn []
+                                                             (let [page-data (with-out-str (pprint/pprint (db/pull (:db/id page))))]
+                                                               (println page-data)
+                                                               (notification/show!
+                                                                [:div
+                                                                 [:pre.code page-data]
+                                                                 [:br]
+                                                                 (ui/button "Copy to clipboard"
+                                                                   :on-click #(.writeText js/navigator.clipboard page-data))]
+                                                                :success
+                                                                false)))}})]
+                                   (flatten)
+                                   (remove nil?)))]
+                  [:div.flex.flex-row
+                   [:a.opacity-30.hover:opacity-100.page-op.mr-1
+                    {:title "Search in current page"
+                     :on-click #(route-handler/go-to-search! :page)}
+                    svg/search]
+                   (ui/dropdown-with-links
+                    (fn [{:keys [toggle-fn]}]
+                      [:a.cp__vertial-menu-button
+                       {:title    "More options"
+                        :on-click toggle-fn}
+                       (svg/vertical-dots nil)])
+                    links
+                    {:modal-class (util/hiccup->class
+                                   "origin-top-right.absolute.right-0.top-10.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.dropdown-overflow-auto.page-drop-options")
+                     :z-index     1})]))])
+           [:div
+            ;; [:div.content
+            ;;  (when (and file-path
+            ;;             (not sidebar?)
+            ;;             (not block?)
+            ;;             (not (state/hide-file?))
+            ;;             (not config/publishing?))
+            ;;    [:div.text-sm.ml-1.mb-4.flex-1.inline-flex
+            ;;     {:key "page-file"}
+            ;;     [:span.opacity-50 {:style {:margin-top 2}} (t :file/file)]
+            ;;     [:a.bg-base-2.px-1.ml-1.mr-3 {:style {:border-radius 4
+            ;;                                           :word-break    "break-word"}
+            ;;                                   :href  (rfe/href :file {:path file-path})}
+            ;;      file-path]])]
+
+            (when (and repo (not block?))
+              (let [alias (db/get-page-alias-names repo page-name)]
+                (when (seq alias)
+                  [:div.text-sm.ml-1.mb-4 {:key "page-file"}
+                   [:span.opacity-50 "Alias: "]
+                   (for [item alias]
+                     [:a.ml-1.mr-1 {:href (rfe/href :page {:name item})}
+                      item])])))
+
+            (when (and block? (not sidebar?))
+              (let [config {:id "block-parent"
+                            :block? true}]
+                [:div.mb-4
+                 (block/block-parents config repo block-id format)]))
+
+            ;; blocks
+            (page-blocks-cp repo page file-path page-name page-original-name page-name sidebar? journal? block? block-id format)]]
+
+          (when-not block?
+            (today-queries repo today? sidebar?))
+
+          (tagged-pages repo page-name)
+
+          ;; referenced blocks
+          [:div {:key "page-references"}
+           (rum/with-key
+             (reference/references route-page-name false)
+             (str route-page-name "-refs"))]
+
+          ;; TODO: or we can lazy load them
+          (when-not sidebar?
+            [:div {:key "page-unlinked-references"}
+             (reference/unlinked-references route-page-name)])])))))
 
 
 (defonce layout (atom [js/window.outerWidth js/window.outerHeight]))
 (defonce layout (atom [js/window.outerWidth js/window.outerHeight]))
 
 
@@ -490,14 +458,14 @@
            [:table.table-auto
            [:table.table-auto
             [:thead
             [:thead
              [:tr
              [:tr
-              [:th (t :page/name)]
+              [:th (t :block/name)]
               [:th (t :file/last-modified-at)]]]
               [:th (t :file/last-modified-at)]]]
             [:tbody
             [:tbody
              (for [page pages]
              (for [page pages]
                [:tr {:key page}
                [:tr {:key page}
                 [:td [:a {:on-click (fn [e]
                 [:td [:a {:on-click (fn [e]
                                       (let [repo (state/get-current-repo)
                                       (let [repo (state/get-current-repo)
-                                            page (db/pull repo '[*] [:page/name (string/lower-case page)])]
+                                            page (db/pull repo '[*] [:block/name (string/lower-case page)])]
                                         (when (gobj/get e "shiftKey")
                                         (when (gobj/get e "shiftKey")
                                           (state/sidebar-add-block!
                                           (state/sidebar-add-block!
                                            repo
                                            repo

+ 14 - 0
src/main/frontend/components/page.css

@@ -28,3 +28,17 @@
     }
     }
   }
   }
 }
 }
+
+.cp__vertial-menu-button {
+    opacity: 0.3;
+    display: block;
+}
+
+.cp__vertial-menu-button:hover {
+    opacity: 1;
+}
+
+.cp__vertial-menu-button svg {
+    width: 20px;
+    height: 20px;
+}

+ 0 - 51
src/main/frontend/components/project.cljs

@@ -1,51 +0,0 @@
-(ns frontend.components.project
-  (:require [rum.core :as rum]
-            [frontend.util :as util :refer-macros [profile]]
-            [frontend.handler.project :as project-handler]
-            [frontend.handler.config :as config-handler]
-            [clojure.string :as string]))
-
-(rum/defcs add-project <
-  (rum/local "" ::project)
-  [state close-fn]
-  (let [project (get state ::project)]
-    [:div
-     [:div.sm:flex.sm:items-start
-      [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
-       [:svg.h-6.w-6.text-red-600
-        {:stroke "currentColor", :view-box "0 0 24 24", :fill "none"}
-        [:path
-         {:d
-          "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z",
-          :stroke-width "2",
-          :stroke-linejoin "round",
-          :stroke-linecap "round"}]]]
-      [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
-       [:h3#modal-headline.text-lg.leading-6.font-medium
-        "Setup a public project on Logseq"]
-       [:div.mt-2
-        [:p.text-sm.leading-5.text-gray-500
-         "All published pages will be located under "
-         [:b "/project/"]
-         "."]]]]
-
-     [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
-      {:auto-focus true
-       :on-change (fn [e]
-                    (reset! project (util/evalue e)))}]
-
-     [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
-      [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto
-       [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
-        {:type "button"
-         :on-click (fn []
-                     (let [value @project]
-                       (when (and value (>= (count value) 2))
-                         (project-handler/add-project! value
-                                                       config-handler/set-project!))))}
-        "Submit"]]
-      [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto
-       [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
-        {:type "button"
-         :on-click close-fn}
-        "Cancel"]]]]))

+ 0 - 136
src/main/frontend/components/publishing.cljs

@@ -1,136 +0,0 @@
-(ns frontend.components.publishing
-  (:require [rum.core :as rum]
-            [frontend.context.i18n :as i18n]
-            [frontend.db :as db]
-            [frontend.state :as state]
-            [frontend.util :as util]
-            [reitit.frontend.easy :as rfe]
-            [frontend.db-mixins :as db-mixins]
-            [frontend.config :as config]
-            [lambdaisland.glogi :as log]
-            [promesa.core :as p]
-            [frontend.handler.page :as page-handler]
-            [frontend.handler.notification :as notification]
-            [frontend.ui :as ui]
-            [frontend.components.svg :as svg]
-            [frontend.handler.project :as project-handler]))
-
-
-(rum/defcs project
-  < (rum/local :display ::project-state)
-  [state current-project pages]
-  (let [editor-state (get state ::project-state)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      (if (= :display @editor-state)
-        (when current-project
-          [:div.cp__publishing-pj
-           [:span.cp__publishing-pj-name current-project]
-           [:a.cp__publishing-edit
-            {:on-click
-             (fn [_]
-               (reset! editor-state :editor))}
-            (t :publishing/edit)]])
-        [:div.flex.cp__publishing_pj_edit
-         [:input#cp__publishing-project-input
-          {:placeholder current-project
-           :auto-focus true
-           :default-value current-project}]
-         [:div.cp__publishing-pj-bt
-          (ui/button
-            (t :publishing/save)
-            :on-click (fn [e]
-                        (util/stop e)
-                        (let [editor (.getElementById js/document "cp__publishing-project-input")
-                              v (.-value editor)
-                              data {:name v}]
-                          (-> (p/let [result (project-handler/update-project current-project data)]
-                                (when (:result result)
-                                  (state/update-current-project :name v)
-                                  (notification/show! "Updated project name successfully." :success)
-                                  (reset! editor-state :display)))
-                              (p/catch
-                                (fn [error]
-                                  (notification/show! "Failed to update project name." :failed))))))
-            :background "green")]
-
-         [:div.cp__publishing-pj-bt
-          (ui/button
-            (t :publishing/delete)
-            :on-click (fn [e]
-                        (util/stop e)
-                        (let [confirm-message
-                              (util/format
-                                "This operation will delete all the published pages under the project \"%s\", are you sure?"
-                                current-project)]
-                          (when (.confirm js/window confirm-message)
-                            (p/let [result (project-handler/delete-project current-project)]
-                              (when (:result result)
-                                (reset! editor-state :display)
-                                (state/remove-current-project)
-                                (state/reset-published-pages)
-                                (doseq [{:keys [title]} pages]
-                                  (page-handler/page-add-properties! title {:published false}))
-                                (notification/show! "The project was deleted successfully." :success))))))
-            :background "red")]
-
-         [:div.cp__publishing-pj-bt
-          (ui/button
-            (t :publishing/cancel)
-            :on-click (fn [e]
-                        (util/stop e)
-                        (reset! editor-state :display))
-            :background "pink")]]))))
-
-(rum/defc my-publishing
-  < rum/reactive db-mixins/query
-    (rum/local :display ::project-state)
-  []
-  (let [current-repo (state/sub :git/current-repo)
-        projects (state/sub [:me :projects])
-        current-project (project-handler/get-current-project current-repo projects)]
-    (when current-repo
-      (p/let [_ (page-handler/get-page-list-by-project-name current-project)]
-        (let [publishing-pages (state/sub [:me :published-pages])
-              pages (get publishing-pages current-repo)]
-          (rum/with-context [[t] i18n/*tongue-context*]
-            [:div.flex-1
-             [:h1.title (t :my-publishing)]
-             [:div#cp__publishing-pj-ct
-              [:span (t :publishing/current-project)]
-              (project current-project pages)]
-             [:div#cp__publishing-pg-ct
-              [:div (t :publishing/pages)]
-              [:table.table-auto
-               [:thead
-                [:tr
-                 [:th (t :publishing/page-name)]
-                 [:th (t :publishing/delete-from-logseq)]]]
-               [:tbody
-                (for [{:keys [title permalink]} pages]
-                  [:tr {:key permalink}
-                   [:td [:div.flex {}
-                         [:span [:a {:on-click (fn [e] (util/stop e))
-                                     :href (rfe/href :page {:name title})}
-                                 title]]
-                         [:span [:a {:href (util/format "%s/%s/%s" config/website current-project title)
-                                     :target "_blank"}
-                                 svg/external-link]]]
-                    ]
-                   [:td [:span.text-gray-500.text-sm
-                         [:a {:on-click
-                              (fn [e]
-                                (util/stop e)
-                                (-> (p/let [_ (page-handler/delete-page-from-logseq current-project permalink)]
-                                      (page-handler/update-state-and-notify title))
-                                    (p/catch
-                                      (fn [error]
-                                        (let [status (.-status error)
-                                              not-found-on-server 404]
-                                          (if (= not-found-on-server status)
-                                            (page-handler/update-state-and-notify title)
-                                            (let [message (util/format "Failed to remove the page \"%s\" from Logseq"
-                                                            title)]
-                                              (notification/show! message :failed))))))))}
-                          (t :publishing/delete)]]]])]]]]))))))
-
-

+ 0 - 49
src/main/frontend/components/publishing.css

@@ -1,49 +0,0 @@
-#cp__publishing-pj-ct{
- padding: 10px;
- background-color: var(--ls-quaternary-background-color);
-}
-
-.cp__publishing-pj{
- padding: 5px;
- margin: 5px;
-}
-
-.cp__publishing-pj-name {
- font-size: 25px;
- color: var(--ls-primary-text-color);
-}
-
-.cp__publishing-edit{
-  font-size: 20px;
-  margin-left: 40px;
-  opacity: .5;
-}
-
-.cp__publishing-edit:hover{
-  font-size: 20px;
-  margin-left: 40px;
-  opacity: .6;
-}
-
-.cp__publishing_pj_edit {
-  padding: 5px;
-  margin: 5px;
-}
-
-.cp__publishing-pj-bt {
-  margin: 5px 5px;
-}
-
-#cp__publishing-project-input{
-  border-width: thin;
-  padding: 0 10px;
-  display: inline-block;
-  margin: 5px;
-  border-radius: var(--ls-border-radius-low);
-  border-color: var(--ls-border-color);
-}
-
-#cp__publishing-pg-ct {
-  margin-top: 30px;
-  padding: 10px;
-}

+ 83 - 71
src/main/frontend/components/reference.cljs

@@ -18,70 +18,78 @@
             [medley.core :as medley]))
             [medley.core :as medley]))
 
 
 (rum/defc filter-dialog-inner < rum/reactive
 (rum/defc filter-dialog-inner < rum/reactive
-  [close-fn references page-name]
-  (let [filter-state (page-handler/get-filter page-name)]
-    [:div.filters
-     [:div.sm:flex.sm:items-start
-      [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-gray-200.text-gray-500.sm:mx-0.sm:h-10.sm:w-10
-       (svg/filter-icon)]
-      [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
-       [:h3#modal-headline.text-lg.leading-6.font-medium "Filter"]
-       [:span.text-xs
-        "Click to include and shift-click to exclude. Click again to remove."]]]
-     (when (seq references)
+  [filters-atom close-fn references page-name]
+  [:div.filters
+   [:div.sm:flex.sm:items-start
+    [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-gray-200.text-gray-500.sm:mx-0.sm:h-10.sm:w-10
+     (svg/filter-icon)]
+    [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
+     [:h3#modal-headline.text-lg.leading-6.font-medium "Filter"]
+     [:span.text-xs
+      "Click to include and shift-click to exclude. Click again to remove."]]]
+   (when (seq references)
+     (let [filters (rum/react filters-atom)]
        [:div.mt-5.sm:mt-4.sm:flex.sm.gap-1.flex-wrap
        [:div.mt-5.sm:mt-4.sm:flex.sm.gap-1.flex-wrap
-        (for [reference references]
-          (let [filtered (get (rum/react filter-state) reference)
-                color (condp = filtered
-                        true "text-green-400"
-                        false "text-red-400"
-                        nil)]
-            [:button.border.rounded.px-1.mb-1 {:key reference :class color :style {:border-color "currentColor"}
-                                               :on-click (fn [e]
-                                                           (swap! filter-state #(if (nil? (get @filter-state reference))
-                                                                                  (assoc % reference (not (.-shiftKey e)))
-                                                                                  (dissoc % reference)))
-                                                           (page-handler/save-filter! page-name @filter-state))}
-             reference]))])]))
+       (for [reference references]
+         (let [lc-reference (string/lower-case reference)
+               filtered (get filters lc-reference)
+               color (condp = filtered
+                       true "text-green-400"
+                       false "text-red-400"
+                       nil)]
+           [:button.border.rounded.px-1.mb-1.mr-1 {:key reference :class color :style {:border-color "currentColor"}
+                                                   :on-click (fn [e]
+                                                               (swap! filters-atom #(if (nil? (get filters lc-reference))
+                                                                                      (assoc % lc-reference (not (.-shiftKey e)))
+                                                                                      (dissoc % lc-reference)))
+                                                               (page-handler/save-filter! page-name @filters-atom))}
+            reference]))]))])
 
 
 (defn filter-dialog
 (defn filter-dialog
-  [references page-name]
+  [filters-atom references page-name]
   (fn [close-fn]
   (fn [close-fn]
-    (filter-dialog-inner close-fn references page-name)))
+    (filter-dialog-inner filters-atom close-fn references page-name)))
 
 
-(rum/defc references < rum/reactive
-  [page-name marker? priority?]
+(rum/defcs references < rum/reactive
+  {:init (fn [state]
+           (let [page-name (first (:rum/args state))
+                 filters (when page-name
+                           (atom (page-handler/get-filters (string/lower-case page-name))))]
+             (assoc state ::filters filters)))}
+  [state page-name marker? priority?]
   (when page-name
   (when page-name
-    (let [block? (util/uuid-string? page-name)
+    (let [filters-atom (get state ::filters)
+          block? (util/uuid-string? page-name)
           block-id (and block? (uuid page-name))
           block-id (and block? (uuid page-name))
           page-name (string/lower-case page-name)
           page-name (string/lower-case page-name)
           journal? (date/valid-journal-title? (string/capitalize page-name))
           journal? (date/valid-journal-title? (string/capitalize page-name))
           repo (state/get-current-repo)
           repo (state/get-current-repo)
           ref-blocks (cond
           ref-blocks (cond
-                       priority?
-                       (db/get-blocks-by-priority repo page-name)
-
-                       marker?
-                       (db/get-marker-blocks repo page-name)
                        block-id
                        block-id
                        (db/get-block-referenced-blocks block-id)
                        (db/get-block-referenced-blocks block-id)
                        :else
                        :else
                        (db/get-page-referenced-blocks page-name))
                        (db/get-page-referenced-blocks page-name))
+          ref-pages (map (comp :block/original-name first) ref-blocks)
           scheduled-or-deadlines (if (and journal?
           scheduled-or-deadlines (if (and journal?
                                           (not (true? (state/scheduled-deadlines-disabled?)))
                                           (not (true? (state/scheduled-deadlines-disabled?)))
                                           (= page-name (string/lower-case (date/journal-name))))
                                           (= page-name (string/lower-case (date/journal-name))))
                                    (db/get-date-scheduled-or-deadlines (string/capitalize page-name))
                                    (db/get-date-scheduled-or-deadlines (string/capitalize page-name))
                                    nil)
                                    nil)
           references (db/get-page-linked-refs-refed-pages repo page-name)
           references (db/get-page-linked-refs-refed-pages repo page-name)
-          filter-state (rum/react (page-handler/get-filter page-name))
-          included (filter (fn [[x v]]) filter-state)
+          references (->> (concat ref-pages references)
+                          (remove nil?)
+                          (distinct))
+          filter-state (rum/react filters-atom)
           filters (when (seq filter-state)
           filters (when (seq filter-state)
                     (->> (group-by second filter-state)
                     (->> (group-by second filter-state)
                          (medley/map-vals #(map first %))))
                          (medley/map-vals #(map first %))))
           filtered-ref-blocks (block-handler/filter-blocks repo ref-blocks filters true)
           filtered-ref-blocks (block-handler/filter-blocks repo ref-blocks filters true)
-          n-ref (count filtered-ref-blocks)]
+          n-ref (apply +
+                 (for [[_ rfs] filtered-ref-blocks]
+                   (count rfs)))]
       (when (or (> n-ref 0)
       (when (or (> n-ref 0)
-                (seq scheduled-or-deadlines))
+                (seq scheduled-or-deadlines)
+                (seq filter-state))
         [:div.references.mt-6.flex-1.flex-row
         [:div.references.mt-6.flex-1.flex-row
          [:div.content
          [:div.content
           (when (seq scheduled-or-deadlines)
           (when (seq scheduled-or-deadlines)
@@ -90,7 +98,6 @@
              [:div.references-blocks.mb-6
              [:div.references-blocks.mb-6
               (let [ref-hiccup (block/->hiccup scheduled-or-deadlines
               (let [ref-hiccup (block/->hiccup scheduled-or-deadlines
                                                {:id (str page-name "-agenda")
                                                {:id (str page-name "-agenda")
-                                                :start-level 2
                                                 :ref? true
                                                 :ref? true
                                                 :group-by-page? true
                                                 :group-by-page? true
                                                 :editor-box editor/box}
                                                 :editor-box editor/box}
@@ -98,46 +105,51 @@
                 (content/content page-name
                 (content/content page-name
                                  {:hiccup ref-hiccup}))]))
                                  {:hiccup ref-hiccup}))]))
 
 
-          (ui/foldable
-           [:div.flex.flex-row.flex-1.justify-between
-            [:h2.font-bold.opacity-50 (let []
-                                        (str n-ref " Linked Reference"
-                                             (if (> n-ref 1) "s")))]
-            [:a.opacity-50.hover:opacity-100
-             {:title "Filter"
-              :on-click #(state/set-modal! (filter-dialog references page-name))}
-             (svg/filter-icon (cond
-                                (empty? filter-state) nil
-                                (every? true? (vals filter-state)) "text-green-400"
-                                (every? false? (vals filter-state)) "text-red-400"
-                                :else "text-yellow-400"))]]
+          (when (or (> n-ref 0)
+                    (seq filter-state))
+            (ui/foldable
+             [:div.flex.flex-row.flex-1.justify-between
+              [:h2.font-bold.opacity-50 (let []
+                                          (str n-ref " Linked Reference"
+                                               (if (> n-ref 1) "s")))]
+              [:a.opacity-50.hover:opacity-100
+               {:title "Filter"
+                :on-click #(state/set-modal! (filter-dialog filters-atom references page-name))}
+               (svg/filter-icon (cond
+                                  (empty? filter-state) nil
+                                  (every? true? (vals filter-state)) "text-green-400"
+                                  (every? false? (vals filter-state)) "text-red-400"
+                                  :else "text-yellow-400"))]]
 
 
-           [:div.references-blocks
-            (let [ref-hiccup (block/->hiccup filtered-ref-blocks
-                                             {:id page-name
-                                              :start-level 2
-                                              :ref? true
-                                              :breadcrumb-show? true
-                                              :group-by-page? true
-                                              :editor-box editor/box
-                                              :filters filters}
-                                             {})]
-              (content/content page-name
-                               {:hiccup ref-hiccup}))])]]))))
+             [:div.references-blocks
+              (let [ref-hiccup (block/->hiccup filtered-ref-blocks
+                                               {:id page-name
+                                                :ref? true
+                                                :breadcrumb-show? true
+                                                :group-by-page? true
+                                                :editor-box editor/box
+                                                :filters filters}
+                                               {})]
+                (content/content page-name
+                                 {:hiccup ref-hiccup}))]))]]))))
 
 
 (rum/defcs unlinked-references-aux
 (rum/defcs unlinked-references-aux
   < rum/reactive db-mixins/query
   < rum/reactive db-mixins/query
-  {:will-mount (fn [state]
-                 (let [[page-name n-ref] (:rum/args state)
-                       ref-blocks (db/get-page-unlinked-references page-name)]
-                   (reset! n-ref (count ref-blocks))
-                   (assoc state ::ref-blocks ref-blocks)))}
+  {:wrap-render
+   (fn [render-fn]
+     (fn [state]
+       (reset! (second (:rum/args state))
+               (apply +
+                      (for [[_ rfs]
+                            (db/get-page-unlinked-references
+                             (first (:rum/args state)))]
+                        (count rfs))))
+       (render-fn state)))}
   [state page-name n-ref]
   [state page-name n-ref]
-  (let [ref-blocks (::ref-blocks state)]
+  (let [ref-blocks (db/get-page-unlinked-references page-name)]
     [:div.references-blocks
     [:div.references-blocks
      (let [ref-hiccup (block/->hiccup ref-blocks
      (let [ref-hiccup (block/->hiccup ref-blocks
                                       {:id (str page-name "-unlinked-")
                                       {:id (str page-name "-unlinked-")
-                                       :start-level 2
                                        :ref? true
                                        :ref? true
                                        :group-by-page? true
                                        :group-by-page? true
                                        :editor-box editor/box}
                                        :editor-box editor/box}

+ 8 - 7
src/main/frontend/components/right_sidebar.cljs

@@ -67,6 +67,7 @@
 (defn recent-pages
 (defn recent-pages
   []
   []
   (let [pages (->> (db/get-key-value :recent/pages)
   (let [pages (->> (db/get-key-value :recent/pages)
+                   (remove nil?)
                    (remove #(= (string/lower-case %) "contents")))]
                    (remove #(= (string/lower-case %) "contents")))]
     [:div.recent-pages.text-sm.flex-col.flex.ml-3.mt-2
     [:div.recent-pages.text-sm.flex-col.flex.ml-3.mt-2
      (if (seq pages)
      (if (seq pages)
@@ -75,7 +76,7 @@
                    :href     (rfe/href :page {:name page})
                    :href     (rfe/href :page {:name page})
                    :on-click (fn [e]
                    :on-click (fn [e]
                                (when (gobj/get e "shiftKey")
                                (when (gobj/get e "shiftKey")
-                                 (when-let [page (db/pull [:page/name (string/lower-case page)])]
+                                 (when-let [page (db/pull [:block/name (string/lower-case page)])]
                                    (state/sidebar-add-block!
                                    (state/sidebar-add-block!
                                     (state/get-current-repo)
                                     (state/get-current-repo)
                                     (:db/id page)
                                     (:db/id page)
@@ -87,7 +88,7 @@
 (rum/defc contents < rum/reactive db-mixins/query
 (rum/defc contents < rum/reactive db-mixins/query
   []
   []
   [:div.contents.flex-col.flex.ml-3
   [:div.contents.flex-col.flex.ml-3
-   (when-let [contents (db/entity [:page/name "contents"])]
+   (when-let [contents (db/entity [:block/name "contents"])]
      (page/contents-page contents))])
      (page/contents-page contents))])
 
 
 (defn build-sidebar-item
 (defn build-sidebar-item
@@ -96,7 +97,7 @@
     :contents
     :contents
     [[:a {:on-click (fn [e]
     [[:a {:on-click (fn [e]
                       (util/stop e)
                       (util/stop e)
-                      (if-not (db/entity [:page/name "contents"])
+                      (if-not (db/entity [:block/name "contents"])
                         (page-handler/create! "contents")
                         (page-handler/create! "contents")
                         (route-handler/redirect! {:to          :page
                         (route-handler/redirect! {:to          :page
                                                   :path-params {:name "contents"}})))}
                                                   :path-params {:name "contents"}})))}
@@ -135,7 +136,7 @@
           (block-cp repo idx block-data)]]))
           (block-cp repo idx block-data)]]))
 
 
     :page
     :page
-    (let [page-name (:page/name block-data)]
+    (let [page-name (:block/name block-data)]
       [[:a {:href     (rfe/href :page {:name page-name})
       [[:a {:href     (rfe/href :page {:name page-name})
             :on-click (fn [e]
             :on-click (fn [e]
                         (when (gobj/get e "shiftKey")
                         (when (gobj/get e "shiftKey")
@@ -145,14 +146,13 @@
         (page-cp repo page-name)]])
         (page-cp repo page-name)]])
 
 
     :page-presentation
     :page-presentation
-    (let [page-name (get-in block-data [:page :page/name])
+    (let [page-name (get-in block-data [:page :block/name])
           journal? (:journal? block-data)
           journal? (:journal? block-data)
           blocks (db/get-page-blocks repo page-name)
           blocks (db/get-page-blocks repo page-name)
           blocks (if journal?
           blocks (if journal?
                    (rest blocks)
                    (rest blocks)
                    blocks)
                    blocks)
           sections (block/build-slide-sections blocks {:id          "slide-reveal-js"
           sections (block/build-slide-sections blocks {:id          "slide-reveal-js"
-                                                       :start-level 2
                                                        :slide?      true
                                                        :slide?      true
                                                        :sidebar?    true
                                                        :sidebar?    true
                                                        :page-name   page-name})]
                                                        :page-name   page-name})]
@@ -297,7 +297,8 @@
               (t :right-side-bar/help)]]]
               (t :right-side-bar/help)]]]
 
 
            (when sidebar-open?
            (when sidebar-open?
-             [:div.mr-1.flex.align-items {:style {:z-index 999}}
+             [:div.flex.align-items {:style {:z-index 999
+                                             :margin-right 2}}
               (toggle)])]
               (toggle)])]
 
 
            [:.sidebar-item-list.flex-1
            [:.sidebar-item-list.flex-1

+ 17 - 24
src/main/frontend/components/search.cljs

@@ -5,7 +5,6 @@
             [frontend.components.svg :as svg]
             [frontend.components.svg :as svg]
             [frontend.handler.route :as route]
             [frontend.handler.route :as route]
             [frontend.handler.page :as page-handler]
             [frontend.handler.page :as page-handler]
-            [frontend.handler.file :as file-handler]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.handler.search :as search-handler]
             [frontend.handler.search :as search-handler]
             [frontend.ui :as ui]
             [frontend.ui :as ui]
@@ -141,10 +140,7 @@
 (rum/defc search-auto-complete
 (rum/defc search-auto-complete
   [{:keys [pages files blocks has-more?] :as result} search-q all?]
   [{:keys [pages files blocks has-more?] :as result} search-q all?]
   (rum/with-context [[t] i18n/*tongue-context*]
   (rum/with-context [[t] i18n/*tongue-context*]
-    (let [new-file (when-let [ext (util/get-file-ext search-q)]
-                     (when (contains? config/mldoc-support-formats (keyword (string/lower-case ext)))
-                       [{:type :new-file}]))
-          pages (when-not all? (map (fn [page] {:type :page :data page}) pages))
+    (let [pages (when-not all? (map (fn [page] {:type :page :data page}) pages))
           files (when-not all? (map (fn [file] {:type :file :data file}) files))
           files (when-not all? (map (fn [file] {:type :file :data file}) files))
           blocks (map (fn [block] {:type :block :data block}) blocks)
           blocks (map (fn [block] {:type :block :data block}) blocks)
           search-mode (state/get-search-mode)
           search-mode (state/get-search-mode)
@@ -153,13 +149,12 @@
                              (= (string/lower-case search-q)
                              (= (string/lower-case search-q)
                                 (string/lower-case (:data (first pages)))))
                                 (string/lower-case (:data (first pages)))))
                         (nil? result)
                         (nil? result)
-                        (not= :global search-mode)
                         all?)
                         all?)
                      []
                      []
                      [{:type :new-page}])
                      [{:type :new-page}])
           result (if config/publishing?
           result (if config/publishing?
                    (concat pages files blocks)
                    (concat pages files blocks)
-                   (concat new-page pages new-file files blocks))]
+                   (concat new-page pages files blocks))]
       [:div.rounded-md.shadow-lg
       [:div.rounded-md.shadow-lg
        {:style (merge
        {:style (merge
                 {:top 48
                 {:top 48
@@ -180,24 +175,25 @@
                         (route/redirect! {:to :page
                         (route/redirect! {:to :page
                                           :path-params {:name data}})
                                           :path-params {:name data}})
 
 
-                        :new-file
-                        (file-handler/create! search-q)
-
                         :file
                         :file
                         (route/redirect! {:to :file
                         (route/redirect! {:to :file
                                           :path-params {:path data}})
                                           :path-params {:path data}})
 
 
                         :block
                         :block
                         (let [block-uuid (uuid (:block/uuid data))
                         (let [block-uuid (uuid (:block/uuid data))
-                              page (:page/name (:block/page (db/entity [:block/uuid block-uuid])))]
-                          (route/redirect! {:to :page
-                                            :path-params {:name page}
-                                            :query-params {:anchor (str "ls-block-" (:block/uuid data))}}))
+                              collapsed? (db/parents-collapsed? (state/get-current-repo) block-uuid)]
+                          (if collapsed?
+                            (route/redirect! {:to :page
+                                              :path-params {:name (str block-uuid)}})
+                            (let [page (:block/name (:block/page (db/entity [:block/uuid block-uuid])))]
+                             (route/redirect! {:to :page
+                                               :path-params {:name page}
+                                               :query-params {:anchor (str "ls-block-" (:block/uuid data))}}))))
                         nil))
                         nil))
          :on-shift-chosen (fn [{:keys [type data]}]
          :on-shift-chosen (fn [{:keys [type data]}]
                             (case type
                             (case type
                               :page
                               :page
-                              (let [page (db/entity [:page/name (string/lower-case data)])]
+                              (let [page (db/entity [:block/name (string/lower-case data)])]
                                 (state/sidebar-add-block!
                                 (state/sidebar-add-block!
                                  (state/get-current-repo)
                                  (state/get-current-repo)
                                  (:db/id page)
                                  (:db/id page)
@@ -213,7 +209,8 @@
                                  :block
                                  :block
                                  block))
                                  block))
 
 
-                              nil))
+                              nil)
+                            (search-handler/clear-search!))
          :item-render (fn [{:keys [type data]}]
          :item-render (fn [{:keys [type data]}]
                         (let [search-mode (state/get-search-mode)]
                         (let [search-mode (state/get-search-mode)]
                           [:div {:class "py-2"} (case type
                           [:div {:class "py-2"} (case type
@@ -221,10 +218,6 @@
                                                   [:div.text.font-bold (str (t :new-page) ": ")
                                                   [:div.text.font-bold (str (t :new-page) ": ")
                                                    [:span.ml-1 (str "\"" search-q "\"")]]
                                                    [:span.ml-1 (str "\"" search-q "\"")]]
 
 
-                                                  :new-file
-                                                  [:div.text.font-bold (str (t :new-file) ": ")
-                                                   [:span.ml-1 (str "\"" search-q "\"")]]
-
                                                   :page
                                                   :page
                                                   (search-result-item "Page" (highlight-exact-query data search-q))
                                                   (search-result-item "Page" (highlight-exact-query data search-q))
 
 
@@ -233,8 +226,8 @@
 
 
                                                   :block
                                                   :block
                                                   (let [{:block/keys [page content uuid]} data
                                                   (let [{:block/keys [page content uuid]} data
-                                                        page (or (:page/original-name page)
-                                                                 (:page/name page))
+                                                        page (or (:block/original-name page)
+                                                                 (:block/name page))
                                                         repo (state/sub :git/current-repo)
                                                         repo (state/sub :git/current-repo)
                                                         format (db/get-page-format page)]
                                                         format (db/get-page-format page)]
                                                     (search-result-item "Block" (block-search-result-item repo uuid format content search-q search-mode)))
                                                     (search-result-item "Block" (block-search-result-item repo uuid format content search-q search-mode)))
@@ -293,12 +286,12 @@
                           (js/clearTimeout @search-timeout))
                           (js/clearTimeout @search-timeout))
                         (let [value (util/evalue e)]
                         (let [value (util/evalue e)]
                           (if (string/blank? value)
                           (if (string/blank? value)
-                            (search-handler/clear-search!)
+                            (search-handler/clear-search! false)
                             (let [search-mode (state/get-search-mode)
                             (let [search-mode (state/get-search-mode)
                                   opts (if (= :page search-mode)
                                   opts (if (= :page search-mode)
                                          (let [current-page (or (state/get-current-page)
                                          (let [current-page (or (state/get-current-page)
                                                                 (date/today))]
                                                                 (date/today))]
-                                           {:page-db-id (:db/id (db/entity [:page/name (string/lower-case current-page)]))})
+                                           {:page-db-id (:db/id (db/entity [:block/name (string/lower-case current-page)]))})
                                          {})]
                                          {})]
                               (state/set-q! value)
                               (state/set-q! value)
                               (reset! search-timeout
                               (reset! search-timeout

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

@@ -8,6 +8,7 @@
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.config :as config-handler]
             [frontend.handler.config :as config-handler]
             [frontend.handler.page :as page-handler]
             [frontend.handler.page :as page-handler]
+            [frontend.handler :as handler]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.version :refer [version]]
             [frontend.version :refer [version]]
             [frontend.util :as util]
             [frontend.util :as util]
@@ -134,6 +135,15 @@
          :on-click close-fn}
          :on-click close-fn}
         "Cancel"]]]]))
         "Cancel"]]]]))
 
 
+(rum/defc outdenting-hint
+  []
+  [:div
+   [:p "See more details at " [:a {:href "https://discuss.logseq.com/t/whats-your-preferred-outdent-behavior-the-direct-one-or-the-logical-one/978"} "here"] "."]
+   [:p "default(left) vs logical(right)"]
+   [:img {:src "https://discuss.logseq.com/uploads/default/original/1X/e8ea82f63a5e01f6d21b5da827927f538f3277b9.gif"
+          :width 500
+          :height 500}]])
+
 (rum/defcs settings < rum/reactive
 (rum/defcs settings < rum/reactive
   []
   []
   (let [preferred-format (state/get-preferred-format)
   (let [preferred-format (state/get-preferred-format)
@@ -144,6 +154,8 @@
         current-repo (state/get-current-repo)
         current-repo (state/get-current-repo)
         enable-journals? (state/enable-journals? current-repo)
         enable-journals? (state/enable-journals? current-repo)
         enable-encryption? (state/enable-encryption? current-repo)
         enable-encryption? (state/enable-encryption? current-repo)
+        sentry-disabled? (state/sub :sentry/disabled?)
+        logical-outdenting? (state/logical-outdenting?)
         enable-git-auto-push? (state/enable-git-auto-push? current-repo)
         enable-git-auto-push? (state/enable-git-auto-push? current-repo)
         enable-block-time? (state/enable-block-time?)
         enable-block-time? (state/enable-block-time?)
         show-brackets? (state/show-brackets?)
         show-brackets? (state/show-brackets?)
@@ -271,6 +283,15 @@
                  "NOW/LATER"
                  "NOW/LATER"
                  "TODO/DOING")])]]]]
                  "TODO/DOING")])]]]]
 
 
+        (toggle "preferred_outdenting"
+                (ui/tippy {:html (outdenting-hint)
+                           :interactive true
+                           :theme "customized"}
+                          (t :settings-page/preferred-outdenting))
+                logical-outdenting?
+                (fn []
+                  (config-handler/toggle-logical-outdenting!)))
+
         (toggle "enable_timetracking"
         (toggle "enable_timetracking"
                 (t :settings-page/enable-timetracking)
                 (t :settings-page/enable-timetracking)
                 enable-timetracking?
                 enable-timetracking?
@@ -336,6 +357,18 @@
 
 
        [:hr]
        [:hr]
 
 
+       [:div.panel-wrap
+
+        [:div.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center.sm:pt-5
+         [:label.block.text-sm.font-medium.leading-5.opacity-70
+          {:for "clear_cache"}
+          (t :settings-page/clear-cache)]
+         [:div.mt-1.sm:mt-0.sm:col-span-2
+          [:div.max-w-lg.rounded-md.sm:max-w-xs
+           (ui/button (t :settings-page/clear)
+             :on-click (fn []
+                         (handler/clear-cache!)))]]]]
+
        [:div.panel-wrap
        [:div.panel-wrap
         [:div.it.app-updater.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
         [:div.it.app-updater.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
          [:label.block.text-sm.font-medium.leading-5.opacity-70
          [:label.block.text-sm.font-medium.leading-5.opacity-70
@@ -344,6 +377,13 @@
           [:div.ver version]
           [:div.ver version]
           (if (util/electron?) (app-updater))]]
           (if (util/electron?) (app-updater))]]
 
 
+        (toggle "disable_sentry"
+                (t :settings-page/disable-sentry)
+                sentry-disabled?
+                (fn []
+                  (let [value (not sentry-disabled?)]
+                    (state/set-sentry-disabled! value))))
+
         [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
         [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
          [:label.block.text-sm.font-medium.leading-5.opacity-70
          [:label.block.text-sm.font-medium.leading-5.opacity-70
           {:for "developer_mode"}
           {:for "developer_mode"}

+ 65 - 0
src/main/frontend/components/shortcut.cljs

@@ -0,0 +1,65 @@
+(ns frontend.components.shortcut
+  (:require [frontend.context.i18n :as i18n]
+            [frontend.modules.shortcut.data-helper :as dh]
+            [frontend.state :as state]
+            [rum.core :as rum]))
+(def *shortcut-config (rum/cursor-in state/state [:config (state/get-current-repo) :shortcuts]))
+
+(rum/defc shortcut-table < rum/reactive
+  [name]
+  (let [_ (rum/react *shortcut-config)]
+    (rum/with-context [[t] i18n/*tongue-context*]
+      [:div
+       [:table
+        [:thead
+         [:tr
+          [:th.text-left [:b (t name)]]
+          [:th.text-right [:b (t :help/shortcut)]]]]
+        [:tbody
+         (map (fn [[k {:keys [binding]}]]
+                [:tr {:key k}
+                 [:td.text-left (t (dh/decorate-namespace k))]
+                 [:td.text-right (dh/binding-for-display k binding)]])
+              (dh/binding-by-category name))]]])))
+
+(rum/defc trigger-table []
+  (rum/with-context [[t] i18n/*tongue-context*]
+    [:table
+     [:thead
+      [:tr
+       [:th.text-left [:b (t :help/shortcuts-triggers)]]
+       [:th.text-right [:b (t :help/shortcut)]]]]
+     [:tbody
+      [:tr
+       [:td.text-left (t :help/slash-autocomplete)]
+       [:td.text-right "/"]]
+      [:tr
+       [:td.text-left (t :help/block-content-autocomplete)]
+       [:td.text-right "<"]]
+      [:tr
+       [:td.text-left (t :help/reference-autocomplete)]
+       [:td.text-right "[[]]"]]
+      [:tr
+       [:td.text-left (t :help/block-reference)]
+       [:td.text-right "(())"]]
+      [:tr
+       [:td.text-left (t :shortcut.editor/open-link-in-sidebar)]
+       [:td.text-right "shift-click"]]
+      [:tr
+       [:td.text-left (t :help/context-menu)]
+       [:td.text-right "right click"]]]]))
+
+(rum/defc shortcut
+  []
+  (rum/with-context [[t] i18n/*tongue-context*]
+    [:div
+     [:h1.title (t :help/shortcut-page-title)]
+     (trigger-table)
+     (shortcut-table :shortcut.category/basics)
+     (shortcut-table :shortcut.category/navigating)
+     (shortcut-table :shortcut.category/block-editing)
+     (shortcut-table :shortcut.category/block-command-editing)
+     (shortcut-table :shortcut.category/block-selection)
+     (shortcut-table :shortcut.category/formatting)
+     (shortcut-table :shortcut.category/toggle)
+     (shortcut-table :shortcut.category/others)]))

+ 14 - 33
src/main/frontend/components/sidebar.cljs

@@ -23,7 +23,6 @@
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.export :as export]
             [frontend.handler.export :as export]
             [frontend.config :as config]
             [frontend.config :as config]
-            [frontend.keyboards :as keyboards]
             [dommy.core :as d]
             [dommy.core :as d]
             [clojure.string :as string]
             [clojure.string :as string]
             [goog.object :as gobj]
             [goog.object :as gobj]
@@ -136,11 +135,16 @@
                       :style {:margin-bottom (if global-graph-pages? 0 120)}}
                       :style {:margin-bottom (if global-graph-pages? 0 120)}}
           main-content])]]]))
           main-content])]]]))
 
 
+(rum/defc footer
+  []
+  (when-let [user-footer (and config/publishing? (get-in (state/get-config) [:publish-common-footer]))]
+    [:div.p-6 user-footer]))
+
 (defn get-default-home-if-valid
 (defn get-default-home-if-valid
   []
   []
   (when-let [default-home (state/get-default-home)]
   (when-let [default-home (state/get-default-home)]
     (when-let [page (:page default-home)]
     (when-let [page (:page default-home)]
-      (when (db/entity [:page/name (string/lower-case page)])
+      (when (db/entity [:block/name (string/lower-case page)])
         default-home))))
         default-home))))
 
 
 (defonce sidebar-inited? (atom false))
 (defonce sidebar-inited? (atom false))
@@ -229,11 +233,11 @@
                   :exit 300}}
                   :exit 300}}
        links
        links
        ;; (custom-context-menu-content)
        ;; (custom-context-menu-content)
-       ))))
+))))
 
 
 (rum/defc new-block-mode < rum/reactive
 (rum/defc new-block-mode < rum/reactive
   []
   []
-  (when-let [alt-enter? (= "alt+enter" (state/sub [:shortcuts :editor/new-block]))]
+  (when (state/sub [:editor/new-block-toggle?])
     [:a.px-1.text-sm.font-medium.bg-base-2.mr-4.rounded-md
     [:a.px-1.text-sm.font-medium.bg-base-2.mr-4.rounded-md
      {:title "Click to switch to \"Enter\" for creating new block"
      {:title "Click to switch to \"Enter\" for creating new block"
       :on-click state/toggle-new-block-shortcut!}
       :on-click state/toggle-new-block-shortcut!}
@@ -268,38 +272,13 @@
 (rum/defcs sidebar <
 (rum/defcs sidebar <
   (mixins/modal :modal/show?)
   (mixins/modal :modal/show?)
   rum/reactive
   rum/reactive
-  ;; TODO: move this to keyboards
   (mixins/event-mixin
   (mixins/event-mixin
    (fn [state]
    (fn [state]
      (mixins/listen state js/window "click"
      (mixins/listen state js/window "click"
                     (fn [e]
                     (fn [e]
                       ;; hide context menu
                       ;; hide context menu
                       (state/hide-custom-context-menu!)
                       (state/hide-custom-context-menu!)
-
-                      (editor-handler/clear-selection! e)))
-
-     ;; TODO: move to keyboards
-     (mixins/on-key-down
-      state
-      {;; esc
-       27 (fn [_state e]
-            (editor-handler/clear-selection! e))
-
-       ;; shift+up
-       38 (fn [state e]
-            (editor-handler/on-select-block state e true))
-
-       ;; shift+down
-       40 (fn [state e]
-            (editor-handler/on-select-block state e false))
-
-       ;; ?
-       191 (fn [state e]
-             (when-not (util/input? (gobj/get e "target"))
-               (ui-handler/toggle-help!)))})))
-  {:did-mount (fn [state]
-                (keyboards/bind-shortcuts!)
-                state)}
+                      (editor-handler/clear-selection! e)))))
   [state route-match main-content]
   [state route-match main-content]
   (let [{:keys [open? close-fn open-fn]} state
   (let [{:keys [open? close-fn open-fn]} state
         close-fn (fn []
         close-fn (fn []
@@ -326,7 +305,7 @@
         :route         route-match
         :route         route-match
         :nfs-granted?  granted?
         :nfs-granted?  granted?
         :db-restoring? db-restoring?
         :db-restoring? db-restoring?
-        :on-click      editor-handler/unhighlight-block!}
+        :on-click      editor-handler/unhighlight-blocks!}
 
 
        [:div.theme-inner
        [:div.theme-inner
         (sidebar-mobile-sidebar
         (sidebar-mobile-sidebar
@@ -354,7 +333,9 @@
                   :indexeddb-support?  indexeddb-support?
                   :indexeddb-support?  indexeddb-support?
                   :white?              white?
                   :white?              white?
                   :db-restoring?       db-restoring?
                   :db-restoring?       db-restoring?
-                  :main-content        main-content})]]
+                  :main-content        main-content})
+
+           (footer)]]
          (right-sidebar/sidebar)]
          (right-sidebar/sidebar)]
 
 
         (ui/notification)
         (ui/notification)
@@ -372,4 +353,4 @@
          ;;   :on-click (fn []
          ;;   :on-click (fn []
          ;;               (state/set-left-sidebar-open! (not (state/get-left-sidebar-open?))))}
          ;;               (state/set-left-sidebar-open! (not (state/get-left-sidebar-open?))))}
          ;;  (if (state/sub :ui/left-sidebar-open?) "<" ">")]
          ;;  (if (state/sub :ui/left-sidebar-open?) "<" ">")]
-          )]))))
+)]))))

文件差异内容过多而无法显示
+ 13 - 5
src/main/frontend/config.cljs


+ 12 - 9
src/main/frontend/context/i18n.cljs

@@ -1,14 +1,16 @@
 (ns frontend.context.i18n
 (ns frontend.context.i18n
   (:require [frontend.dicts :as dicts]
   (:require [frontend.dicts :as dicts]
+            [frontend.modules.shortcut.dict :as shortcut-dict]
             [rum.core :as rum]
             [rum.core :as rum]
+            [medley.core :refer [deep-merge]]
             [frontend.state :as state]))
             [frontend.state :as state]))
 
 
 ;; TODO
 ;; TODO
-;; - [x] Get the preffered language from state
-;; - [x] Update the preffered language
-;; - [x] Create t functiona which takes a keyword and returns text with the current preffered language
-;; - [x] Add fetch for local browser prefered language if user has set it already
-;; - [ ] Fetch prefered language from backend if user is logged in
+;; - [x] Get the preferred language from state
+;; - [x] Update the preferred language
+;; - [x] Create t functiona which takes a keyword and returns text with the current preferred language
+;; - [x] Add fetch for local browser preferred language if user has set it already
+;; - [ ] Fetch preferred language from backend if user is logged in
 
 
 (defn fetch-local-language []
 (defn fetch-local-language []
   (.. js/window -navigator -language))
   (.. js/window -navigator -language))
@@ -16,13 +18,14 @@
 (rum/defcontext *tongue-context*)
 (rum/defcontext *tongue-context*)
 
 
 (rum/defc tongue-provider [children]
 (rum/defc tongue-provider [children]
-  (let [prefered-language (keyword (state/sub :preferred-language))
+  (let [preferred-language (keyword (state/sub :preferred-language))
         set-preferred-language state/set-preferred-language!
         set-preferred-language state/set-preferred-language!
-        t (partial dicts/translate prefered-language)]
-    (if (nil? prefered-language)
+        all-dicts (deep-merge dicts/dicts shortcut-dict/dict)
+        t (partial (dicts/translate all-dicts) preferred-language)]
+    (if (nil? preferred-language)
       (set-preferred-language (fetch-local-language))
       (set-preferred-language (fetch-local-language))
       :ok)
       :ok)
-    (rum/bind-context [*tongue-context* [t prefered-language set-preferred-language]]
+    (rum/bind-context [*tongue-context* [t preferred-language set-preferred-language]]
                       children)))
                       children)))
 
 
 (rum/defc use-tongue []
 (rum/defc use-tongue []

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

@@ -8,7 +8,7 @@
             [frontend.log]
             [frontend.log]
             [reitit.frontend :as rf]
             [reitit.frontend :as rf]
             [reitit.frontend.easy :as rfe]
             [reitit.frontend.easy :as rfe]
-            [api]))
+            [logseq.api]))
 
 
 (defn set-router!
 (defn set-router!
   []
   []
@@ -55,4 +55,5 @@
 (defn stop []
 (defn stop []
   ;; stop is called before any code is reloaded
   ;; stop is called before any code is reloaded
   ;; this is controlled by :before-load in the config
   ;; this is controlled by :before-load in the config
+  (handler/stop!)
   (js/console.log "stop"))
   (js/console.log "stop"))

+ 24 - 3
src/main/frontend/date.cljs

@@ -20,7 +20,7 @@
   (when-let [formatter-string (state/get-date-formatter)]
   (when-let [formatter-string (state/get-date-formatter)]
     (tf/unparse (tf/formatter formatter-string) date)))
     (tf/unparse (tf/formatter formatter-string) date)))
 
 
-(def custom-formatter (tf/formatter "yyyy-MM-dd HH:mm:ssZ"))
+(def custom-formatter (tf/formatter "yyyy-MM-dd'T'HH:mm:ssZZ"))
 
 
 (defn journal-title-formatters
 (defn journal-title-formatters
   []
   []
@@ -44,8 +44,24 @@
      "yyyy年MM月dd日"}
      "yyyy年MM月dd日"}
    (state/get-date-formatter)))
    (state/get-date-formatter)))
 
 
-(defn get-date-time-string [date-time]
-  (tf/unparse custom-formatter date-time))
+(defn get-date-time-string
+  ([]
+   (get-date-time-string (t/now)))
+  ([date-time]
+   (tf/unparse custom-formatter date-time)))
+
+(defn get-locale-string
+  [s]
+  (try
+    (->> (tf/parse (tf/formatters :date-time-no-ms) s)
+        (t/to-default-time-zone)
+        (tf/unparse (tf/formatter "MMM do, yyyy")))
+    (catch js/Error e
+      nil)))
+
+(defn ISO-string
+  []
+  (.toISOString (js/Date.)))
 
 
 (defn get-local-date-time-string
 (defn get-local-date-time-string
   []
   []
@@ -170,6 +186,11 @@
     (let [journal-title (util/capitalize-all journal-title)]
     (let [journal-title (util/capitalize-all journal-title)]
       (journal-title-> journal-title #(util/parse-int (tf/unparse (tf/formatter "yyyyMMdd") %))))))
       (journal-title-> journal-title #(util/parse-int (tf/unparse (tf/formatter "yyyyMMdd") %))))))
 
 
+(defn int->journal-title
+  [day]
+  (when day
+    (format (tf/parse (tf/formatter "yyyyMMdd") (str day)))))
+
 (defn journal-title->long
 (defn journal-title->long
   [journal-title]
   [journal-title]
   (journal-title-> journal-title #(tc/to-long %)))
   (journal-title-> journal-title #(tc/to-long %)))

+ 35 - 52
src/main/frontend/db.cljs

@@ -21,11 +21,8 @@
   conns
   conns
   get-repo-path
   get-repo-path
   datascript-db
   datascript-db
-  datascript-files-db
   remove-db!
   remove-db!
-  remove-files-db!
   get-conn
   get-conn
-  get-files-conn
   me-tx
   me-tx
   remove-conn!]
   remove-conn!]
 
 
@@ -37,29 +34,29 @@
   entity pull pull-many transact! get-key-value]
   entity pull pull-many transact! get-key-value]
 
 
  [frontend.db.model
  [frontend.db.model
-  add-properties! block-and-children-transform blocks-count blocks-count-cache clean-export!  cloned? delete-blocks
+  block-and-children-transform blocks-count blocks-count-cache clean-export!  cloned? delete-blocks get-pre-block
   delete-file! delete-file-blocks! delete-file-pages! delete-file-tx delete-files delete-pages-by-files
   delete-file! delete-file-blocks! delete-file-pages! delete-file-tx delete-files delete-pages-by-files
   filter-only-public-pages-and-blocks get-all-block-contents get-all-tagged-pages
   filter-only-public-pages-and-blocks get-all-block-contents get-all-tagged-pages
-  get-all-templates get-block-and-children get-block-and-children-no-cache get-block-by-uuid get-block-children
-  get-block-children-ids get-block-content get-block-file get-block-immediate-children get-block-page
-  get-block-page-end-pos get-block-parent get-block-parents get-block-referenced-blocks get-block-refs-count
-  get-blocks-by-priority get-blocks-contents get-collapsed-blocks get-custom-css
-  get-date-scheduled-or-deadlines get-db-type get-empty-pages get-file get-file-after-blocks get-file-after-blocks-meta
-  get-file-blocks get-file-contents get-file-last-modified-at get-file-no-sub get-file-page get-file-page-id
+  get-all-templates get-block-and-children get-block-by-uuid get-block-children sort-by-left
+  get-block-parent get-block-parents parents-collapsed? get-block-referenced-blocks
+  get-block-children-ids get-block-immediate-children get-block-page
+  get-blocks-contents get-custom-css
+  get-date-scheduled-or-deadlines get-db-type get-empty-pages get-file
+  get-file-blocks get-file-contents get-file-last-modified-at get-file-no-sub get-file-page get-file-page-id file-exists?
   get-file-pages get-files get-files-blocks get-files-full get-files-that-referenced-page get-journals-length
   get-file-pages get-files get-files-blocks get-files-full get-files-that-referenced-page get-journals-length
-  get-latest-journals get-marker-blocks get-matched-blocks get-page get-page-alias get-page-alias-names get-page-blocks get-page-linked-refs-refed-pages
-  get-page-blocks-count get-page-blocks-no-cache get-page-file get-page-format get-page-name get-page-properties
-  get-page-properties-content get-page-referenced-blocks get-page-referenced-pages get-page-unlinked-references
+  get-latest-journals get-matched-blocks get-page get-page-alias get-page-alias-names get-page-blocks get-page-linked-refs-refed-pages
+  get-page-blocks-count get-page-blocks-no-cache get-page-file get-page-format get-page-properties
+  get-page-referenced-blocks get-page-referenced-pages get-page-unlinked-references get-page-referenced-blocks-no-cache
   get-pages get-pages-relation get-pages-that-mentioned-page get-public-pages get-tag-pages
   get-pages get-pages-relation get-pages-that-mentioned-page get-public-pages get-tag-pages
   journal-page? local-native-fs? mark-repo-as-cloned! page-alias-set page-blocks-transform pull-block
   journal-page? local-native-fs? mark-repo-as-cloned! page-alias-set page-blocks-transform pull-block
   set-file-last-modified-at! transact-files-db! with-block-refs-count get-modified-pages page-empty? get-alias-source-page
   set-file-last-modified-at! transact-files-db! with-block-refs-count get-modified-pages page-empty? get-alias-source-page
-  set-file-content!]
+  set-file-content! has-children?]
 
 
  [frontend.db.react
  [frontend.db.react
-  get-current-marker get-current-page get-current-priority get-handler-keys set-key-value
+  get-current-marker get-current-page get-current-priority set-key-value
   transact-react! remove-key! remove-q! remove-query-component! add-q! add-query-component! clear-query-state!
   transact-react! remove-key! remove-q! remove-query-component! add-q! add-query-component! clear-query-state!
   clear-query-state-without-refs-and-embeds! get-block-blocks-cache-atom get-page-blocks-cache-atom kv q
   clear-query-state-without-refs-and-embeds! get-block-blocks-cache-atom get-page-blocks-cache-atom kv q
-  query-state query-components query-entity-in-component remove-custom-query! set-new-result! sub-key-value]
+  query-state query-components query-entity-in-component remove-custom-query! set-new-result! sub-key-value refresh!]
 
 
  [frontend.db.query-custom
  [frontend.db.query-custom
   custom-query]
   custom-query]
@@ -70,18 +67,13 @@
 
 
 ;; persisting DBs between page reloads
 ;; persisting DBs between page reloads
 (defn persist! [repo]
 (defn persist! [repo]
-  (let [file-key (datascript-files-db repo)
-        non-file-key (datascript-db repo)
-        files-conn (get-files-conn repo)
-        file-db (when files-conn (d/db files-conn))
-        non-file-conn (get-conn repo false)
-        non-file-db (when non-file-conn (d/db non-file-conn))
-        file-db-str (if file-db (db->string file-db) "")
-        non-file-db-str (if non-file-db (db->string non-file-db) "")]
-    (p/let [_ (idb/set-batch! [{:key file-key :value file-db-str}
-                               {:key non-file-key :value non-file-db-str}])]
-      (state/set-last-persist-transact-id! repo true (get-max-tx-id file-db))
-      (state/set-last-persist-transact-id! repo false (get-max-tx-id non-file-db)))))
+  (let [key (datascript-db repo)
+        conn (get-conn repo false)]
+    (when conn
+      (let [db (d/db conn)
+            db-str (if db (db->string db) "")]
+        (p/let [_ (idb/set-batch! [{:key key :value db-str}])]
+          (state/set-last-persist-transact-id! repo false (get-max-tx-id db)))))))
 
 
 (defonce persistent-jobs (atom {}))
 (defonce persistent-jobs (atom {}))
 
 
@@ -110,31 +102,27 @@
 ;; TODO: pass as a parameter
 ;; TODO: pass as a parameter
 (defonce *sync-search-indice-f (atom nil))
 (defonce *sync-search-indice-f (atom nil))
 (defn- repo-listen-to-tx!
 (defn- repo-listen-to-tx!
-  [repo conn files-db?]
+  [repo conn]
   (d/listen! conn :persistence
   (d/listen! conn :persistence
              (fn [tx-report]
              (fn [tx-report]
                (when-not (util/electron?)
                (when-not (util/electron?)
                 (let [tx-id (get-tx-id tx-report)]
                 (let [tx-id (get-tx-id tx-report)]
                   (state/set-last-transact-time! repo (util/time-ms))
                   (state/set-last-transact-time! repo (util/time-ms))
-                  ;; (state/persist-transaction! repo files-db? tx-id (:tx-data tx-report))
                   (persist-if-idle! repo)))
                   (persist-if-idle! repo)))
 
 
                ;; rebuild search indices
                ;; rebuild search indices
-               (when-not files-db?
-                 (let [data (:tx-data tx-report)
-                       datoms (filter
-                               (fn [datom]
-                                 (contains? #{:page/name :block/content} (:a datom)))
-                               data)]
-                   (when-let [f @*sync-search-indice-f]
-                     (f datoms)))))))
+               (let [data (:tx-data tx-report)
+                     datoms (filter
+                             (fn [datom]
+                               (contains? #{:block/name :block/content} (:a datom)))
+                             data)]
+                 (when-let [f @*sync-search-indice-f]
+                   (f datoms))))))
 
 
 (defn- listen-and-persist!
 (defn- listen-and-persist!
   [repo]
   [repo]
-  (when-let [conn (get-files-conn repo)]
-    (repo-listen-to-tx! repo conn true))
   (when-let [conn (get-conn repo false)]
   (when-let [conn (get-conn repo false)]
-    (repo-listen-to-tx! repo conn false)))
+    (repo-listen-to-tx! repo conn)))
 
 
 (defn start-db-conn!
 (defn start-db-conn!
   ([me repo]
   ([me repo]
@@ -149,17 +137,8 @@
   (let [logged? (:name me)]
   (let [logged? (:name me)]
     (doall
     (doall
      (for [{:keys [url]} repos]
      (for [{:keys [url]} repos]
-       (let [repo url
-             db-name (datascript-files-db repo)
-             db-conn (d/create-conn db-schema/files-db-schema)]
-         (swap! conns assoc db-name db-conn)
-         (p/let [stored (idb/get-item db-name)
-                 _ (when stored
-                     (let [stored-db (string->db stored)
-                           attached-db (d/db-with stored-db
-                                                  [(me-tx stored-db me)])]
-                       (conn/reset-conn! db-conn attached-db)))
-                 db-name (datascript-db repo)
+       (let [repo url]
+         (p/let [db-name (datascript-db repo)
                  db-conn (d/create-conn db-schema/schema)
                  db-conn (d/create-conn db-schema/schema)
                  _ (d/transact! db-conn [{:schema/version db-schema/version}])
                  _ (d/transact! db-conn [{:schema/version db-schema/version}])
                  _ (swap! conns assoc db-name db-conn)
                  _ (swap! conns assoc db-name db-conn)
@@ -183,3 +162,7 @@
         (f))
         (f))
       (recur))
       (recur))
     chan))
     chan))
+
+(defn new-block-id
+  []
+  (d/squuid))

+ 2 - 21
src/main/frontend/db/conn.cljs

@@ -23,19 +23,10 @@
   (when repo
   (when repo
     (str config/idb-db-prefix (get-repo-path repo))))
     (str config/idb-db-prefix (get-repo-path repo))))
 
 
-(defn datascript-files-db
-  [repo]
-  (when repo
-    (str "logseq-files-db/" (get-repo-path repo))))
-
 (defn remove-db!
 (defn remove-db!
   [repo]
   [repo]
   (idb/remove-item! (datascript-db repo)))
   (idb/remove-item! (datascript-db repo)))
 
 
-(defn remove-files-db!
-  [repo]
-  (idb/remove-item! (datascript-files-db repo)))
-
 (defn get-conn
 (defn get-conn
   ([]
   ([]
    (get-conn (state/get-current-repo) true))
    (get-conn (state/get-current-repo) true))
@@ -50,19 +41,12 @@
          @conn
          @conn
          conn)))))
          conn)))))
 
 
-(defn get-files-conn
-  ([]
-   (get-files-conn (state/get-current-repo)))
-  ([repo]
-   (get @conns (datascript-files-db repo))))
-
 (defn reset-conn! [conn db]
 (defn reset-conn! [conn db]
   (reset! conn db))
   (reset! conn db))
 
 
 (defn remove-conn!
 (defn remove-conn!
   [repo]
   [repo]
-  (swap! conns dissoc (datascript-db repo))
-  (swap! conns dissoc (datascript-files-db repo)))
+  (swap! conns dissoc (datascript-db repo)))
 
 
 (defn me-tx
 (defn me-tx
   [db {:keys [name email avatar]}]
   [db {:keys [name email avatar]}]
@@ -74,11 +58,8 @@
   ([me repo]
   ([me repo]
    (start! me repo {}))
    (start! me repo {}))
   ([me repo {:keys [db-type listen-handler]}]
   ([me repo {:keys [db-type listen-handler]}]
-   (let [files-db-name (datascript-files-db repo)
-         files-db-conn (d/create-conn db-schema/files-db-schema)
-         db-name (datascript-db repo)
+   (let [db-name (datascript-db repo)
          db-conn (d/create-conn db-schema/schema)]
          db-conn (d/create-conn db-schema/schema)]
-     (swap! conns assoc files-db-name files-db-conn)
      (swap! conns assoc db-name db-conn)
      (swap! conns assoc db-name db-conn)
      (d/transact! db-conn [(cond-> {:schema/version db-schema/version}
      (d/transact! db-conn [(cond-> {:schema/version db-schema/version}
                              db-type
                              db-type

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

@@ -18,37 +18,4 @@
                :git/cloned? (cloned? repo)
                :git/cloned? (cloned? repo)
                :git/status (get-key-value repo :git/status)
                :git/status (get-key-value repo :git/status)
                :git/error (get-key-value repo :git/error)})
                :git/error (get-key-value repo :git/error)})
-            repos)))
-
-  ;; filtered blocks
-
-  (def page-and-aliases #{22})
-  (def excluded-pages #{59})
-  (def include-pages #{106})
-  (def page-linked-blocks
-    (->
-     (d/q
-      '[:find (pull ?b [:block/uuid
-                        :block/title
-                        {:block/children ...}])
-        :in $ ?pages
-        :where
-        [?b :block/ref-pages ?ref-page]
-        [(contains? ?pages ?ref-page)]]
-      (get-conn)
-      page-and-aliases)
-     flatten))
-
-  (def page-linked-blocks-include-filter
-    (if (seq include-pages)
-      (filter (fn [{:block/keys [ref-pages]}]
-                (some include-pages (map :db/id ref-pages)))
-              page-linked-blocks)
-      page-linked-blocks))
-
-  (def page-linked-blocks-exclude-filter
-    (if (seq excluded-pages)
-      (remove (fn [{:block/keys [ref-pages]}]
-                (some excluded-pages (map :db/id ref-pages)))
-              page-linked-blocks-include-filter)
-      page-linked-blocks-include-filter)))
+            repos))))

+ 3 - 3
src/main/frontend/db/default.cljs

@@ -3,7 +3,7 @@
 
 
 (def built-in-pages
 (def built-in-pages
   (mapv (fn [p]
   (mapv (fn [p]
-          {:page/name (string/lower-case p)
-           :page/original-name p
-           :page/journal? false})
+          {:block/name (string/lower-case p)
+           :block/original-name p
+           :block/journal? false})
         #{"NOW" "LATER" "DOING" "DONE" "IN-PROGRESS" "TODO" "WAIT" "WAITING" "A" "B" "C"}))
         #{"NOW" "LATER" "DOING" "DONE" "IN-PROGRESS" "TODO" "WAIT" "WAITING" "A" "B" "C"}))

文件差异内容过多而无法显示
+ 269 - 362
src/main/frontend/db/model.cljs


+ 61 - 0
src/main/frontend/db/outliner.cljs

@@ -0,0 +1,61 @@
+(ns frontend.db.outliner
+  (:require [datascript.core :as d]
+            [frontend.db :as db]
+            [frontend.db.react :as react]
+            [frontend.util :as util]
+            [frontend.debug :as debug]
+            [frontend.db.utils :as db-utils]))
+
+(defn get-by-id
+  [conn id]
+  (try
+    (d/pull @conn '[*] id)
+    (catch js/Error e nil)))
+
+(defn get-by-parent-&-left
+  [conn parent-id left-id]
+  (let [r (d/q '[:find (pull ?a [*])
+                 :in $ ?p ?l
+                 :where
+                 [?a :block/left ?l]
+                 [?a :block/parent ?p]]
+            @conn parent-id left-id)]
+    (ffirst r)))
+
+;; key [:block/children parent-id]
+
+(def get-by-parent-id
+  '[:find (pull ?a [*])
+    :in $ ?id
+    :where
+    [?a :block/parent ?id]])
+
+(defn save-block
+  [conn block-m]
+  (let [tx (-> (dissoc block-m :block/children :block/dummy? :block/level :block/meta)
+             (util/remove-nils))
+        block-id (:block/uuid block-m)]
+    (d/transact! conn [tx])
+    (db-utils/pull [:block/uuid block-id])))
+
+(defn del-block
+  [conn id-or-look-ref]
+  (d/transact! conn [[:db.fn/retractEntity id-or-look-ref]]))
+
+(defn del-blocks
+  [ids-or-look-refs]
+  (mapv (fn [id-or-look-ref]
+         [:db.fn/retractEntity id-or-look-ref])
+    ids-or-look-refs))
+
+(defn get-journals
+  [conn]
+  (let [r (d/q '[:find (pull ?a [*])
+                 :where
+                 [?a :block/journal? true]]
+               @conn)]
+    (flatten r)))
+
+(defn remove-non-existed-refs!
+  [refs]
+  (filter db/entity refs))

+ 18 - 17
src/main/frontend/db/query_dsl.cljs

@@ -27,7 +27,7 @@
 ;;            (between last-modified-at -1d today)
 ;;            (between last-modified-at -1d today)
 ;; [[page-ref]]
 ;; [[page-ref]]
 ;; property (block)
 ;; property (block)
-;; todo (block)
+;; task (block)
 ;; priority (block)
 ;; priority (block)
 ;; page
 ;; page
 ;; page-property (page)
 ;; page-property (page)
@@ -138,7 +138,7 @@
          page-ref? (text/page-ref? e)]
          page-ref? (text/page-ref? e)]
      (when (or (and page-ref?
      (when (or (and page-ref?
                     (not (contains? #{'page-property 'page-tags} (:current-filter env))))
                     (not (contains? #{'page-property 'page-tags} (:current-filter env))))
-               (contains? #{'between 'property 'todo 'priority 'sort-by 'page} fe))
+               (contains? #{'between 'property 'todo 'task 'priority 'sort-by 'page} fe))
        (reset! blocks? true))
        (reset! blocks? true))
      (cond
      (cond
        (nil? e)
        (nil? e)
@@ -147,7 +147,7 @@
        page-ref?
        page-ref?
        (let [page-name (-> (text/page-ref-un-brackets! e)
        (let [page-name (-> (text/page-ref-un-brackets! e)
                            (string/lower-case))]
                            (string/lower-case))]
-         [['?b :block/path-ref-pages [:page/name page-name]]])
+         [['?b :block/path-refs [:block/name page-name]]])
 
 
        (contains? #{'and 'or 'not} fe)
        (contains? #{'and 'or 'not} fe)
        (let [clauses (->> (map (fn [form]
        (let [clauses (->> (map (fn [form]
@@ -193,8 +193,8 @@
              end (->journal-day-int (nth e 2))
              end (->journal-day-int (nth e 2))
              [start end] (sort [start end])]
              [start end] (sort [start end])]
          [['?b :block/page '?p]
          [['?b :block/page '?p]
-          ['?p :page/journal? true]
-          ['?p :page/journal-day '?d]
+          ['?p :block/journal? true]
+          ['?p :block/journal-day '?d]
           [(list '>= '?d start)]
           [(list '>= '?d start)]
           [(list '<= '?d end)]])
           [(list '<= '?d end)]])
 
 
@@ -224,7 +224,7 @@
                    '?v
                    '?v
                      (uniq-symbol counter "?v"))]
                      (uniq-symbol counter "?v"))]
          [['?b :block/properties '?prop]
          [['?b :block/properties '?prop]
-          [(list 'get '?prop (name (nth e 1))) sym]
+          [(list 'get '?prop (keyword (nth e 1))) sym]
           (list
           (list
            'or
            'or
            [(list '= sym v)]
            [(list '= sym v)]
@@ -233,9 +233,9 @@
        (and (= 'property fe)
        (and (= 'property fe)
             (= 2 (count e)))
             (= 2 (count e)))
        [['?b :block/properties '?prop]
        [['?b :block/properties '?prop]
-        [(list 'get '?prop (name (nth e 1)))]]
+        [(list 'get '?prop (keyword (nth e 1)))]]
 
 
-       (= 'todo fe)
+       (or (= 'todo fe) (= 'task fe))
        (let [markers (if (coll? (first (rest e)))
        (let [markers (if (coll? (first (rest e)))
                        (first (rest e))
                        (first (rest e))
                        (rest e))]
                        (rest e))]
@@ -274,7 +274,7 @@
        (= 'page fe)
        (= 'page fe)
        (let [page-name (string/lower-case (first (rest e)))
        (let [page-name (string/lower-case (first (rest e)))
              page-name (text/page-ref-un-brackets! page-name)]
              page-name (text/page-ref-un-brackets! page-name)]
-         [['?b :block/page [:page/name page-name]]])
+         [['?b :block/page [:block/name page-name]]])
 
 
        (= 'page-property fe)
        (= 'page-property fe)
        (let [[k v] (rest e)]
        (let [[k v] (rest e)]
@@ -282,13 +282,14 @@
            (let [v (some->> (name (nth e 2))
            (let [v (some->> (name (nth e 2))
                             (text/page-ref-un-brackets!))
                             (text/page-ref-un-brackets!))
                  sym '?v]
                  sym '?v]
-             [['?p :page/properties '?prop]
+             [['?p :block/name]
+              ['?p :block/properties '?prop]
               [(list 'get '?prop (keyword (nth e 1))) sym]
               [(list 'get '?prop (keyword (nth e 1))) sym]
               (list
               (list
                'or
                'or
                [(list '= sym v)]
                [(list '= sym v)]
                [(list 'contains? sym v)])])
                [(list 'contains? sym v)])])
-           [['?p :page/properties '?prop]
+           [['?p :block/properties '?prop]
             [(list 'get '?prop (keyword (nth e 1)))]]))
             [(list 'get '?prop (keyword (nth e 1)))]]))
 
 
        (= 'page-tags fe)
        (= 'page-tags fe)
@@ -301,12 +302,12 @@
              (let [tags (set (map (comp text/page-ref-un-brackets! string/lower-case name) tags))]
              (let [tags (set (map (comp text/page-ref-un-brackets! string/lower-case name) tags))]
                (let [sym-1 (uniq-symbol counter "?t")
                (let [sym-1 (uniq-symbol counter "?t")
                      sym-2 (uniq-symbol counter "?tag")]
                      sym-2 (uniq-symbol counter "?tag")]
-                 [['?p :page/tags sym-1]
-                  [sym-1 :page/name sym-2]
+                 [['?p :block/tags sym-1]
+                  [sym-1 :block/name sym-2]
                   [(list 'contains? tags sym-2)]])))))
                   [(list 'contains? tags sym-2)]])))))
 
 
        (= 'all-page-tags fe)
        (= 'all-page-tags fe)
-       [['?e :page/tags '?p]]
+       [['?e :block/tags '?p]]
 
 
        :else
        :else
        nil))))
        nil))))
@@ -335,13 +336,13 @@
     (if not?
     (if not?
       (cond
       (cond
         (and b? p?)
         (and b? p?)
-        (concat [['?b :block/uuid] ['?p :page/name] ['?b :block/page '?p]] q)
+        (concat [['?b :block/uuid] ['?p :block/name] ['?b :block/page '?p]] q)
 
 
         b?
         b?
         (concat [['?b :block/uuid]] q)
         (concat [['?b :block/uuid]] q)
 
 
         p?
         p?
-        (concat [['?p :page/name]] q)
+        (concat [['?p :block/name]] q)
 
 
         :else
         :else
         q)
         q)
@@ -437,7 +438,7 @@
 
 
   (query "(and [[some page]] (property foo bar))")
   (query "(and [[some page]] (property foo bar))")
 
 
-  (query "(and [[some page]] (todo now later))")
+  (query "(and [[some page]] (task now later))")
 
 
   (query "(and [[some page]] (priority A))")
   (query "(and [[some page]] (priority A))")
 
 

+ 40 - 29
src/main/frontend/db/query_react.cljs

@@ -47,31 +47,31 @@
   [query-result remove-blocks q]
   [query-result remove-blocks q]
   (try
   (try
     (let [repo (state/get-current-repo)
     (let [repo (state/get-current-repo)
-         result (db-utils/seq-flatten query-result)
-         block? (:block/uuid (first result))]
-     (let [result (if block?
-                    (let [result (if (seq remove-blocks)
-                                   (let [remove-blocks (set remove-blocks)]
-                                     (remove (fn [h]
-                                               (contains? remove-blocks (:block/uuid h)))
-                                             result))
-                                   result)]
-                      (some->> result
-                               (db-utils/with-repo repo)
-                               (model/with-block-refs-count repo)
-                               (model/sort-blocks)))
-                    result)]
-       (if-let [result-transform (:result-transform q)]
-         (if-let [f (sci/eval-string (pr-str result-transform))]
-           (try
-             (sci/call-fn f result)
-             (catch js/Error e
-               (log/error :sci/call-error e)
-               result))
-           result)
-         (if block?
-           (db-utils/group-by-page result)
-           result))))
+          result (db-utils/seq-flatten query-result)
+          block? (:block/uuid (first result))]
+      (let [result (if block?
+                     (let [result (if (seq remove-blocks)
+                                    (let [remove-blocks (set remove-blocks)]
+                                      (remove (fn [h]
+                                                (contains? remove-blocks (:block/uuid h)))
+                                              result))
+                                    result)]
+                       (some->> result
+                                (db-utils/with-repo repo)
+                                (model/with-block-refs-count repo)
+                                (model/with-pages)))
+                     result)]
+        (if-let [result-transform (:result-transform q)]
+          (if-let [f (sci/eval-string (pr-str result-transform))]
+            (try
+              (sci/call-fn f result)
+              (catch js/Error e
+                (log/error :sci/call-error e)
+                result))
+            result)
+          (if block?
+            (db-utils/group-by-page result)
+            result))))
     (catch js/Error e
     (catch js/Error e
       (log/error :query/failed e))))
       (log/error :query/failed e))))
 
 
@@ -80,14 +80,25 @@
   (let [page-ref? #(and (string? %) (text/page-ref? %))]
   (let [page-ref? #(and (string? %) (text/page-ref? %))]
     (walk/postwalk
     (walk/postwalk
      (fn [f]
      (fn [f]
-       (if (and (list? f)
-                (= (first f) '=)
-                (= 3 (count f))
-                (some page-ref? (rest f)))
+       (cond
+         ;; backward compatible
+         ;; 1. replace :page/ => :block/
+         (and (keyword? f) (= "page" (namespace f)))
+         (keyword "block" (name f))
+
+         (and (keyword? f) (contains? #{:block/ref-pages :block/ref-blocks} f))
+         :block/refs
+
+         (and (list? f)
+              (= (first f) '=)
+              (= 3 (count f))
+              (some page-ref? (rest f)))
          (let [[x y] (rest f)
          (let [[x y] (rest f)
                [page-ref sym] (if (page-ref? x) [x y] [y x])
                [page-ref sym] (if (page-ref? x) [x y] [y x])
                page-ref (string/lower-case page-ref)]
                page-ref (string/lower-case page-ref)]
            (list 'contains? sym (text/page-ref-un-brackets! page-ref)))
            (list 'contains? sym (text/page-ref-un-brackets! page-ref)))
+
+         :else
          f)) query)))
          f)) query)))
 
 
 (defn react-query
 (defn react-query

+ 70 - 61
src/main/frontend/db/react.cljs

@@ -8,6 +8,7 @@
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.date :as date]
             [frontend.date :as date]
             [frontend.util :as util :refer-macros [profile] :refer [react]]
             [frontend.util :as util :refer-macros [profile] :refer [react]]
+            [frontend.util.marker :as marker]
             [clojure.string :as string]
             [clojure.string :as string]
             [frontend.config :as config]
             [frontend.config :as config]
             [datascript.core :as d]
             [datascript.core :as d]
@@ -54,6 +55,17 @@
                    (into {}))]
                    (into {}))]
     (reset! query-state state)))
     (reset! query-state state)))
 
 
+(defn get-current-repo-refs-keys
+  []
+  (when-let [current-repo (state/get-current-repo)]
+    (->>
+     (map (fn [[repo k id]]
+            (when (and (= repo current-repo)
+                       (contains? #{:block/refed-blocks :block/unlinked-refs} k))
+              [k id]))
+       (keys @query-state))
+     (remove nil?))))
+
 ;; TODO: Add components which subscribed to a specific query
 ;; TODO: Add components which subscribed to a specific query
 (defn add-q!
 (defn add-q!
   [k query inputs result-atom transform-fn query-fn inputs-fn]
   [k query inputs result-atom transform-fn query-fn inputs-fn]
@@ -119,16 +131,12 @@
          (add-q! k nil nil result-atom identity identity identity))))))
          (add-q! k nil nil result-atom identity identity identity))))))
 
 
 (defn q
 (defn q
-  [repo k {:keys [use-cache? files-db? transform-fn query-fn inputs-fn]
+  [repo k {:keys [use-cache? transform-fn query-fn inputs-fn]
            :or {use-cache? true
            :or {use-cache? true
-                files-db? false
                 transform-fn identity}} query & inputs]
                 transform-fn identity}} query & inputs]
   (let [kv? (and (vector? k) (= :kv (first k)))
   (let [kv? (and (vector? k) (= :kv (first k)))
         k (vec (cons repo k))]
         k (vec (cons repo k))]
-    (when-let [conn (if files-db?
-                      (when-let [files-conn (conn/get-files-conn repo)]
-                        (deref files-conn))
-                      (conn/get-conn repo))]
+    (when-let [conn (conn/get-conn repo)]
       (let [result-atom (:result (get @query-state k))]
       (let [result-atom (:result (get @query-state k))]
         (when-let [component *query-component*]
         (when-let [component *query-component*]
           (add-query-component! k component))
           (add-query-component! k component))
@@ -175,7 +183,7 @@
                (date/journal-name))]
                (date/journal-name))]
     (when page
     (when page
       (let [page-name (string/lower-case page)]
       (let [page-name (string/lower-case page)]
-        (db-utils/entity [:page/name page-name])))))
+        (db-utils/entity [:block/name page-name])))))
 
 
 (defn get-current-priority
 (defn get-current-priority
   []
   []
@@ -192,10 +200,10 @@
         route-name (get-in match [:data :name])]
         route-name (get-in match [:data :name])]
     (when (= route-name :page)
     (when (= route-name :page)
       (when-let [page-name (get-in match [:path-params :name])]
       (when-let [page-name (get-in match [:path-params :name])]
-        (and (util/marker? page-name)
+        (and (marker/marker? page-name)
              (string/upper-case page-name))))))
              (string/upper-case page-name))))))
 
 
-(defn get-handler-keys
+(defn get-related-keys
   [{:keys [key data]}]
   [{:keys [key data]}]
   (cond
   (cond
     (coll? key)
     (coll? key)
@@ -210,7 +218,7 @@
               current-marker (get-current-marker)
               current-marker (get-current-marker)
               current-page-id (:db/id (get-current-page))
               current-page-id (:db/id (get-current-page))
               {:block/keys [page]} (first blocks)
               {:block/keys [page]} (first blocks)
-              handler-keys (->>
+              related-keys (->>
                             (util/concat-without-nil
                             (util/concat-without-nil
                              (mapcat
                              (mapcat
                               (fn [block]
                               (fn [block]
@@ -233,29 +241,26 @@
 
 
                              (when current-page-id
                              (when current-page-id
                                [[:page/ref-pages current-page-id]
                                [[:page/ref-pages current-page-id]
-                                [:page/refed-blocks current-page-id]
+                                ;; [:block/refed-blocks current-page-id]
                                 [:page/mentioned-pages current-page-id]])
                                 [:page/mentioned-pages current-page-id]])
 
 
-                             ;; refed-pages
                              (apply concat
                              (apply concat
-                                    (for [{:block/keys [ref-pages]} blocks]
-                                      (map (fn [page]
-                                             (when-let [page (db-utils/entity [:page/name (:page/name page)])]
-                                               [:page/refed-blocks (:db/id page)]))
-                                           ref-pages)))
-
-                             ;; refed-blocks
-                             (apply concat
-                                    (for [{:block/keys [ref-blocks]} blocks]
-                                      (map (fn [ref-block]
-                                             [:block/refed-blocks (last ref-block)])
-                                           ref-blocks))))
+                               (for [{:block/keys [refs]} blocks]
+                                 (mapcat (fn [ref]
+                                           (when-let [block (if (and (map? ref) (:block/name ref))
+                                                              (db-utils/entity [:block/name (:block/name ref)])
+                                                              (db-utils/entity ref))]
+                                             [[:page/blocks (:db/id (:block/page block))]
+                                              ;; [:block/refed-blocks (:db/id block)]
+                                              ]))
+                                         refs))))
                             (distinct))
                             (distinct))
               refed-pages (map
               refed-pages (map
                            (fn [[k page-id]]
                            (fn [[k page-id]]
-                             (if (= k :page/refed-blocks)
+                             (if (= k :block/refed-blocks)
                                [:page/ref-pages page-id]))
                                [:page/ref-pages page-id]))
-                           handler-keys)
+                            related-keys)
+              all-refed-blocks (get-current-repo-refs-keys)
               custom-queries (some->>
               custom-queries (some->>
                               (filter (fn [v]
                               (filter (fn [v]
                                         (and (= (first v) (state/get-current-repo))
                                         (and (= (first v) (state/get-current-repo))
@@ -272,53 +277,57 @@
                                    (vec (drop 1 v)))))]
                                    (vec (drop 1 v)))))]
           (->>
           (->>
            (util/concat-without-nil
            (util/concat-without-nil
-            handler-keys
+            related-keys
             refed-pages
             refed-pages
+            all-refed-blocks
             custom-queries
             custom-queries
             block-blocks)
             block-blocks)
            distinct)))
            distinct)))
       [[key]])))
       [[key]])))
 
 
+(defn refresh!
+  [repo-url {:keys [key data] :as handler-opts}]
+  (let [related-keys (get-related-keys handler-opts)
+        db (conn/get-conn repo-url)]
+    (doseq [related-key related-keys]
+      (let [related-key (vec (cons repo-url related-key))]
+        (when-let [cache (get @query-state related-key)]
+          (let [{:keys [query inputs transform-fn query-fn inputs-fn]} cache]
+            (when (or query query-fn)
+              (let [new-result (profile
+                                "takes"
+                                (->
+                                 (cond
+                                   query-fn
+                                   (profile
+                                    "Query:"
+                                    (doall (query-fn db)))
+
+                                   inputs-fn
+                                   (let [inputs (inputs-fn)]
+                                     (apply d/q query db inputs))
+
+                                   (keyword? query)
+                                   (db-utils/get-key-value repo-url query)
+
+                                   (seq inputs)
+                                   (apply d/q query db inputs)
+
+                                   :else
+                                   (d/q query db))
+                                 transform-fn))]
+                (set-new-result! related-key new-result)))))))))
+
 (defn transact-react!
 (defn transact-react!
-  [repo-url tx-data {:keys [key data files-db?] :as handler-opts
-                     :or {files-db? false}}]
+  [repo-url tx-data {:keys [key data] :as handler-opts}]
   (when-not config/publishing?
   (when-not config/publishing?
     (let [repo-url (or repo-url (state/get-current-repo))
     (let [repo-url (or repo-url (state/get-current-repo))
           tx-data (->> (util/remove-nils tx-data)
           tx-data (->> (util/remove-nils tx-data)
                        (remove nil?))
                        (remove nil?))
-          get-conn (fn [] (if files-db?
-                            (conn/get-files-conn repo-url)
-                            (conn/get-conn repo-url false)))]
+          get-conn (fn [] (conn/get-conn repo-url false))]
       (when (and (seq tx-data) (get-conn))
       (when (and (seq tx-data) (get-conn))
-        (let [tx-result (d/transact! (get-conn) (vec tx-data))
-              db (:db-after tx-result)
-              handler-keys (get-handler-keys handler-opts)]
-          (doseq [handler-key handler-keys]
-            (let [handler-key (vec (cons repo-url handler-key))]
-              (when-let [cache (get @query-state handler-key)]
-                (let [{:keys [query inputs transform-fn query-fn inputs-fn]} cache]
-                  (when (or query query-fn)
-                    (let [new-result (->
-                                      (cond
-                                        query-fn
-                                        (profile
-                                         "Query:"
-                                         (doall (query-fn db)))
-
-                                        inputs-fn
-                                        (let [inputs (inputs-fn)]
-                                          (apply d/q query db inputs))
-
-                                        (keyword? query)
-                                        (db-utils/get-key-value repo-url query)
-
-                                        (seq inputs)
-                                        (apply d/q query db inputs)
-
-                                        :else
-                                        (d/q query db))
-                                      transform-fn)]
-                      (set-new-result! handler-key new-result))))))))))))
+        (d/transact! (get-conn) (vec tx-data))
+        (refresh! repo-url handler-opts)))))
 
 
 (defn set-key-value
 (defn set-key-value
   [repo-url key value]
   [repo-url key value]

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

@@ -37,8 +37,10 @@
 
 
 (defn group-by-page
 (defn group-by-page
   [blocks]
   [blocks]
-  (some->> blocks
-           (group-by :block/page)))
+  (if (:block/page (first blocks))
+    (some->> blocks
+             (group-by :block/page))
+    blocks))
 
 
 (defn group-by-file
 (defn group-by-file
   [blocks]
   [blocks]
@@ -114,3 +116,8 @@
    (when-let [db (conn/get-conn repo-url)]
    (when-let [db (conn/get-conn repo-url)]
      (some-> (d/entity db key)
      (some-> (d/entity db key)
              key))))
              key))))
+
+(defn q
+  [query & inputs]
+  (when-let [repo (state/get-current-repo)]
+    (apply d/q query (conn/get-conn repo) inputs)))

+ 107 - 66
src/main/frontend/db_schema.cljs

@@ -1,100 +1,141 @@
 (ns frontend.db-schema)
 (ns frontend.db-schema)
 
 
-(defonce version "0.0.1")
+(defonce version "0.0.2")
 
 
-(def files-db-schema
-  {:file/path {:db/unique :db.unique/identity}
-   :file/content {}
-   :file/size {}
-   :file/handle {}})
-
-;; A page can corresponds to multiple files (same title),
-;; a month journal file can have multiple pages,
-;; also, each block can be treated as a page too.
+;; A page is a special block, a page can corresponds to multiple files with the same ":block/name".
 (def schema
 (def schema
   {:schema/version  {}
   {:schema/version  {}
    :db/type         {}
    :db/type         {}
    :db/ident        {:db/unique :db.unique/identity}
    :db/ident        {:db/unique :db.unique/identity}
+
    :db/encrypted?    {}
    :db/encrypted?    {}
    :db/encryption-keys {}
    :db/encryption-keys {}
+
    ;; user
    ;; user
-   :me/name  {}
+   :me/name {}
    :me/email {}
    :me/email {}
    :me/avatar {}
    :me/avatar {}
 
 
    ;; Git
    ;; Git
-   :repo/url        {:db/unique :db.unique/identity}
-   :repo/cloned?    {}
-   :git/status {}
-   :git/last-pulled-at {}
-   ;; last error, better we should record all the errors
-   :git/error {}
+   :repo/url {:db/unique :db.unique/identity}
 
 
-   ;; file
-   :file/path       {:db/unique :db.unique/identity}
-   :file/created-at {}
-   :file/last-modified-at {}
+   :recent/pages {}
 
 
-   ;; toggle to comment this line to force to clone
-   :release/re-clone? {}
+   :block/type {}
+   :block/uuid {:db/unique :db.unique/identity}
+   :block/parent {:db/valueType :db.type/ref}
+   :block/left {:db/valueType :db.type/ref}
 
 
-   :recent/pages    {}
-
-   :page/name       {:db/unique      :db.unique/identity}
-   :page/original-name {:db/unique      :db.unique/identity}
-   :page/file       {:db/valueType   :db.type/ref}
-   :page/properties {}
-   :page/alias      {:db/valueType   :db.type/ref
-                     :db/cardinality :db.cardinality/many}
-   :page/tags       {:db/valueType   :db.type/ref
-                     :db/cardinality :db.cardinality/many}
-   :page/journal?   {}
-   :page/journal-day {}
-
-   ;; block
-   :block/uuid   {:db/unique      :db.unique/identity}
-   :block/file   {:db/valueType   :db.type/ref}
+   ;; :markdown, :org
    :block/format {}
    :block/format {}
+
+   ;; mldoc parsed ast
    :block/title {}
    :block/title {}
+
    ;; belongs to which page
    ;; belongs to which page
-   :block/page   {:db/valueType   :db.type/ref
-                  :db/index       true}
-   ;; referenced pages
-   :block/ref-pages {:db/valueType   :db.type/ref
-                     :db/cardinality :db.cardinality/many}
+   :block/page {:db/valueType :db.type/ref
+                :db/index true}
+   ;; reference blocks
+   :block/refs {:db/valueType :db.type/ref
+                :db/cardinality :db.cardinality/many}
    ;; referenced pages inherited from the parents
    ;; referenced pages inherited from the parents
-   :block/path-ref-pages {:db/valueType   :db.type/ref
-                          :db/cardinality :db.cardinality/many}
+   :block/path-refs {:db/valueType   :db.type/ref
+                     :db/cardinality :db.cardinality/many}
 
 
-   ;; Referenced pages
-   ;; Notice: it's only for org mode, :tag1:tag2:
-   ;; Markdown tags will be only stored in :block/ref-pages
-   :block/tags {:db/valueType   :db.type/ref
+   ;; for pages
+   :block/tags {:db/valueType :db.type/ref
                 :db/cardinality :db.cardinality/many}
                 :db/cardinality :db.cardinality/many}
 
 
-   ;; referenced blocks
-   :block/ref-blocks {:db/valueType   :db.type/ref
-                      :db/cardinality :db.cardinality/many}
-   :block/embed-blocks {:db/valueType   :db.type/ref
-                        :db/cardinality :db.cardinality/many}
-   :block/embed-pages {:db/valueType   :db.type/ref
-                       :db/cardinality :db.cardinality/many}
+   ;; for pages
+   :block/alias {:db/valueType :db.type/ref
+                 :db/cardinality :db.cardinality/many}
+
+   ;; full-text for current block
    :block/content {}
    :block/content {}
-   :block/anchor {}
+
+   ;; todo keywords, e.g. "TODO", "DOING", "DONE"
    :block/marker {}
    :block/marker {}
+
+   ;; "A", "B", "C"
    :block/priority {}
    :block/priority {}
+
+   ;; TODO: remove
+   ;; 1, 2, 3, etc.
    :block/level {}
    :block/level {}
-   ;; :start-pos :end-pos
+   ;; TODO: remove
    :block/meta {}
    :block/meta {}
+
+   ;; block key value properties
    :block/properties {}
    :block/properties {}
+
+   ;; parsed ast
    :block/body {}
    :block/body {}
+
+   ;; first block that's not a heading or unordered list
    :block/pre-block? {}
    :block/pre-block? {}
-   :block/collapsed? {}
-   :block/children {:db/valueType   :db.type/ref
-                    :db/cardinality :db.cardinality/many
-                    :db/unique :db.unique/identity}
+
+   ;; scheduled day
    :block/scheduled {}
    :block/scheduled {}
-   :block/scheduled-ast {}
+
+   ;; deadline day
    :block/deadline {}
    :block/deadline {}
-   :block/deadline-ast {}
-   :block/repeated? {}})
+
+   ;; whether blocks is a repeated block (usually a task)
+   :block/repeated? {}
+
+   :block/created-at {}
+   :block/updated-at {}
+
+   ;; page additional attributes
+   ;; page's name, lowercase
+   :block/name {:db/unique :db.unique/identity}
+   ;; page's original name
+   :block/original-name {:db/unique :db.unique/identity}
+   ;; whether page's is a journal
+   :block/journal? {}
+   :block/journal-day {}
+
+   ;; block's file
+   :block/file {:db/valueType :db.type/ref}
+
+   ;; file
+   :file/path {:db/unique :db.unique/identity}
+   ;; only store the content of logseq's files
+   :file/content {}
+   :file/handle {}
+   ;; :file/created-at {}
+   ;; :file/last-modified-at {}
+   ;; :file/size {}
+   ;; :file/handle {}
+
+   ;; git
+   :repo/cloned? {}
+   :git/status {}
+   :git/last-pulled-at {}
+   ;; last error, better we should record all the errors
+   :git/error {}
+
+   })
+
+(def retract-attributes
+  #{
+    :block/refs
+    :block/path-refs
+    :block/tags
+    :block/alias
+    :block/marker
+    :block/priority
+    :block/scheduled
+    :block/deadline
+    :block/repeated?
+    :block/pre-block?
+    :block/level
+    :block/heading-level
+    :block/type
+    :block/title
+    :block/body
+    :block/properties
+    :block/created-at
+    :block/updated-at
+    }
+  )

+ 7 - 0
src/main/frontend/debug.cljs

@@ -0,0 +1,7 @@
+(ns frontend.debug
+  (:require [cljs.pprint :as pprint]))
+
+(defn pprint
+  [& xs]
+  (doseq [x xs]
+    (pprint/pprint x)))

+ 26 - 233
src/main/frontend/dicts.cljs

@@ -1,76 +1,15 @@
 (ns frontend.dicts
 (ns frontend.dicts
-  (:require [tongue.core :as tongue]
-            [frontend.config :as config]))
-
-;; TODO
-;; - [ ] Localizing Number Formats
-;; - [ ] Localizing Dates
-
+  (:require [frontend.config :as config]
+            [shadow.resource :as rc]
+            [tongue.core :as tongue]))
 
 
 (def dicts
 (def dicts
-  {:en {:tutorial/text "---
-title: $today
----
-
-## Hi, welcome to Logseq!
-:PROPERTIES:
-:heading: true
-:END:
-## Logseq is a _privacy-first_, _open-source_ platform for _knowledge_ sharing and management.
-## This is a 3 minute tutorial on how to use Logseq. Let's get started!
-## (Feel free to edit anything, no change will be saved at this moment. If you do want to persist your work, click the **top-right** corner of the screen to connect Logseq to either Github or local directory.)
-## Here are some tips might be useful.
-#+BEGIN_TIP
-Click to edit any block.
-Type `Enter` to create a new block.
-Type `Shift+Enter` to create a new line.
-Type `/` to show all the commands.
-#+END_TIP
-## 1. Let's create a page called [[How to take dummy notes?]]. You can click it to go to that page, or you can `Shift+Click` to open it in the right sidebar! Now you should see both _Linked References_ and _Unlinked References_.
-## 2. Let's reference some blocks on [[How to take dummy notes?]], you can `Shift+Click` any block reference to open it in the right sidebar. Try making
-some changes on the right sidebar, those referenced blocks will be changed too!
-### ((5f713e91-8a3c-4b04-a33a-c39482428e2d)) : This is a block reference.
-### ((5f713ea8-8cba-403d-ac00-9964b1ec7190)) : This is another block reference.
-## 3. Do you support tags?
-### Of course, this is a #dummy tag.
-## 4. Do you support tasks like todo/doing/done and priorities?
-### Yes, type `/` and pick your favorite todo keyword or priority (A/B/C).
-### NOW [#A] A dummy tutorial on \"How to take dummy notes?\"
-### LATER [#A] Check out this awesome video by [:a {:href \"https://twitter.com/EdTravelling\" :target \"_blank\"} \"@EdTravelling\"], which shows how to use logseq to open your local directory.
-
-[:div.video-wrapper.mb-4
-        [:iframe
-         {:allowFullScreen \"allowfullscreen\"
-          :allow
-          \"accelerometer; autoplay; encrypted-media; gyroscope\"
-        :frameBorder \"0\"
-        :src \"https://www.youtube.com/embed/Afmqowr0qEQ\"
-        :height \"367\"
-        :width \"653\"}]]
-### DONE Create a page
-### CANCELED [#C] Write a page with more than 1000 blocks
-## That's it! You can create more bullets or open a local directory to import some notes now!
-## You can also download our desktop app at https://github.com/logseq/logseq/releases
-"
-        :tutorial/dummy-notes "---
-title: How to take dummy notes?
----
-
-## Hello, I'm a block!
-:PROPERTIES:
-:id: 5f713e91-8a3c-4b04-a33a-c39482428e2d
-:END:
-### I'm a child block!
-### I'm another child block!
-## Hey, I'm another block!
-:PROPERTIES:
-:id: 5f713ea8-8cba-403d-ac00-9964b1ec7190
-:END:
-"
+  {:en {:tutorial/text (rc/inline "tutorial-en.md")
+        :tutorial/dummy-notes (rc/inline "dummy-notes-en.md")
         :on-boarding/title "Hi, welcome to Logseq!"
         :on-boarding/title "Hi, welcome to Logseq!"
         :on-boarding/sharing "sharing"
         :on-boarding/sharing "sharing"
         :on-boarding/is-a " is a "
         :on-boarding/is-a " is a "
-        :on-boarding/vision "A privacy-first, open-source platform for knowledge sharing and management."
+        :on-boarding/vision "A privacy-first, open-source platform for knowledge management and collaboration."
         :on-boarding/local-first "local-first"
         :on-boarding/local-first "local-first"
         :on-boarding/non-linear "non-linear"
         :on-boarding/non-linear "non-linear"
         :on-boarding/outliner "outliner"
         :on-boarding/outliner "outliner"
@@ -134,42 +73,15 @@ title: How to take dummy notes?
         :help/block-reference "Block Reference"
         :help/block-reference "Block Reference"
         :help/key-commands "Key Commands"
         :help/key-commands "Key Commands"
         :help/working-with-lists " (working with lists)"
         :help/working-with-lists " (working with lists)"
-        :help/indent-block-tab "Indent Block Tab"
-        :help/unindent-block "Unindent Block"
-        :help/move-block-up "Move Block Up"
-        :help/move-block-down "Move Block Down"
-        :help/create-new-block "Create New Block"
-        :help/new-line-in-block "New Line in Block"
         :help/select-nfs-browser "Please use another browser (like latest chrome) which support NFS features to open local directory."
         :help/select-nfs-browser "Please use another browser (like latest chrome) which support NFS features to open local directory."
         :undo "Undo"
         :undo "Undo"
         :redo "Redo"
         :redo "Redo"
-        :help/zoom-in "Zoom In/Forward"
-        :help/zoom-out "Zoom out/Back"
-        :help/follow-link-under-cursor "Follow link under cursor"
-        :help/open-link-in-sidebar "Open link in Sidebar"
-        :expand "Expand"
-        :collapse "Collapse"
-        :select-block-above "Select Block Above"
-        :select-block-below "Select Block Below"
-        :select-all-blocks "Select All Blocks"
         :general "General"
         :general "General"
         :more "More"
         :more "More"
         :search/result-for "Search result for "
         :search/result-for "Search result for "
         :search/items "items"
         :search/items "items"
-        :help/toggle "Toggle help"
-        :help/git-commit-message "Git commit message"
-        :help/full-text-search "Full Text Search"
-        :help/page-search "Search in the current page"
         :help/context-menu "Context Menu"
         :help/context-menu "Context Menu"
         :help/fold-unfold "Fold/Unfold blocks (when not in edit mode)"
         :help/fold-unfold "Fold/Unfold blocks (when not in edit mode)"
-        :help/toggle-doc-mode "Toggle document mode"
-        :help/toggle-contents "Toggle Contents"
-        :help/toggle-theme "Toggle between dark/light theme"
-        :help/toggle-right-sidebar "Toggle right sidebar"
-        :help/toggle-settings "Toggle settings"
-        :help/toggle-insert-new-block "Toggle Enter/Alt+Enter for inserting new block"
-        :help/jump-to-journals "Jump to Journals"
-        :formatting "Formatting"
         :help/markdown-syntax "Markdown syntax"
         :help/markdown-syntax "Markdown syntax"
         :help/org-mode-syntax "Org mode syntax"
         :help/org-mode-syntax "Org mode syntax"
         :bold "Bold"
         :bold "Bold"
@@ -222,12 +134,11 @@ title: How to take dummy notes?
         :project/location "All published pages will be located under"
         :project/location "All published pages will be located under"
         :project/sync-settings "Sync project settings"
         :project/sync-settings "Sync project settings"
         :page/presentation-mode "Presentation mode (Powered by Reveal.js)"
         :page/presentation-mode "Presentation mode (Powered by Reveal.js)"
-        :page/edit-properties-placeholder "Click here to edit this page's properties"
+        :page/edit-properties-placeholder "Properties"
         :page/delete-success "Page {1} was deleted successfully!"
         :page/delete-success "Page {1} was deleted successfully!"
         :page/delete-confirmation "Are you sure you want to delete this page and its file?"
         :page/delete-confirmation "Are you sure you want to delete this page and its file?"
         :page/rename-to "Rename \"{1}\" to:"
         :page/rename-to "Rename \"{1}\" to:"
         :page/priority "Priority \"{1}\""
         :page/priority "Priority \"{1}\""
-        :page/re-index "Re-index this page"
         :page/copy-to-json "Copy the whole page as JSON"
         :page/copy-to-json "Copy the whole page as JSON"
         :page/rename "Rename page"
         :page/rename "Rename page"
         :page/open-in-finder "Open in directory"
         :page/open-in-finder "Open in directory"
@@ -244,7 +155,7 @@ title: How to take dummy notes?
         :page/show-journals "Show Journals"
         :page/show-journals "Show Journals"
         :page/show-name "Show page name"
         :page/show-name "Show page name"
         :page/hide-name "Hide page name"
         :page/hide-name "Hide page name"
-        :page/name "Page name"
+        :block/name "Page name"
         :page/last-modified "Last modified at"
         :page/last-modified "Last modified at"
         :page/new-title "What's your new page title?"
         :page/new-title "What's your new page title?"
         :page/earlier "Earlier"
         :page/earlier "Earlier"
@@ -291,6 +202,8 @@ title: How to take dummy notes?
         :content/click-to-edit "Click to edit"
         :content/click-to-edit "Click to edit"
         :settings-page/edit-config-edn "Edit config.edn (for current repo)"
         :settings-page/edit-config-edn "Edit config.edn (for current repo)"
         :settings-page/show-brackets "Show brackets"
         :settings-page/show-brackets "Show brackets"
+        :settings-page/disable-sentry "Disable Sentry.io (for error tracking)"
+        :settings-page/preferred-outdenting "Enable logical outdenting"
         :settings-page/custom-date-format "Preferred journal format"
         :settings-page/custom-date-format "Preferred journal format"
         :settings-page/preferred-file-format "Preferred file format"
         :settings-page/preferred-file-format "Preferred file format"
         :settings-page/preferred-workflow "Preferred workflow"
         :settings-page/preferred-workflow "Preferred workflow"
@@ -300,6 +213,8 @@ title: How to take dummy notes?
         :settings-page/home-default-page "Set the default home page"
         :settings-page/home-default-page "Set the default home page"
         :settings-page/enable-block-time "Enable block timestamps"
         :settings-page/enable-block-time "Enable block timestamps"
         :settings-page/dont-use-other-peoples-proxy-servers "Don't use other people's proxy servers. It's very dangerous, which could make your token and notes stolen. Logseq will not be responsible for this loss if you use other people's proxy servers. You can deploy it yourself, check "
         :settings-page/dont-use-other-peoples-proxy-servers "Don't use other people's proxy servers. It's very dangerous, which could make your token and notes stolen. Logseq will not be responsible for this loss if you use other people's proxy servers. You can deploy it yourself, check "
+        :settings-page/clear-cache "Clear cache"
+        :settings-page/clear "Clear"
         :settings-page/custom-cors-proxy-server "Custom CORS proxy server"
         :settings-page/custom-cors-proxy-server "Custom CORS proxy server"
         :settings-page/developer-mode "Developer mode"
         :settings-page/developer-mode "Developer mode"
         :settings-page/enable-developer-mode "Enable developer mode"
         :settings-page/enable-developer-mode "Enable developer mode"
@@ -331,6 +246,7 @@ title: How to take dummy notes?
         :export-markdown "Export as Markdown"
         :export-markdown "Export as Markdown"
         :export-public-pages "Export public pages"
         :export-public-pages "Export public pages"
         :export-edn "Export as EDN"
         :export-edn "Export as EDN"
+        :convert-markdown "Convert Markdown headings to unordered lists (# -> -)"
         :all-graphs "All graphs"
         :all-graphs "All graphs"
         :all-pages "All pages"
         :all-pages "All pages"
         :all-files "All files"
         :all-files "All files"
@@ -362,7 +278,9 @@ title: How to take dummy notes?
         :open-a-directory "Open a local directory"
         :open-a-directory "Open a local directory"
         :user/delete-account "Delete account"
         :user/delete-account "Delete account"
         :user/delete-your-account "Delete your account"
         :user/delete-your-account "Delete your account"
-        :user/delete-account-notice "All your published pages on logseq.com will be deleted."}
+        :user/delete-account-notice "All your published pages on logseq.com will be deleted."
+
+        :help/shortcut-page-title "Learn & Customize Shortcuts"}
 
 
    :de {:help/about "Über Logseq"
    :de {:help/about "Über Logseq"
         :help/bug "Fehlerbericht"
         :help/bug "Fehlerbericht"
@@ -382,36 +300,12 @@ title: How to take dummy notes?
         :help/block-reference "Blockverweis"
         :help/block-reference "Blockverweis"
         :help/key-commands "Tastenbefehle"
         :help/key-commands "Tastenbefehle"
         :help/working-with-lists " (mit Listen arbeiten)"
         :help/working-with-lists " (mit Listen arbeiten)"
-        :help/indent-block-tab "Block einrücken"
-        :help/unindent-block "Block ausrücken"
-        :help/move-block-up "Block nach oben verschieben"
-        :help/move-block-down "Block nach unten verschieben"
-        :help/create-new-block "Neuen Block erstellen"
-        :help/new-line-in-block "Neue Zeile innerhalb des Blocks erstellen"
         :help/select-nfs-browser "Bitte einen anderen Browser verwenden (z. B. den neuesten Chrome), der NFS-Funktionen unterstützt, um lokale Verzeichnisse zu öffnen."
         :help/select-nfs-browser "Bitte einen anderen Browser verwenden (z. B. den neuesten Chrome), der NFS-Funktionen unterstützt, um lokale Verzeichnisse zu öffnen."
         :undo "Rückgängig machen"
         :undo "Rückgängig machen"
         :redo "Wiederholen"
         :redo "Wiederholen"
-        :help/zoom-in "Heranzoomen"
-        :help/zoom-out "Herauszoomen"
-        :help/follow-link-under-cursor "Link unter dem Cursor folgen"
-        :help/open-link-in-sidebar "Link in Seitenleiste öffnen"
-        :expand "Erweitern"
-        :collapse "Zusammenklappen"
-        :select-block-above "Block oberhalb auswählen"
-        :select-block-below "Block unterhalb auswählen"
-        :select-all-blocks "Alle Blöcke auswählen"
         :general "Allgemein"
         :general "Allgemein"
-        :help/toggle "Hilfe aktivieren"
-        :help/git-commit-message "Git Commit-Nachricht"
-        :help/full-text-search "Volltextsuche"
         :help/context-menu "Kontextmenü"
         :help/context-menu "Kontextmenü"
         :help/fold-unfold "Blöcke ein-/ausklappen (wenn nicht im Bearbeitungsmodus)"
         :help/fold-unfold "Blöcke ein-/ausklappen (wenn nicht im Bearbeitungsmodus)"
-        :help/toggle-doc-mode "Dokumentenmodus umschalten"
-        :help/toggle-theme "Umschalten zwischen dunklem/hellem Thema"
-        :help/toggle-right-sidebar "Rechte Seitenleiste umschalten"
-        :help/toggle-insert-new-block "Umschalten von Enter/Alt+Enter zum Einfügen eines neuen Blocks"
-        :help/jump-to-journals "Zu Journalen springen"
-        :formatting "Formatierung"
         :help/markdown-syntax "Markdown-Syntax"
         :help/markdown-syntax "Markdown-Syntax"
         :help/org-mode-syntax "Org-Mode-Syntax"
         :help/org-mode-syntax "Org-Mode-Syntax"
         :bold "Fett"
         :bold "Fett"
@@ -468,7 +362,6 @@ title: How to take dummy notes?
         :page/delete-confirmation "Diese Seite und die zugehörige Datei löschen?"
         :page/delete-confirmation "Diese Seite und die zugehörige Datei löschen?"
         :page/rename-to "\"{1}\" umbenennen nach:"
         :page/rename-to "\"{1}\" umbenennen nach:"
         :page/priority "Priorität \"{1}\""
         :page/priority "Priorität \"{1}\""
-        :page/re-index "Diese Seite neu indizieren"
         :page/copy-to-json "Gesamte Seite als JSON kopieren"
         :page/copy-to-json "Gesamte Seite als JSON kopieren"
         :page/rename "Seite umbenennen"
         :page/rename "Seite umbenennen"
         :page/open-in-finder "Im Verzeichnis öffnen"
         :page/open-in-finder "Im Verzeichnis öffnen"
@@ -485,7 +378,7 @@ title: How to take dummy notes?
         :page/show-journals "Journal anzeigen"
         :page/show-journals "Journal anzeigen"
         :page/show-name "Seitennamen anzeigen"
         :page/show-name "Seitennamen anzeigen"
         :page/hide-name "Seitennamen verbergen"
         :page/hide-name "Seitennamen verbergen"
-        :page/name "Seitenname"
+        :block/name "Seitenname"
         :page/last-modified "Zuletzt geändert am"
         :page/last-modified "Zuletzt geändert am"
         :page/new-title "Wie lautet der neue Seitenname?"
         :page/new-title "Wie lautet der neue Seitenname?"
         :publishing/pages "Seiten"
         :publishing/pages "Seiten"
@@ -609,36 +502,12 @@ title: How to take dummy notes?
         :help/block-reference "Référence à un Bloc"
         :help/block-reference "Référence à un Bloc"
         :help/key-commands "Key Commands"
         :help/key-commands "Key Commands"
         :help/working-with-lists " (working with lists)"
         :help/working-with-lists " (working with lists)"
-        :help/indent-block-tab "Indenter un Bloc vers la droite"
-        :help/unindent-block "Indenter un Bloc vers la gauche"
-        :help/move-block-up "Déplacer un bloc au dessus"
-        :help/move-block-down "Déplacer un bloc en dessous"
-        :help/create-new-block "Créer un nouveau bloc"
-        :help/new-line-in-block "Aller à la ligne dans un bloc"
         :help/select-nfs-browser "Please use another browser (like latest chrome) which support NFS features to open local directory."
         :help/select-nfs-browser "Please use another browser (like latest chrome) which support NFS features to open local directory."
         :undo "Annuler"
         :undo "Annuler"
         :redo "Redo"
         :redo "Redo"
-        :help/zoom-in "Zoomer"
-        :help/zoom-out "Dézoomer"
-        :help/follow-link-under-cursor "Suivre le lien sous le curseur"
-        :help/open-link-in-sidebar "Ouvrir le lien dans la barre latérale"
-        :expand "Etendre"
-        :collapse "Réduire"
-        :select-block-above "Sélectionner le bloc au dessus"
-        :select-block-below "Sélectionner le bloc en dessous"
-        :select-all-blocks "Sélectionner tous les blocs"
         :general "Général"
         :general "Général"
-        :help/toggle "Afficher l'aide"
-        :help/git-commit-message "Message de commit Git"
-        :help/full-text-search "Recherche globale dans le texte"
         :help/context-menu "Menu contextuel"
         :help/context-menu "Menu contextuel"
         :help/fold-unfold "Plier/Déplier les blocs (hors mode édition)"
         :help/fold-unfold "Plier/Déplier les blocs (hors mode édition)"
-        :help/toggle-doc-mode "Intervertir le mode document"
-        :help/toggle-theme "Intervertir le thème foncé/clair"
-        :help/toggle-right-sidebar "Afficher/cacher la barre latérale"
-        :help/toggle-insert-new-block "Activer Entreée ou Alt+Enter pour insérer un bloc"
-        :help/jump-to-journals "Aller au Journal"
-        :formatting "Formats"
         :help/markdown-syntax "Syntaxe Markdown"
         :help/markdown-syntax "Syntaxe Markdown"
         :help/org-mode-syntax "Syntaxe Org mode"
         :help/org-mode-syntax "Syntaxe Org mode"
         :bold "Gras"
         :bold "Gras"
@@ -694,7 +563,6 @@ title: How to take dummy notes?
         :page/delete-confirmation "Etes-vous sûr de vouloir supprimer la page ?"
         :page/delete-confirmation "Etes-vous sûr de vouloir supprimer la page ?"
         :page/rename-to "Renommer \"{1}\" en:"
         :page/rename-to "Renommer \"{1}\" en:"
         :page/priority "Priorité \"{1}\""
         :page/priority "Priorité \"{1}\""
-        :page/re-index "Indexer à nouveau cette page"
         :page/copy-to-json "Copier la page au format JSON"
         :page/copy-to-json "Copier la page au format JSON"
         :page/rename "Renommer la page"
         :page/rename "Renommer la page"
         :page/make-public "Rendre la page publique"
         :page/make-public "Rendre la page publique"
@@ -708,7 +576,7 @@ title: How to take dummy notes?
         :page/show-journals "Afficher le Journal"
         :page/show-journals "Afficher le Journal"
         :page/show-name "Afficher le nom de la page"
         :page/show-name "Afficher le nom de la page"
         :page/hide-name "Cacher le nom de la page"
         :page/hide-name "Cacher le nom de la page"
-        :page/name "Nom de la page"
+        :block/name "Nom de la page"
         :page/last-modified "Dernières modifications à"
         :page/last-modified "Dernières modifications à"
         :page/new-title "Quel est le nouveau titre de la page ?"
         :page/new-title "Quel est le nouveau titre de la page ?"
         :publishing/pages "Pages"
         :publishing/pages "Pages"
@@ -858,51 +726,25 @@ title: How to take dummy notes?
            :help/shortcuts "快捷键"
            :help/shortcuts "快捷键"
            :help/shortcuts-triggers "触发"
            :help/shortcuts-triggers "触发"
            :help/shortcut "快捷键"
            :help/shortcut "快捷键"
+           :help/shortcut-page-title "完整快捷键"
            :help/slash-autocomplete "Slash 自动提示"
            :help/slash-autocomplete "Slash 自动提示"
            :help/block-content-autocomplete "块内容 (Src, Quote, Query 等) 自动完成"
            :help/block-content-autocomplete "块内容 (Src, Quote, Query 等) 自动完成"
            :help/reference-autocomplete "页面引用自动补全"
            :help/reference-autocomplete "页面引用自动补全"
            :help/block-reference "块引用"
            :help/block-reference "块引用"
            :help/key-commands "关键命令"
            :help/key-commands "关键命令"
            :help/working-with-lists " (与列表相关)"
            :help/working-with-lists " (与列表相关)"
-           :help/indent-block-tab "缩进块标签"
-           :help/unindent-block "取消缩进块"
-           :help/move-block-up "向上移动块"
-           :help/move-block-down "向下移动块"
-           :help/create-new-block "创建块"
-           :help/new-line-in-block "块中新建行"
            :help/select-nfs-browser "请选择支持nfs的浏览来使用logseq本地文件夹功能, 如最新的Chrome浏览器."
            :help/select-nfs-browser "请选择支持nfs的浏览来使用logseq本地文件夹功能, 如最新的Chrome浏览器."
            :text/image "图片"
            :text/image "图片"
            :asset/confirm-delete "确定要删除{1}吗?"
            :asset/confirm-delete "确定要删除{1}吗?"
            :asset/physical-delete "同时删除本地文件(目前不可撤销)"
            :asset/physical-delete "同时删除本地文件(目前不可撤销)"
            :undo "撤销"
            :undo "撤销"
            :redo "重做"
            :redo "重做"
-           :help/zoom-in "聚焦"
-           :help/zoom-out "退出聚焦"
-           :help/follow-link-under-cursor "跟随光标下的链接"
-           :help/open-link-in-sidebar "在侧边栏打开"
-           :expand "展开"
-           :collapse "折叠"
-           :select-block-above "选择上方的块"
-           :select-block-below "选择下方的块"
-           :select-all-blocks "选择所有块"
            :general "常规​​​​​"
            :general "常规​​​​​"
            :more "更多"
            :more "更多"
            :search/result-for "更多搜索结果 "
            :search/result-for "更多搜索结果 "
            :search/items "条目"
            :search/items "条目"
-           :help/toggle "显示/关闭帮助"
-           :help/git-commit-message "提交消息"
-           :help/full-text-search "全文搜索"
-           :help/page-search "在当前页面搜索"
            :help/context-menu "右键菜单"
            :help/context-menu "右键菜单"
            :help/fold-unfold "折叠/展开方块(不在编辑模式中)"
            :help/fold-unfold "折叠/展开方块(不在编辑模式中)"
-           :help/toggle-doc-mode "切换文档模式"
-           :help/toggle-contents "打开/关闭目录"
-           :help/toggle-theme "“在暗色/亮色主题之间切换”"
-           :help/toggle-right-sidebar "启用/关闭右侧栏"
-           :help/toggle-settings "显示/关闭设置"
-           :help/toggle-insert-new-block "切换 Enter/Alt+Enter 以插入新块"
-           :help/jump-to-journals "跳转到日记"
-           :formatting "格式化"
            :help/markdown-syntax "Markdown 语法"
            :help/markdown-syntax "Markdown 语法"
            :help/org-mode-syntax "Org Mode 语法"
            :help/org-mode-syntax "Org Mode 语法"
            :bold "粗体"
            :bold "粗体"
@@ -960,7 +802,6 @@ title: How to take dummy notes?
            :page/delete-confirmation "您确定要删除此页面和文件吗?"
            :page/delete-confirmation "您确定要删除此页面和文件吗?"
            :page/rename-to "重命名 \"{1}\" 至:"
            :page/rename-to "重命名 \"{1}\" 至:"
            :page/priority "优先级 \"{1}\""
            :page/priority "优先级 \"{1}\""
-           :page/re-index "对此页面重新建立索引"
            :page/copy-to-json "将整页以 JSON 格式复制"
            :page/copy-to-json "将整页以 JSON 格式复制"
            :page/rename "重命名本页"
            :page/rename "重命名本页"
            :page/open-in-finder "打开文件对应目录"
            :page/open-in-finder "打开文件对应目录"
@@ -977,7 +818,7 @@ title: How to take dummy notes?
            :page/show-journals "显示日志"
            :page/show-journals "显示日志"
            :page/show-name "显示页面名"
            :page/show-name "显示页面名"
            :page/hide-name "隐藏页面名"
            :page/hide-name "隐藏页面名"
-           :page/name "页面名称"
+           :block/name "页面名称"
            :page/last-modified "最后更改于"
            :page/last-modified "最后更改于"
            :page/new-title "请输入新页面的名字:"
            :page/new-title "请输入新页面的名字:"
            :page/earlier "之前"
            :page/earlier "之前"
@@ -1046,6 +887,7 @@ title: How to take dummy notes?
            :re-index "重新建立索引"
            :re-index "重新建立索引"
            :export-json "以 JSON 格式导出"
            :export-json "以 JSON 格式导出"
            :export-markdown "以 Markdown 格式导出"
            :export-markdown "以 Markdown 格式导出"
+           :convert-markdown "转换 Markdown 格式(Unordered list 或 Heading)"
            :unlink "解除绑定"
            :unlink "解除绑定"
            :search (if config/publishing?
            :search (if config/publishing?
                      "搜索"
                      "搜索"
@@ -1089,6 +931,7 @@ title: How to take dummy notes?
            :user/delete-your-account "删除你的帐号"
            :user/delete-your-account "删除你的帐号"
            :user/delete-account-notice "你在 logseq.com 发布的页面(假如有的话)也会被删除。"}
            :user/delete-account-notice "你在 logseq.com 发布的页面(假如有的话)也会被删除。"}
 
 
+
    :zh-Hant {:on-boarding/title "你好,歡迎使用 Logseq!"
    :zh-Hant {:on-boarding/title "你好,歡迎使用 Logseq!"
              :on-boarding/sharing "分享"
              :on-boarding/sharing "分享"
              :on-boarding/is-a " 是一個 "
              :on-boarding/is-a " 是一個 "
@@ -1153,36 +996,12 @@ title: How to take dummy notes?
              :help/block-reference "塊引用"
              :help/block-reference "塊引用"
              :help/key-commands "關鍵命令"
              :help/key-commands "關鍵命令"
              :help/working-with-lists " (與列表相關)"
              :help/working-with-lists " (與列表相關)"
-             :help/indent-block-tab "縮進塊標簽"
-             :help/unindent-block "取消縮進塊"
-             :help/move-block-up "向上移動塊"
-             :help/move-block-down "向下移動塊"
-             :help/create-new-block "創建塊"
-             :help/new-line-in-block "塊中新建行"
              :help/select-nfs-browser "Please use another browser (like latest chrome) which support NFS features to open local directory."
              :help/select-nfs-browser "Please use another browser (like latest chrome) which support NFS features to open local directory."
              :undo "撤銷"
              :undo "撤銷"
              :redo "重做"
              :redo "重做"
-             :help/zoom-in "聚焦"
-             :help/zoom-out "推出聚焦"
-             :help/follow-link-under-cursor "跟隨光標下的鏈接"
-             :help/open-link-in-sidebar "在側邊欄打開"
-             :expand "展開"
-             :collapse "折疊"
-             :select-block-above "選擇上方的塊"
-             :select-block-below "選擇下方的塊"
-             :select-all-blocks "選擇所有塊"
              :general "常規​​​​​"
              :general "常規​​​​​"
-             :help/toggle "顯示/關閉幫助"
-             :help/git-commit-message "提交消息"
-             :help/full-text-search "全文搜索"
              :help/context-menu "右鍵菜單"
              :help/context-menu "右鍵菜單"
              :help/fold-unfold "折疊/展開方塊(不在編輯模式中)"
              :help/fold-unfold "折疊/展開方塊(不在編輯模式中)"
-             :help/toggle-doc-mode "切換文檔模式"
-             :help/toggle-theme "“在暗色/亮色主題之間切換”"
-             :help/toggle-right-sidebar "啟用/關閉右側欄"
-             :help/toggle-insert-new-block "切換 Enter/Alt+Enter 以插入新塊"
-             :help/jump-to-journals "跳轉到日記"
-             :formatting "格式化"
              :help/markdown-syntax "Markdown 語法"
              :help/markdown-syntax "Markdown 語法"
              :help/org-mode-syntax "Org Mode 語法"
              :help/org-mode-syntax "Org Mode 語法"
              :bold "粗體"
              :bold "粗體"
@@ -1237,7 +1056,6 @@ title: How to take dummy notes?
              :page/delete-confirmation "您確定要刪除此頁面嗎?"
              :page/delete-confirmation "您確定要刪除此頁面嗎?"
              :page/rename-to "重命名 \"{1}\" 至:"
              :page/rename-to "重命名 \"{1}\" 至:"
              :page/priority "優先級 \"{1}\""
              :page/priority "優先級 \"{1}\""
-             :page/re-index "對此頁面重新建立索引"
              :page/copy-to-json "將整頁以 JSON 格式復制"
              :page/copy-to-json "將整頁以 JSON 格式復制"
              :page/rename "重命名本頁"
              :page/rename "重命名本頁"
              :page/make-public "導出 HTML 時發布本頁面"
              :page/make-public "導出 HTML 時發布本頁面"
@@ -1251,7 +1069,7 @@ title: How to take dummy notes?
              :page/show-journals "顯示日志"
              :page/show-journals "顯示日志"
              :page/show-name "顯示頁面名"
              :page/show-name "顯示頁面名"
              :page/hide-name "隱藏頁面名"
              :page/hide-name "隱藏頁面名"
-             :page/name "頁面名稱:"
+             :block/name "頁面名稱:"
              :page/last-modified "最後更改於"
              :page/last-modified "最後更改於"
              :page/new-title "請輸入新頁面的名字:"
              :page/new-title "請輸入新頁面的名字:"
              :page/load-more-journals "載入更多"
              :page/load-more-journals "載入更多"
@@ -1312,6 +1130,7 @@ title: How to take dummy notes?
              :re-index "重新建立索引"
              :re-index "重新建立索引"
              :export-json "以 JSON 格式導出"
              :export-json "以 JSON 格式導出"
              :export-markdown "以 Markdown 格式導出"
              :export-markdown "以 Markdown 格式導出"
+             :convert-markdown "轉換 Markdown 格式(Unordered list 或 Heading)"
              :unlink "解除綁定"
              :unlink "解除綁定"
              :search (if config/publishing?
              :search (if config/publishing?
                        "搜索"
                        "搜索"
@@ -1406,36 +1225,12 @@ title: How to take dummy notes?
         :help/block-reference "Blok verwysing"
         :help/block-reference "Blok verwysing"
         :help/key-commands "Sleutel instruksies"
         :help/key-commands "Sleutel instruksies"
         :help/working-with-lists " (werk met lyste)"
         :help/working-with-lists " (werk met lyste)"
-        :help/indent-block-tab "Ingekeepte blok oortjie"
-        :help/unindent-block "Oningekeepte blok"
-        :help/move-block-up "Skuif Blok Boontoe"
-        :help/move-block-down "Skuif Blok Ondertoe"
-        :help/create-new-block "Skep 'n nuwe blok"
-        :help/new-line-in-block "Nuwe lyn in blok"
         :help/select-nfs-browser "Please use another browser (like latest chrome) which support NFS features to open local directory."
         :help/select-nfs-browser "Please use another browser (like latest chrome) which support NFS features to open local directory."
         :undo "Ontdoen"
         :undo "Ontdoen"
         :redo "Herdoen"
         :redo "Herdoen"
-        :help/zoom-in "Zoem in"
-        :help/zoom-out "Zoem uit"
-        :help/follow-link-under-cursor "Volg die skakel onder die wyser"
-        :help/open-link-in-sidebar "Maak skakel in kantlys oop"
-        :expand "Brei uit"
-        :collapse "Vou in"
-        :select-block-above "Kies blok bo"
-        :select-block-below "Kies blok onder"
-        :select-all-blocks "Kies alle blokke"
         :general "Algemeen"
         :general "Algemeen"
-        :help/toggle "Wissel help"
-        :help/git-commit-message "Jou git stoor boodskap"
-        :help/full-text-search "Volteks soek"
         :help/context-menu "Konteks kaart"
         :help/context-menu "Konteks kaart"
         :help/fold-unfold "Vou/ontvou blokke (wanneer nie in wysigings modus)"
         :help/fold-unfold "Vou/ontvou blokke (wanneer nie in wysigings modus)"
-        :help/toggle-doc-mode "Wissel dokument modus"
-        :help/toggle-theme "Wissel tussen donker/lig temas"
-        :help/toggle-right-sidebar "Wissel regter sybalk"
-        :help/toggle-insert-new-block "Wissel Enter/Alt+enter vir die byvoeging van nuwe blokke"
-        :help/jump-to-journals "Spring na joernale"
-        :formatting "Formatering"
         :help/markdown-syntax "Markdown sintaksis"
         :help/markdown-syntax "Markdown sintaksis"
         :help/org-mode-syntax "Org mode sintaksis"
         :help/org-mode-syntax "Org mode sintaksis"
         :bold "Vetdruk"
         :bold "Vetdruk"
@@ -1490,7 +1285,6 @@ title: How to take dummy notes?
         :page/delete-confirmation "Is jy seker jy wil die bladsy uitvee?"
         :page/delete-confirmation "Is jy seker jy wil die bladsy uitvee?"
         :page/rename-to "Hernoem \"{1}\" na:"
         :page/rename-to "Hernoem \"{1}\" na:"
         :page/priority "Prioriteit \"{1}\""
         :page/priority "Prioriteit \"{1}\""
-        :page/re-index "Re-index this page"
         :page/copy-to-json "Kopieer die hele bladsy as JSON"
         :page/copy-to-json "Kopieer die hele bladsy as JSON"
         :page/rename "Hernoem die bladsy"
         :page/rename "Hernoem die bladsy"
         :page/delete "Delete page (will delete the file too)"
         :page/delete "Delete page (will delete the file too)"
@@ -1502,7 +1296,7 @@ title: How to take dummy notes?
         :page/show-journals "Wys joernale"
         :page/show-journals "Wys joernale"
         :page/show-name "Wys blad naam"
         :page/show-name "Wys blad naam"
         :page/hide-name "Steek bladnaam weg"
         :page/hide-name "Steek bladnaam weg"
-        :page/name "Page name"
+        :block/name "Page name"
         :page/last-modified "Laaste verander op"
         :page/last-modified "Laaste verander op"
         :page/new-title "Wat is die nuwe blad se titel?"
         :page/new-title "Wat is die nuwe blad se titel?"
         :publishing/pages "Pages"
         :publishing/pages "Pages"
@@ -1569,7 +1363,6 @@ title: How to take dummy notes?
         :settings "Verstellings"
         :settings "Verstellings"
         :import "Invoer"
         :import "Invoer"
         :join-community "Sluit by die gemeenskap aan"
         :join-community "Sluit by die gemeenskap aan"
-        :discord-title "Ons discord groep!"
         :sign-out "Teken af"
         :sign-out "Teken af"
         :help-shortcut-title "Kliek op die kortpad en ander wenke"
         :help-shortcut-title "Kliek op die kortpad en ander wenke"
         :loading "Laai tans"
         :loading "Laai tans"
@@ -1594,5 +1387,5 @@ title: How to take dummy notes?
                 {:label "繁體中文" :value :zh-Hant}
                 {:label "繁體中文" :value :zh-Hant}
                 {:label "Afrikaans" :value :af}])
                 {:label "Afrikaans" :value :af}])
 
 
-(def translate
+(defn translate [dicts]
   (tongue/build-translate dicts))
   (tongue/build-translate dicts))

+ 50 - 58
src/main/frontend/extensions/code.cljs

@@ -3,7 +3,6 @@
             [frontend.util :as util]
             [frontend.util :as util]
             [goog.dom :as gdom]
             [goog.dom :as gdom]
             [goog.object :as gobj]
             [goog.object :as gobj]
-            [medley.core :as medley]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor :as editor-handler]
@@ -52,43 +51,37 @@
   [editor textarea config state]
   [editor textarea config state]
   (.save editor)
   (.save editor)
   (let [value (gobj/get textarea "value")
   (let [value (gobj/get textarea "value")
-        default-value (gobj/get textarea "defaultValue")
-        pos-meta (:pos-meta state)]
+        default-value (gobj/get textarea "defaultValue")]
     (when (not= value default-value)
     (when (not= value default-value)
       (cond
       (cond
-       (:block/uuid config)
-       (let [block (db/pull [:block/uuid (:block/uuid config)])
-             format (:block/format block)
-             content (:block/content block)
-             {:keys [start_pos end_pos]} @pos-meta
-             prev-content (utf8/substring (utf8/encode content)
-                                          0 start_pos)
-             value (str (if (not= "\n" (last prev-content))
-                          "\n")
-                        (string/trimr value)
-                        "\n")
-             content' (utf8/insert! content start_pos end_pos value)]
-         (state/set-editor-op! :code-editor)
-         (editor-handler/save-block-if-changed! block content')
-         (let [new-pos-meta {:start_pos start_pos
-                             :end_pos (+ start_pos
-                                         (utf8/length (utf8/encode value)))}
-               old-pos-meta @pos-meta]
-           (reset! pos-meta new-pos-meta)))
+        (:block/uuid config)
+        (let [block (db/pull [:block/uuid (:block/uuid config)])
+              format (:block/format block)
+              content (:block/content block)
+              full-content (:full_content (last (:rum/args state)))]
+          (when (and full-content (string/includes? content full-content))
+            (let [lines (string/split-lines full-content)
+                  fl (first lines)
+                  ll (last lines)]
+              (when (and fl ll)
+                (let [value' (str fl "\n" value "\n" ll)
+                      ;; FIXME: What if there're multiple code blocks with the same value?
+                      content' (string/replace-first content full-content value')]
+                  (editor-handler/save-block-if-changed! block content'))))))
 
 
-       (:file-path config)
-       (let [path (:file-path config)
-             content (db/get-file-no-sub path)
-             value (some-> (gdom/getElement path)
-                           (gobj/get "value"))]
-         (when (and
-                (not (string/blank? value))
-                (not= (string/trim value) (string/trim content)))
-           (file-handler/alter-file (state/get-current-repo) path (string/trim value)
-                                    {:re-render-root? true})))
+        (:file-path config)
+        (let [path (:file-path config)
+              content (db/get-file-no-sub path)
+              value (some-> (gdom/getElement path)
+                            (gobj/get "value"))]
+          (when (and
+                 (not (string/blank? value))
+                 (not= (string/trim value) (string/trim content)))
+            (file-handler/alter-file (state/get-current-repo) path (string/trim value)
+                                     {:re-render-root? true})))
 
 
-       :else
-       nil))))
+        :else
+        nil))))
 
 
 (defn- text->cm-mode
 (defn- text->cm-mode
   [text]
   [text]
@@ -116,7 +109,10 @@
   (let [editor-atom (:editor-atom state)
   (let [editor-atom (:editor-atom state)
         esc-pressed? (atom nil)]
         esc-pressed? (atom nil)]
     (if @editor-atom
     (if @editor-atom
-      @editor-atom
+      (let [editor @editor-atom
+            doc (.getDoc editor)
+            code (nth (:rum/args state) 3)]
+        (.setValue doc code))
       (let [[config id attr code] (:rum/args state)
       (let [[config id attr code] (:rum/args state)
             original-mode (get attr :data-lang)
             original-mode (get attr :data-lang)
             mode (or original-mode "javascript")
             mode (or original-mode "javascript")
@@ -135,16 +131,12 @@
                                           :lineNumbers true
                                           :lineNumbers true
                                           :styleActiveLine true
                                           :styleActiveLine true
                                           :extraKeys #js {"Esc" (fn [cm]
                                           :extraKeys #js {"Esc" (fn [cm]
-                                                                  (let [save! #(save-file-or-block-when-blur-or-esc! cm textarea config state)]
-                                                                    (if-let [block-id (:block/uuid config)]
-                                                                      (let [block (db/pull [:block/uuid block-id])
-                                                                            value (.getValue cm)
-                                                                            textarea-value (gobj/get textarea "value")
-                                                                            changed? (not= value textarea-value)]
-                                                                        (if changed?
-                                                                          (save!)
-                                                                          (editor-handler/edit-block! block :max (:block/format block) block-id)))
-                                                                      (save!)))
+                                                                  (save-file-or-block-when-blur-or-esc! cm textarea config state)
+                                                                  (when-let [block-id (:block/uuid config)]
+                                                                    (let [block (db/pull [:block/uuid block-id])
+                                                                          value (.getValue cm)
+                                                                          textarea-value (gobj/get textarea "value")]
+                                                                      (editor-handler/edit-block! block :max (:block/format block) block-id)))
                                                                   ;; TODO: return "handled" or false doesn't always prevent event bubbles
                                                                   ;; TODO: return "handled" or false doesn't always prevent event bubbles
                                                                   (reset! esc-pressed? true)
                                                                   (reset! esc-pressed? true)
                                                                   (js/setTimeout #(reset! esc-pressed? false) 10))}})))]
                                                                   (js/setTimeout #(reset! esc-pressed? false) 10))}})))]
@@ -152,35 +144,35 @@
           (let [element (.getWrapperElement editor)]
           (let [element (.getWrapperElement editor)]
             (.on editor "blur" (fn [_cm e]
             (.on editor "blur" (fn [_cm e]
                                  (util/stop e)
                                  (util/stop e)
+                                 (state/set-block-component-editing-mode! false)
                                  (when-not @esc-pressed?
                                  (when-not @esc-pressed?
                                    (save-file-or-block-when-blur-or-esc! editor textarea config state))))
                                    (save-file-or-block-when-blur-or-esc! editor textarea config state))))
-            (.addEventListener element "click"
+            (.addEventListener element "mousedown"
                                (fn [e]
                                (fn [e]
-                                 (util/stop e)))
+                                 (state/clear-selection!)
+                                 (util/stop e)
+                                 (state/set-block-component-editing-mode! true)))
             (.save editor)
             (.save editor)
             (.refresh editor)))
             (.refresh editor)))
         editor))))
         editor))))
 
 
 (defn- load-and-render!
 (defn- load-and-render!
   [state]
   [state]
-  (let [editor-atom (:editor-atom state)]
-    (let [editor (render! state)]
-      (reset! editor-atom editor))))
+  (let [editor-atom (:editor-atom state)
+        editor (render! state)]
+    (reset! editor-atom editor)))
 
 
 (rum/defcs editor < rum/reactive
 (rum/defcs editor < rum/reactive
   {:init (fn [state]
   {:init (fn [state]
-           (assoc state
-                  :pos-meta (atom (last (:rum/args state)))
-                  :editor-atom (atom nil)))
+           (assoc state :editor-atom (atom nil)))
    :did-mount (fn [state]
    :did-mount (fn [state]
                 (load-and-render! state)
                 (load-and-render! state)
                 state)
                 state)
-   :did-remount (fn [old_state state]
-                  (load-and-render! state)
-                  state)}
-  [state config id attr code pos-meta]
+   :did-update (fn [state]
+                 (load-and-render! state)
+                 state)}
+  [state config id attr code options]
   [:div.extensions__code
   [:div.extensions__code
-   {:on-mouse-down (fn [e] (util/stop e))}
    [:div.extensions__code-lang
    [:div.extensions__code-lang
     (let [mode (string/lower-case (get attr :data-lang "javascript"))]
     (let [mode (string/lower-case (get attr :data-lang "javascript"))]
       (if (= mode "text/x-clojure")
       (if (= mode "text/x-clojure")

+ 4 - 0
src/main/frontend/extensions/excalidraw.cljs

@@ -80,6 +80,10 @@
         [:a.mr-2 {:on-click #(swap! *view-mode? not)}
         [:a.mr-2 {:on-click #(swap! *view-mode? not)}
          (util/format "View Mode (%s)" (if @*view-mode? "ON" "OFF"))]]
          (util/format "View Mode (%s)" (if @*view-mode? "ON" "OFF"))]]
        [:div.draw-wrap
        [:div.draw-wrap
+        {:on-mouse-down (fn [e]
+                          (util/stop e)
+                          (state/set-block-component-editing-mode! true))
+         :on-blur #(state/set-block-component-editing-mode! false)}
         (excalidraw
         (excalidraw
          (merge
          (merge
           {:on-change (fn [elements state]
           {:on-change (fn [elements state]

+ 11 - 4
src/main/frontend/extensions/zip.cljs

@@ -2,6 +2,8 @@
   (:require ["jszip" :as JSZip]
   (:require ["jszip" :as JSZip]
             ["/frontend/utils" :as utils]
             ["/frontend/utils" :as utils]
             [promesa.core :as p]
             [promesa.core :as p]
+            [clojure.string :as string]
+            [frontend.config :as config]
             [medley.core :as medley]))
             [medley.core :as medley]))
 
 
 (defn make-file [content file-name args]
 (defn make-file [content file-name args]
@@ -11,10 +13,15 @@
     (aset args "lastModified" last-modified)
     (aset args "lastModified" last-modified)
     (js/File. blob-content file-name args)))
     (js/File. blob-content file-name args)))
 
 
-(defn make-zip [repo file-name->content]
+(defn make-zip [zip-filename file-name->content repo]
   (let [zip (JSZip.)
   (let [zip (JSZip.)
-        folder (.folder zip repo)]
+        zip-foldername (subs zip-filename (inc (string/last-index-of zip-filename "/")))
+        src-filepath (string/replace repo config/local-db-prefix "")
+        folder (.folder zip zip-foldername)]
     (doseq [[file-name content] file-name->content]
     (doseq [[file-name content] file-name->content]
-      (.file folder file-name content))
+      (.file folder (-> file-name
+                        (string/replace src-filepath "")
+                        (string/replace #"^/+" ""))
+             content))
     (p/let [zip-blob (.generateAsync zip #js {:type "blob"})]
     (p/let [zip-blob (.generateAsync zip #js {:type "blob"})]
-      (make-file zip-blob (str repo ".zip") {:type "application/zip"}))))
+      (make-file zip-blob (str zip-filename ".zip") {:type "application/zip"}))))

+ 1 - 1
src/main/frontend/external/roam.cljc

@@ -85,7 +85,7 @@
         level-pattern (apply str (repeat level "#"))
         level-pattern (apply str (repeat level "#"))
         properties (when (contains? @all-refed-uids uid)
         properties (when (contains? @all-refed-uids uid)
                      (str
                      (str
-                      (util/format ":PROPERTIES:\n:ID:%s\n:END:"
+                      (util/format "id:: %s"
                                    (str (get @uid->uuid uid)))
                                    (str (get @uid->uuid uid)))
                       "\n"))]
                       "\n"))]
     (if string
     (if string

+ 3 - 7
src/main/frontend/format.cljs

@@ -36,7 +36,9 @@
   ([format]
   ([format]
    (mldoc/default-config format))
    (mldoc/default-config format))
   ([format heading-to-list?]
   ([format heading-to-list?]
-   (mldoc/default-config format heading-to-list?)))
+   (mldoc/default-config format heading-to-list?))
+  ([format heading-to-list? exporting-keep-properties?]
+   (mldoc/default-config format heading-to-list? exporting-keep-properties?)))
 
 
 (defn to-html
 (defn to-html
   ([content format]
   ([content format]
@@ -62,9 +64,3 @@
   [format]
   [format]
   (when-let [record (get-format-record format)]
   (when-let [record (get-format-record format)]
     (protocol/loaded? record)))
     (protocol/loaded? record)))
-
-(def marker-pattern
-  #"^(NOW|LATER|TODO|DOING|DONE|WAITING|WAIT|CANCELED|CANCELLED|STARTED|IN-PROGRESS)?\s?")
-
-(def bare-marker-pattern
-  #"^(NOW|LATER|TODO|DOING|DONE|WAITING|WAIT|CANCELED|CANCELLED|STARTED|IN-PROGRESS){1}\s+")

+ 276 - 191
src/main/frontend/format/block.cljs

@@ -9,6 +9,7 @@
             [datascript.core :as d]
             [datascript.core :as d]
             [frontend.date :as date]
             [frontend.date :as date]
             [frontend.text :as text]
             [frontend.text :as text]
+            [frontend.util.property :as property]
             [medley.core :as medley]
             [medley.core :as medley]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.db :as db]))
             [frontend.db :as db]))
@@ -41,7 +42,8 @@
                      (when (and (not (util/starts-with? page "http:"))
                      (when (and (not (util/starts-with? page "http:"))
                                 (not (util/starts-with? page "https:"))
                                 (not (util/starts-with? page "https:"))
                                 (not (util/starts-with? page "file:"))
                                 (not (util/starts-with? page "file:"))
-                                (or (= ext :excalidraw) (not (contains? (config/supported-formats) ext))))
+                                (or (= ext :excalidraw)
+                                    (not (contains? (config/supported-formats) ext))))
                        page)))
                        page)))
 
 
                   (and
                   (and
@@ -108,10 +110,12 @@
 
 
                         (and (vector? block)
                         (and (vector? block)
                              (= "Link" (first block))
                              (= "Link" (first block))
-                             (map? (second block))
-                             (= "id" (:protocol (second (:url (second block))))))
-
-                        (:link (second (:url (second block))))
+                             (map? (second block)))
+                        (if (= "id" (:protocol (second (:url (second block)))))
+                          (:link (second (:url (second block))))
+                          (let [id (second (:url (second block)))]
+                            (when (text/block-ref? id)
+                             (text/block-ref-un-brackets! id))))
 
 
                         :else
                         :else
                         nil)]
                         nil)]
@@ -138,18 +142,12 @@
    (vector? block)
    (vector? block)
    (= "Hiccup" (first block))))
    (= "Hiccup" (first block))))
 
 
-(defn- timestamp-block?
+(defn timestamp-block?
   [block]
   [block]
   (and
   (and
    (vector? block)
    (vector? block)
    (= "Timestamp" (first block))))
    (= "Timestamp" (first block))))
 
 
-(defn properties-block?
-  [block]
-  (and
-   (vector? block)
-   (= "Property_Drawer" (first block))))
-
 (defn definition-list-block?
 (defn definition-list-block?
   [block]
   [block]
   (and
   (and
@@ -157,14 +155,8 @@
    (= "List" (first block))
    (= "List" (first block))
    (:name (first (second block)))))
    (:name (first (second block)))))
 
 
-(defn- ->schema-properties
-  [properties]
-  (-> properties
-      (update "created_at" util/safe-parse-int)
-      (update "last_modified_at" util/safe-parse-int)))
-
 (defonce non-parsing-properties
 (defonce non-parsing-properties
-  (atom #{"background_color"}))
+  (atom #{"background-color" "background_color"}))
 
 
 (defn extract-properties
 (defn extract-properties
   [[_ properties] _start-pos _end-pos]
   [[_ properties] _start-pos _end-pos]
@@ -183,34 +175,43 @@
                    (remove string/blank?))
                    (remove string/blank?))
         properties (->> properties
         properties (->> properties
                         (medley/map-kv (fn [k v]
                         (medley/map-kv (fn [k v]
-                                         (let [v (string/trim v)
-                                               k (string/replace k " " "_")]
-                                           (cond
-                                             (and (= "\"" (first v) (last v))) ; wrapped in ""
-                                             [(string/lower-case k) (string/trim (subs v 1 (dec (count v))))]
-
-                                             (contains? @non-parsing-properties (string/lower-case k))
-                                             [(string/lower-case k) v]
-
-                                             :else
-                                             (let [k' (and k (string/trim (string/lower-case k)))
-                                                   v' v
-                                                   ;; built-in collections
-                                                   comma? (contains? #{"tags" "alias"} k)
-                                                   v' (if (and k' v'
-                                                               (contains? config/markers k')
+                                         (if (coll? v)
+                                           [(keyword k) v]
+                                           (let [k (name k)
+                                                v (string/trim v)
+                                                k (string/replace k " " "-")
+                                                 k (string/lower-case k)
+                                                v (cond
+                                                    (= v "true")
+                                                    true
+                                                    (= v "false")
+                                                    false
+
+                                                    (re-find #"^\d+$" v)
+                                                    (util/safe-parse-int v)
+
+                                                    (and (= "\"" (first v) (last v))) ; wrapped in ""
+                                                    (string/trim (subs v 1 (dec (count v))))
+
+                                                    (contains? @non-parsing-properties (string/lower-case k))
+                                                    v
+
+                                                    :else
+                                                    (let [v' v]
+                                                      (if (and k v'
+                                                               (contains? config/markers k)
                                                                (util/safe-parse-int v'))
                                                                (util/safe-parse-int v'))
                                                         (util/safe-parse-int v')
                                                         (util/safe-parse-int v')
-                                                        (text/split-page-refs-without-brackets v' comma?))]
-                                               [k' v'])))))
-                        (->schema-properties))]
+                                                        (text/split-page-refs-without-brackets v' true))))]
+                                            [(keyword k) v])))))]
     {:properties properties
     {:properties properties
      :page-refs page-refs}))
      :page-refs page-refs}))
 
 
 (defn- paragraph-timestamp-block?
 (defn- paragraph-timestamp-block?
   [block]
   [block]
   (and (paragraph-block? block)
   (and (paragraph-block? block)
-       (timestamp-block? (first (second block)))))
+       (or (timestamp-block? (first (second block)))
+           (timestamp-block? (second (second block))))))
 
 
 (defn extract-timestamps
 (defn extract-timestamps
   [block]
   [block]
@@ -232,38 +233,56 @@
                             (cond->
                             (cond->
                              (case k
                              (case k
                                :scheduled
                                :scheduled
-                               {:scheduled day
-                                :scheduled-ast v}
+                               {:scheduled day}
                                :deadline
                                :deadline
-                               {:deadline day
-                                :deadline-ast v})
+                               {:deadline day})
                               repetition
                               repetition
                               (assoc :repeated? true))))))]
                               (assoc :repeated? true))))))]
     (apply merge m)))
     (apply merge m)))
 
 
-(defn block-tags->pages
-  [{:keys [tags] :as block}]
-  (if (seq tags)
-    (assoc block :tags (map (fn [tag]
-                              [:page/name (string/lower-case tag)]) tags))
-    block))
+(defn convert-page-if-journal
+  "Convert journal file name to user' custom date format"
+  [original-page-name]
+  (let [page-name (string/lower-case original-page-name)
+        day (date/journal-title->int page-name)]
+    (if day
+      (let [original-page-name (date/int->journal-title day)]
+        [original-page-name (string/lower-case original-page-name) day])
+      [original-page-name page-name day])))
+
+(defn page-name->map
+  [original-page-name with-id?]
+  (when original-page-name
+    (let [[original-page-name page-name journal-day] (convert-page-if-journal original-page-name)
+          m (merge
+             {:block/name page-name
+              :block/original-name original-page-name}
+             (when with-id?
+               (if-let [block (db/entity [:block/name page-name])]
+                 {}
+                 {:block/uuid (db/new-block-id)})))]
+      (if journal-day
+        (merge m
+               {:block/journal? true
+                :block/journal-day journal-day})
+        (assoc m :block/journal? false)))))
 
 
 (defn with-page-refs
 (defn with-page-refs
-  [{:keys [title body tags ref-pages] :as block}]
-  (let [ref-pages (->> (concat tags ref-pages)
-                       (remove string/blank?)
-                       (distinct))
-        ref-pages (atom ref-pages)]
+  [{:keys [title body tags refs marker priority] :as block} with-id?]
+  (let [refs (->> (concat tags refs [marker priority])
+                  (remove string/blank?)
+                  (distinct))
+        refs (atom refs)]
     (walk/postwalk
     (walk/postwalk
      (fn [form]
      (fn [form]
        (when-let [page (get-page-reference form)]
        (when-let [page (get-page-reference form)]
-         (swap! ref-pages conj page))
+         (swap! refs conj page))
        (when-let [tag (get-tag form)]
        (when-let [tag (get-tag form)]
          (when (util/tag-valid? tag)
          (when (util/tag-valid? tag)
-           (swap! ref-pages conj tag)))
+           (swap! refs conj tag)))
        form)
        form)
      (concat title body))
      (concat title body))
-    (let [ref-pages (remove string/blank? @ref-pages)
+    (let [refs (remove string/blank? @refs)
           children-pages (->> (mapcat (fn [p]
           children-pages (->> (mapcat (fn [p]
                                         (when (and (string/includes? p "/")
                                         (when (and (string/includes? p "/")
                                                    (not (string/starts-with? p "../"))
                                                    (not (string/starts-with? p "../"))
@@ -271,10 +290,12 @@
                                                    (not (string/starts-with? p "http")))
                                                    (not (string/starts-with? p "http")))
                                           ;; Don't create the last page for now
                                           ;; Don't create the last page for now
                                           (butlast (string/split p #"/"))))
                                           (butlast (string/split p #"/"))))
-                                      ref-pages)
+                                      refs)
                               (remove string/blank?))
                               (remove string/blank?))
-          ref-pages (distinct (concat ref-pages children-pages))]
-      (assoc block :ref-pages ref-pages))))
+          refs (->> (distinct (concat refs children-pages))
+                    (remove nil?))
+          refs (map (fn [ref] (page-name->map ref with-id?)) refs)]
+      (assoc block :refs refs))))
 
 
 (defn with-block-refs
 (defn with-block-refs
   [{:keys [title body] :as block}]
   [{:keys [title body] :as block}]
@@ -285,27 +306,14 @@
          (swap! ref-blocks conj block))
          (swap! ref-blocks conj block))
        form)
        form)
      (concat title body))
      (concat title body))
-    (let [ref-blocks (remove string/blank? @ref-blocks)]
-      (assoc block :ref-blocks (map
-                                (fn [id]
-                                  [:block/uuid (medley/uuid id)])
-                                ref-blocks)))))
-
-(defn update-src-pos-meta!
-  [{:keys [body] :as block}]
-  (let [body (walk/postwalk
-              (fn [form]
-                (if (and (vector? form)
-                         (= (first form) "Src")
-                         (map? (:pos_meta (second form))))
-                  (let [{:keys [start_pos end_pos]} (:pos_meta (second form))
-                        new_start_pos (- start_pos (get-in block [:meta :start-pos]))]
-                    ["Src" (assoc (second form)
-                                  :pos_meta {:start_pos new_start_pos
-                                             :end_pos (+ new_start_pos (- end_pos start_pos))})])
-                  form))
-              body)]
-    (assoc block :body body)))
+    (let [ref-blocks (->> @ref-blocks
+                          (filter util/uuid-string?))
+          ref-blocks (map
+                       (fn [id]
+                         [:block/uuid (medley/uuid id)])
+                       ref-blocks)
+          refs (distinct (concat (:refs block) ref-blocks))]
+      (assoc block :refs refs))))
 
 
 (defn block-keywordize
 (defn block-keywordize
   [block]
   [block]
@@ -338,32 +346,83 @@
             [path-refs parents]
             [path-refs parents]
             (cond
             (cond
               (zero? level-diff)            ; sibling
               (zero? level-diff)            ; sibling
-              (let [path-refs (mapcat :block/ref-pages (drop-last parents))
+              (let [path-refs (mapcat :block/refs (drop-last parents))
                     parents (conj (vec (butlast parents)) block)]
                     parents (conj (vec (butlast parents)) block)]
                 [path-refs parents])
                 [path-refs parents])
 
 
               (> level-diff 0)              ; child
               (> level-diff 0)              ; child
-              (let [path-refs (mapcat :block/ref-pages parents)]
+              (let [path-refs (mapcat :block/refs parents)]
                 [path-refs (conj parents block)])
                 [path-refs (conj parents block)])
 
 
               (< level-diff 0)              ; new parent
               (< level-diff 0)              ; new parent
               (let [parents (vec (take-while (fn [p] (< (:block/level p) cur-level)) parents))
               (let [parents (vec (take-while (fn [p] (< (:block/level p) cur-level)) parents))
-                    path-refs (mapcat :block/ref-pages parents)]
+                    path-refs (mapcat :block/refs parents)]
                 [path-refs (conj parents block)]))
                 [path-refs (conj parents block)]))
             path-ref-pages (->> path-refs
             path-ref-pages (->> path-refs
-                                (concat (:block/ref-pages block))
+                                (concat (:block/refs block))
+                                (map (fn [ref]
+                                       (cond
+                                         (map? ref)
+                                         (:block/name ref)
+
+                                         :else
+                                         ref)))
                                 (remove string/blank?)
                                 (remove string/blank?)
-                                (map string/lower-case)
-                                (distinct)
-                                (map (fn [p]
-                                       {:page/name p})))]
+                                (map (fn [ref]
+                                       (if (string? ref)
+                                         {:block/name (string/lower-case ref)}
+                                         ref)))
+                                (remove vector?)
+                                (distinct))]
         (recur (rest blocks)
         (recur (rest blocks)
-               (conj acc (assoc block :block/path-ref-pages path-ref-pages))
+               (conj acc (assoc block :block/path-refs path-ref-pages))
                parents)))))
                parents)))))
 
 
+(defn block-tags->pages
+  [{:keys [tags] :as block}]
+  (if (seq tags)
+    (assoc block :tags (map (fn [tag]
+                              [:block/name (string/lower-case tag)]) tags))
+    block))
+
+(defn- remove-indentation-spaces
+  [s level]
+  (let [level (inc level)
+        lines (string/split-lines s)
+        [f & r] lines
+        body (map (fn [line]
+                    (if (string/blank? (util/safe-subs line 0 level))
+                      (util/safe-subs line level)
+                      line))
+                  r)
+        content (cons f body)]
+    (string/join "\n" content)))
+
+(defn- get-block-content
+  [utf8-content block format]
+  (let [meta (:meta block)
+        content (if-let [end-pos (:end-pos meta)]
+                  (utf8/substring utf8-content
+                                  (:start-pos meta)
+                                  end-pos)
+                  (utf8/substring utf8-content
+                                  (:start-pos meta)))]
+    (let [content (when content
+                    (let [content (text/remove-level-spaces content format)]
+                      (if (or (:pre-block? block)
+                              (= (:format block) :org))
+                        content
+                        (remove-indentation-spaces content (:level block)))))]
+      (if (= format :org)
+        content
+        (property/->new-properties content)))))
+
 (defn extract-blocks
 (defn extract-blocks
-  [blocks last-pos encoded-content]
-  (let [pre-block-body (atom nil)
+  [blocks content with-id? format]
+  (let [encoded-content (utf8/encode content)
+        last-pos (utf8/length encoded-content)
+        pre-block-body (atom nil)
+        pre-block-properties (atom nil)
         blocks
         blocks
         (loop [headings []
         (loop [headings []
                block-body []
                block-body []
@@ -375,35 +434,40 @@
                children []]
                children []]
           (if (seq blocks)
           (if (seq blocks)
             (let [[block {:keys [start_pos end_pos]}] (first blocks)
             (let [[block {:keys [start_pos end_pos]}] (first blocks)
-                  level (:level (second block))]
+                  unordered? (:unordered (second block))
+                  markdown-heading? (and (false? unordered?) (= :markdown format))]
               (cond
               (cond
                 (paragraph-timestamp-block? block)
                 (paragraph-timestamp-block? block)
                 (let [timestamps (extract-timestamps block)
                 (let [timestamps (extract-timestamps block)
                       timestamps' (merge timestamps timestamps)
                       timestamps' (merge timestamps timestamps)
-                      other-body (->> (remove timestamp-block? (second block))
+                      other-body (->> (second block)
                                       (drop-while #(= ["Break_Line"] %)))]
                                       (drop-while #(= ["Break_Line"] %)))]
                   (recur headings (conj block-body ["Paragraph" other-body]) (rest blocks) timestamps' properties last-pos last-level children))
                   (recur headings (conj block-body ["Paragraph" other-body]) (rest blocks) timestamps' properties last-pos last-level children))
 
 
-                (properties-block? block)
+                (property/properties-ast? block)
                 (let [properties (extract-properties block start_pos end_pos)]
                 (let [properties (extract-properties block start_pos end_pos)]
                   (recur headings block-body (rest blocks) timestamps properties last-pos last-level children))
                   (recur headings block-body (rest blocks) timestamps properties last-pos last-level children))
 
 
                 (heading-block? block)
                 (heading-block? block)
-                (let [id (or (when-let [custom-id (or (get-in properties [:properties "custom_id"])
-                                                      (get-in properties [:properties "id"]))]
+                (let [id (or (when-let [custom-id (or (get-in properties [:properties :custom-id])
+                                                      (get-in properties [:properties :custom_id])
+                                                      (get-in properties [:properties :id]))]
                                (let [custom-id (string/trim custom-id)]
                                (let [custom-id (string/trim custom-id)]
                                  (when (util/uuid-string? custom-id)
                                  (when (util/uuid-string? custom-id)
                                    (uuid custom-id))))
                                    (uuid custom-id))))
-                             (d/squuid))
-                      ref-pages-in-properties (:page-refs properties)
+                             (db/new-block-id))
+                      ref-pages-in-properties (->> (:page-refs properties)
+                                                   (remove string/blank?))
                       block (second block)
                       block (second block)
+                      block (if markdown-heading?
+                              (assoc block
+                                     :type :heading
+                                     :level 1
+                                     :heading-level (:level block))
+                              block)
                       level (:level block)
                       level (:level block)
                       [children current-block-children]
                       [children current-block-children]
                       (cond
                       (cond
-                        (>= level last-level)
-                        [(conj children [id level])
-                         #{}]
-
                         (< level last-level)
                         (< level last-level)
                         (let [current-block-children (set (->> (filter #(< level (second %)) children)
                         (let [current-block-children (set (->> (filter #(< level (second %)) children)
                                                                (map first)
                                                                (map first)
@@ -411,23 +475,31 @@
                                                                       [:block/uuid id]))))
                                                                       [:block/uuid id]))))
                               others (vec (remove #(< level (second %)) children))]
                               others (vec (remove #(< level (second %)) children))]
                           [(conj others [id level])
                           [(conj others [id level])
-                           current-block-children]))
+                           current-block-children])
+
+                        (>= level last-level)
+                        [(conj children [id level])
+                         #{}])
+
                       block (-> (assoc block
                       block (-> (assoc block
                                        :uuid id
                                        :uuid id
                                        :body (vec (reverse block-body))
                                        :body (vec (reverse block-body))
                                        :properties (:properties properties)
                                        :properties (:properties properties)
-                                       :ref-pages ref-pages-in-properties
-                                       :children (or current-block-children []))
+                                       :refs ref-pages-in-properties
+                                       :children (or current-block-children [])
+                                       :format format)
                                 (assoc-in [:meta :start-pos] start_pos)
                                 (assoc-in [:meta :start-pos] start_pos)
-                                (assoc-in [:meta :end-pos] last-pos))
+                                (assoc-in [:meta :end-pos] last-pos)
+                                ((fn [block]
+                                   (assoc block
+                                          :content (get-block-content encoded-content block format)))))
                       block (if (seq timestamps)
                       block (if (seq timestamps)
                               (merge block (timestamps->scheduled-and-deadline timestamps))
                               (merge block (timestamps->scheduled-and-deadline timestamps))
                               block)
                               block)
                       block (-> block
                       block (-> block
-                                with-page-refs
+                                (with-page-refs with-id?)
                                 with-block-refs
                                 with-block-refs
-                                block-tags->pages
-                                update-src-pos-meta!)
+                                block-tags->pages)
                       last-pos' (get-in block [:meta :start-pos])]
                       last-pos' (get-in block [:meta :start-pos])]
                   (recur (conj headings block) [] (rest blocks) {} {} last-pos' (:level block) children))
                   (recur (conj headings block) [] (rest blocks) {} {} last-pos' (:level block) children))
 
 
@@ -436,108 +508,121 @@
                   (recur headings block-body' (rest blocks) timestamps properties last-pos last-level children))))
                   (recur headings block-body' (rest blocks) timestamps properties last-pos last-level children))))
             (do
             (do
               (when (seq block-body)
               (when (seq block-body)
-                (reset! pre-block-body block-body))
+                (reset! pre-block-body (reverse block-body)))
+              (when (seq properties)
+                (let [properties (:properties properties)]
+                  (reset! pre-block-properties properties)))
               (-> (reverse headings)
               (-> (reverse headings)
                   safe-blocks))))]
                   safe-blocks))))]
     (let [first-block (first blocks)
     (let [first-block (first blocks)
           first-block-start-pos (get-in first-block [:block/meta :start-pos])
           first-block-start-pos (get-in first-block [:block/meta :start-pos])
-          blocks (if (and
-                      (not (string/blank? encoded-content))
-                      (or (empty? blocks)
-                          (> first-block-start-pos 1)))
+          blocks (if (or (seq @pre-block-body)
+                         (seq @pre-block-properties))
                    (cons
                    (cons
                     (merge
                     (merge
-                     (let [content (utf8/substring encoded-content 0 first-block-start-pos)
-                           uuid (d/squuid)]
+                     (let [content (utf8/substring encoded-content 0 first-block-start-pos)]
                        (->
                        (->
-                        {:uuid uuid
+                        {:uuid (db/new-block-id)
                          :content content
                          :content content
-                         :anchor (str uuid)
-                         :level 2
+                         :level 1
                          :meta {:start-pos 0
                          :meta {:start-pos 0
                                 :end-pos (or first-block-start-pos
                                 :end-pos (or first-block-start-pos
                                              (utf8/length encoded-content))}
                                              (utf8/length encoded-content))}
                          :body @pre-block-body
                          :body @pre-block-body
-                         ;; (take-while (fn [block] (not (heading-block? block))) blocks)
-                         :pre-block? true}
+                         :properties @pre-block-properties
+                         :pre-block? true
+                         :unordered true}
                         (block-keywordize)))
                         (block-keywordize)))
                      (select-keys first-block [:block/file :block/format :block/page]))
                      (select-keys first-block [:block/file :block/format :block/page]))
                     blocks)
                     blocks)
                    blocks)]
                    blocks)]
       (with-path-refs blocks))))
       (with-path-refs blocks))))
 
 
-(defn- page-with-journal
-  [original-page-name]
-  (when original-page-name
-    (let [page-name (string/lower-case original-page-name)]
-      (if-let [d (date/journal-title->int page-name)]
-        {:page/name page-name
-         :page/original-name original-page-name
-         :page/journal? true
-         :page/journal-day d}
-        {:page/name page-name
-         :page/original-name original-page-name}))))
+(defn with-parent-and-left
+  [page-id blocks]
+  (loop [blocks (map (fn [block] (assoc block :block/level-spaces (:block/level block))) blocks)
+         parents [{:page/id page-id     ; db id or lookup ref [:block/name "xxx"]
+                   :block/level 0
+                   :block/level-spaces 0}]
+         sibling nil
+         result []]
+    (if (empty? blocks)
+      (map #(dissoc % :block/level-spaces) result)
+      (let [[block & others] blocks
+            level-spaces (:block/level-spaces block)
+            {:block/keys [uuid level parent unordered] :as last-parent} (last parents)
+            parent-spaces (:block/level-spaces last-parent)
+            [blocks parents sibling result]
+            (cond
+              (= level-spaces parent-spaces)        ; sibling
+              (let [block (assoc block
+                                 :block/parent parent
+                                 :block/left [:block/uuid uuid]
+                                 :block/level level
+                                 )
+                    parents' (conj (vec (butlast parents)) block)
+                    result' (conj result block)]
+                [others parents' block result'])
+
+              (> level-spaces parent-spaces)         ; child
+              (let [parent (if uuid [:block/uuid uuid] (:page/id last-parent))
+                    block (cond->
+                            (assoc block
+                                  :block/parent parent
+                                  :block/left parent)
+                            ;; fix block levels with wrong order
+                            ;; For example:
+                            ;;   - a
+                            ;; - b
+                            ;; What if the input indentation is two spaces instead of 4 spaces
+                            (>= (- level-spaces parent-spaces) 1)
+                            (assoc :block/level (inc level)))
+                    parents' (conj parents block)
+                    result' (conj result block)]
+                [others parents' block result'])
+
+              ;; - a
+              ;;    - b
+              ;;  - c
+              (and (>= (count parents) 2)
+                   (< level-spaces parent-spaces)
+                   (> level-spaces (:block/level-spaces (nth parents (- (count parents) 2)))))
+              (let [block (assoc block
+                                 :block/parent parent
+                                 :block/left [:block/uuid uuid]
+                                 :block/level level
+                                 :block/level-spaces parent-spaces)
+                    parents' (conj (vec (butlast parents)) block)
+                    result' (conj result block)]
+                [others parents' block result'])
+
+              (< level-spaces parent-spaces)         ; outdent
+              (let [parents' (vec (filter (fn [p] (<= (:block/level-spaces p) level-spaces)) parents))
+                    blocks (cons (assoc (first blocks) :block/level (dec level))
+                                 (rest blocks))]
+                [blocks parents' (last parents') result]))]
+        (recur blocks parents sibling result)))))
 
 
 (defn parse-block
 (defn parse-block
-  ([block format]
-   (parse-block block format nil))
-  ([{:block/keys [uuid content meta file page pre-block?] :as block} format ast]
+  ([block]
+   (parse-block block nil))
+  ([{:block/keys [uuid content meta file page parent left format] :as block} {:keys [with-id?]
+                                                                              :or {with-id? true}}]
    (when-not (string/blank? content)
    (when-not (string/blank? content)
-     (let [ast (or ast (format/to-edn content format nil))
-           start-pos (:start-pos meta)
-           encoded-content (utf8/encode content)
-           content-length (utf8/length encoded-content)
-           blocks (extract-blocks ast content-length encoded-content)
-           ref-pages-atom (atom [])
-           parent-ref-pages (->> (db/get-block-parent (state/get-current-repo) uuid)
-                                 :block/path-ref-pages
-                                 (map :db/id))
-           blocks (doall
-                   (map-indexed
-                    (fn [idx {:block/keys [ref-pages ref-blocks meta] :as block}]
-                      (let [path-ref-pages (->> ref-pages
-                                                (remove string/blank?)
-                                                (map string/lower-case)
-                                                (map (fn [p] [:page/name p]))
-                                                (concat parent-ref-pages))
-                            block (merge
-                                   block
-                                   {:block/meta meta
-                                    :block/marker (get block :block/marker "nil")
-                                    :block/properties (get block :block/properties {})
-                                    :block/file file
-                                    :block/format format
-                                    :block/page page
-                                    :block/content (utf8/substring encoded-content
-                                                                   (:start-pos meta)
-                                                                   (:end-pos meta))
-                                    :block/path-ref-pages path-ref-pages}
-                                   ;; Preserve the original block id
-                                   (when (zero? idx)
-                                     {:block/uuid uuid})
-                                   (when (seq ref-pages)
-                                     {:block/ref-pages
-                                      (mapv
-                                       (fn [page]
-                                         (let [page (page-with-journal page)]
-                                           (swap! ref-pages-atom conj page)
-                                           page))
-                                       ref-pages)}))]
-                        (-> block
-                            (assoc-in [:block/meta :start-pos] (+ (:start-pos meta) start-pos))
-                            (assoc-in [:block/meta :end-pos] (+ (:end-pos meta) start-pos)))))
-                    blocks))
-           pages (vec (distinct @ref-pages-atom))]
-       {:blocks blocks
-        :pages pages
-        :start-pos start-pos
-        :end-pos (+ start-pos content-length)}))))
-
-(defn with-levels
-  [text format {:block/keys [level pre-block?]}]
-  (let [pattern (config/get-block-pattern format)
-        prefix (if pre-block? "" (str (apply str (repeat level pattern)) " "))]
-    (str prefix (string/triml text))))
+     (let [block (dissoc block :block/pre-block?)
+           ast (format/to-edn content format nil)
+           new-block (first (extract-blocks ast content with-id? format))
+           parent-refs (->> (db/get-block-parent (state/get-current-repo) uuid)
+                            :block/path-refs
+                            (map :db/id))
+           {:block/keys [refs]} new-block
+           ref-pages (filter :block/name refs)
+           path-ref-pages (concat ref-pages parent-refs)
+           block (merge
+                  block
+                  new-block
+                  {:block/path-refs path-ref-pages})]
+       (if uuid (assoc block :block/uuid uuid) block)))))
 
 
 (defn macro-subs
 (defn macro-subs
   [macro-content arguments]
   [macro-content arguments]

+ 36 - 20
src/main/frontend/format/mldoc.cljs

@@ -1,6 +1,7 @@
 (ns frontend.format.mldoc
 (ns frontend.format.mldoc
   (:require [frontend.format.protocol :as protocol]
   (:require [frontend.format.protocol :as protocol]
             [frontend.util :as util]
             [frontend.util :as util]
+            [frontend.utf8 :as utf8]
             [clojure.string :as string]
             [clojure.string :as string]
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
             [cljs.core.match :refer-macros [match]]
             [cljs.core.match :refer-macros [match]]
@@ -15,6 +16,7 @@
 (defonce parseHtml (gobj/get Mldoc "parseHtml"))
 (defonce parseHtml (gobj/get Mldoc "parseHtml"))
 (defonce anchorLink (gobj/get Mldoc "anchorLink"))
 (defonce anchorLink (gobj/get Mldoc "anchorLink"))
 (defonce parseAndExportMarkdown (gobj/get Mldoc "parseAndExportMarkdown"))
 (defonce parseAndExportMarkdown (gobj/get Mldoc "parseAndExportMarkdown"))
+(defonce astExportMarkdown (gobj/get Mldoc "astExportMarkdown"))
 
 
 (defn default-config
 (defn default-config
   ([format]
   ([format]
@@ -27,13 +29,22 @@
         :heading_number false
         :heading_number false
         :keep_line_break true
         :keep_line_break true
         :format format
         :format format
-        :heading_to_list export-heading-to-list?})))))
+        :heading_to_list export-heading-to-list?}))))
+  ([format export-heading-to-list? exporting-keep-properties?]
+   (let [format (string/capitalize (name (or format :markdown)))]
+     (js/JSON.stringify
+      (bean/->js
+       {:toc false
+        :heading_number false
+        :keep_line_break true
+        :format format
+        :heading_to_list export-heading-to-list?
+        :exporting_keep_properties exporting-keep-properties?})))))
 
 
 (def default-references
 (def default-references
   (js/JSON.stringify
   (js/JSON.stringify
    (clj->js {:embed_blocks []
    (clj->js {:embed_blocks []
-             :embed_pages []
-             :refer_blocks []})))
+             :embed_pages []})))
 
 
 (defn parse-json
 (defn parse-json
   [content config]
   [content config]
@@ -49,6 +60,12 @@
                           (or config default-config)
                           (or config default-config)
                           (or references default-references)))
                           (or references default-references)))
 
 
+(defn ast-export-markdown
+  [ast config references]
+  (astExportMarkdown ast
+                     (or config default-config)
+                     (or references default-references)))
+
 ;; Org-roam
 ;; Org-roam
 (defn get-tags-from-definition
 (defn get-tags-from-definition
   [ast]
   [ast]
@@ -106,10 +123,9 @@
           properties (->> (map first directive-ast)
           properties (->> (map first directive-ast)
                           (map (fn [[_ k v]]
                           (map (fn [[_ k v]]
                                  (let [k (keyword (string/lower-case k))
                                  (let [k (keyword (string/lower-case k))
-                                       comma? (contains? #{:tags :alias :roam_tags} k)
                                        v (if (contains? #{:title :description :roam_tags} k)
                                        v (if (contains? #{:title :description :roam_tags} k)
                                            v
                                            v
-                                           (text/split-page-refs-without-brackets v comma?))]
+                                           (text/split-page-refs-without-brackets v true))]
                                    [k v])))
                                    [k v])))
                           (reverse)
                           (reverse)
                           (into {}))
                           (into {}))
@@ -161,6 +177,19 @@
         original-ast))
         original-ast))
     ast))
     ast))
 
 
+(defn update-src-full-content
+  [ast content]
+  (let [content (utf8/encode content)]
+    (map (fn [[block pos-meta]]
+          (if (and (vector? block)
+                   (= "Src" (first block)))
+            (let [{:keys [start_pos end_pos]} pos-meta
+                  block ["Src" (assoc (second block)
+                                      :full_content
+                                      (utf8/substring content start_pos end_pos))]]
+              [block pos-meta])
+            [block pos-meta])) ast)))
+
 (defn ->edn
 (defn ->edn
   [content config]
   [content config]
   (try
   (try
@@ -169,6 +198,7 @@
       (-> content
       (-> content
           (parse-json config)
           (parse-json config)
           (util/json->clj)
           (util/json->clj)
+          (update-src-full-content content)
           (collect-page-properties)))
           (collect-page-properties)))
     (catch js/Error e
     (catch js/Error e
       (log/error :edn/convert-failed e)
       (log/error :edn/convert-failed e)
@@ -196,22 +226,8 @@
   (lazyLoad [this ok-handler]
   (lazyLoad [this ok-handler]
     true)
     true)
   (exportMarkdown [this content config references]
   (exportMarkdown [this content config references]
-    (parse-export-markdown content config references))
-  )
+    (parse-export-markdown content config references)))
 
 
 (defn plain->text
 (defn plain->text
   [plains]
   [plains]
   (string/join (map last plains)))
   (string/join (map last plains)))
-
-(defn parse-properties
-  [content format]
-  (let [ast (->> (->edn content
-                        (default-config format))
-                 (map first))
-        properties (collect-page-properties ast)
-        properties (let [properties (and (seq ast)
-                                         (= "Properties" (ffirst ast))
-                                         (last (first ast)))]
-                     (if (and properties (seq properties))
-                       properties))]
-    (into {} properties)))

+ 1 - 41
src/main/frontend/format/mldoc_test.cljs

@@ -1,44 +1,4 @@
 (ns frontend.format.mldoc-test
 (ns frontend.format.mldoc-test
-  (:require [frontend.format.mldoc :refer [parse-properties]]
+  (:require [frontend.format.mldoc]
             [clojure.string :as string]
             [clojure.string :as string]
             [cljs.test :refer [deftest are is testing]]))
             [cljs.test :refer [deftest are is testing]]))
-
-(deftest test-parse-org-properties
-  []
-  (testing "just title"
-    (let [content "#+TITLE:   some title   "
-          props (parse-properties content "org")]
-      (are [x y] (= x y)
-        ;; TODO: should we trim in parse-properties?
-        "some title" (string/trim (:title props)))))
-
-  (testing "filetags"
-    (let [content "
-#+FILETAGS:   :tag1:tag_2:@tag:
-#+ROAM_TAGS:  roamtag
-body"
-          props (parse-properties content "org")]
-      (are [x y] (= x y)
-        (list "@tag" "tag1" "tag_2") (sort (:filetags props))
-        ["roamtag"] (:roam_tags props)
-        (list "@tag" "roamtag" "tag1" "tag_2") (sort (:tags props)))))
-
-  (testing "roam tags"
-    (let [content "
-#+FILETAGS: filetag
-#+ROAM_TAGS: roam1 roam2
-body
-"
-          props (parse-properties content "org")]
-      (are [x y] (= x y)
-        ["roam1" "roam2"] (:roam_tags props)
-        (list "filetag" "roam1" "roam2") (sort (:tags props)))))
-
-  (testing "quoted roam tags"
-    (let [content "
-#+ROAM_TAGS: \"why would\"  you use \"spaces\" xxx
-body
-"
-          props (parse-properties content "org")]
-      ;; TODO maybe need to sort or something
-      (is (= ["why would" "spaces" "you" "use" "xxx"] (:roam_tags props))))))

+ 13 - 15
src/main/frontend/fs/nfs.cljs

@@ -105,8 +105,7 @@
         (and local-file (.text local-file)))))
         (and local-file (.text local-file)))))
 
 
   (write-file! [this repo dir path content opts]
   (write-file! [this repo dir path content opts]
-    (let [{:keys [old-content]} opts
-          last-modified-at (db/get-file-last-modified-at repo path)
+    (let [last-modified-at (db/get-file-last-modified-at repo path)
           parts (string/split path "/")
           parts (string/split path "/")
           basename (last parts)
           basename (last parts)
           sub-dir (->> (butlast parts)
           sub-dir (->> (butlast parts)
@@ -135,23 +134,22 @@
                              (config/get-file-format))
                              (config/get-file-format))
                   pending-writes (state/get-write-chan-length)
                   pending-writes (state/get-write-chan-length)
                   draw? (and path (string/ends-with? path ".excalidraw"))]
                   draw? (and path (string/ends-with? path ".excalidraw"))]
-            (if (and local-content (or old-content
-                                       ;; temporally fix
-                                       draw?) new?
-                     (or
-                      draw?
-                      ;; Writing not finished
-                      (> pending-writes 0)
-                      ;; not changed by other editors
-                      not-changed?
-                      new-created?))
-              (do
+            (p/let [_ (verify-permission repo file-handle true)
+                    _ (utils/writeFile file-handle content)
+                    file (.getFile file-handle)]
+              (if (and local-content new?
+                       (or
+                        draw?
+                        ;; Writing not finished
+                        (> pending-writes 0)
+                        ;; not changed by other editors
+                        not-changed?
+                        new-created?))
                 (p/let [_ (verify-permission repo file-handle true)
                 (p/let [_ (verify-permission repo file-handle true)
                         _ (utils/writeFile file-handle content)
                         _ (utils/writeFile file-handle content)
                         file (.getFile file-handle)]
                         file (.getFile file-handle)]
                   (when file
                   (when file
-                    (nfs-saved-handler repo path file))))
-              (do
+                    (nfs-saved-handler repo path file)))
                 (js/alert (str "The file has been modified on your local disk! File path: " path
                 (js/alert (str "The file has been modified on your local disk! File path: " path
                                ", please save your changes and click the refresh button to reload it.")))))
                                ", please save your changes and click the refresh button to reload it.")))))
            ;; create file handle
            ;; create file handle

+ 12 - 11
src/main/frontend/fs/node.cljs

@@ -24,10 +24,15 @@
            (str "/" (string/replace path #"^/" ""))))))
            (str "/" (string/replace path #"^/" ""))))))
 
 
 (defn- write-file-impl!
 (defn- write-file-impl!
-  [repo dir path content {:keys [ok-handler error-handler] :as opts} stat]
+  [this repo dir path content {:keys [ok-handler error-handler] :as opts} stat]
   (p/let [disk-mtime (when stat (gobj/get stat "mtime"))
   (p/let [disk-mtime (when stat (gobj/get stat "mtime"))
-          db-mtime (db/get-file-last-modified-at repo path)]
-    (if (not= disk-mtime db-mtime)
+          db-mtime (db/get-file-last-modified-at repo path)
+          old-content nil
+          old-content (-> (protocol/read-file this dir path nil)
+                          (p/catch (fn [error]
+                                     "")))]
+    (if (and (not= disk-mtime db-mtime)
+             (not= (string/trim old-content) (string/trim content)))
       (js/alert (str "The file has been modified on your local disk! File path: " path
       (js/alert (str "The file has been modified on your local disk! File path: " path
                      ", please save your changes and click the refresh button to reload it."))
                      ", please save your changes and click the refresh button to reload it."))
       (->
       (->
@@ -57,14 +62,10 @@
       (ipc/ipc "readFile" path)))
       (ipc/ipc "readFile" path)))
   (write-file! [this repo dir path content {:keys [ok-handler error-handler] :as opts}]
   (write-file! [this repo dir path content {:keys [ok-handler error-handler] :as opts}]
     (let [path (concat-path dir path)]
     (let [path (concat-path dir path)]
-      (->
-       (p/let [stat (protocol/stat this dir path)]
-         ;; update
-         (write-file-impl! repo dir path content opts stat))
-       (p/catch
-        (fn [_error]
-             ;; create
-          (write-file-impl! repo dir path content opts nil))))))
+      (p/let [stat (p/catch
+                       (protocol/stat this dir path)
+                       (fn [_e] nil))]
+        (write-file-impl! this repo dir path content opts stat))))
   (rename! [this repo old-path new-path]
   (rename! [this repo old-path new-path]
     (ipc/ipc "rename" old-path new-path))
     (ipc/ipc "rename" old-path new-path))
   (stat [this dir path]
   (stat [this dir path]

+ 5 - 15
src/main/frontend/fs/watcher_handler.cljs

@@ -20,28 +20,18 @@
       (when (and content (not (encrypt/content-encrypted? content)))
       (when (and content (not (encrypt/content-encrypted? content)))
         (cond
         (cond
           (= "add" type)
           (= "add" type)
-          (let [db-content (db/get-file path)]
-            (when (and (not= content db-content)
-                       ;; Avoid file overwrites
-                       ;; 1. create a new page which writes a new file
-                       ;; 2. add some new content
-                       ;; 3. file watcher notified it with the old content
-                       ;; 4. old content will overwrites the new content in step 2
-                       (not (and db-content
-                                 (string/starts-with? db-content content))))
-              (let [_ (file-handler/alter-file repo path content {:re-render-root? true
-                                                                  :from-disk? true})]
-                (db/set-file-last-modified-at! repo path mtime))))
+          (when-not (db/file-exists? repo path)
+            (let [_ (file-handler/alter-file repo path content {:re-render-root? true
+                                                                :from-disk? true})]
+              (db/set-file-last-modified-at! repo path mtime)))
 
 
           (and (= "change" type)
           (and (= "change" type)
-               (nil? (db/get-file path)))
+               (not (db/file-exists? repo path)))
           (js/console.warn "Can't get file in the db: " path)
           (js/console.warn "Can't get file in the db: " path)
 
 
           (and (= "change" type)
           (and (= "change" type)
-               (not= content (db/get-file path))
                (when-let [last-modified-at (db/get-file-last-modified-at repo path)]
                (when-let [last-modified-at (db/get-file-last-modified-at repo path)]
                  (> mtime last-modified-at)))
                  (> mtime last-modified-at)))
-
           (let [_ (file-handler/alter-file repo path content {:re-render-root? true
           (let [_ (file-handler/alter-file repo path content {:re-render-root? true
                                                               :from-disk? true})]
                                                               :from-disk? true})]
             (db/set-file-last-modified-at! repo path mtime))
             (db/set-file-last-modified-at! repo path mtime))

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

@@ -143,7 +143,7 @@
                      (let [page-name (string/lower-case (gobj/get node "id"))]
                      (let [page-name (string/lower-case (gobj/get node "id"))]
                        (if (gobj/get event "shiftKey")
                        (if (gobj/get event "shiftKey")
                          (let [repo (state/get-current-repo)
                          (let [repo (state/get-current-repo)
-                               page (db/entity repo [:page/name page-name])]
+                               page (db/entity repo [:block/name page-name])]
                            (state/sidebar-add-block!
                            (state/sidebar-add-block!
                             repo
                             repo
                             (:db/id page)
                             (:db/id page)

+ 23 - 19
src/main/frontend/handler.cljs

@@ -18,6 +18,8 @@
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.web.nfs :as nfs]
             [frontend.handler.web.nfs :as nfs]
+            [frontend.modules.shortcut.core :as shortcut]
+            [frontend.handler.events :as events]
             [frontend.fs.watcher-handler :as fs-watcher-handler]
             [frontend.fs.watcher-handler :as fs-watcher-handler]
             [frontend.ui :as ui]
             [frontend.ui :as ui]
             [goog.object :as gobj]
             [goog.object :as gobj]
@@ -25,6 +27,7 @@
             [lambdaisland.glogi :as log]
             [lambdaisland.glogi :as log]
             [frontend.handler.common :as common-handler]
             [frontend.handler.common :as common-handler]
             [electron.listener :as el]
             [electron.listener :as el]
+            [electron.ipc :as ipc]
             [frontend.version :as version]))
             [frontend.version :as version]))
 
 
 (defn- watch-for-date!
 (defn- watch-for-date!
@@ -44,11 +47,6 @@
   []
   []
   (storage/set :db-schema db-schema/schema))
   (storage/set :db-schema db-schema/schema))
 
 
-(defn schema-changed?
-  []
-  (when-let [schema (storage/get :db-schema)]
-    (not= schema db-schema/schema)))
-
 (defn- get-me-and-repos
 (defn- get-me-and-repos
   []
   []
   (let [me (and js/window.user (bean/->clj js/window.user))
   (let [me (and js/window.user (bean/->clj js/window.user))
@@ -60,14 +58,6 @@
      :logged? logged?
      :logged? logged?
      :repos repos}))
      :repos repos}))
 
 
-(declare restore-and-setup!)
-
-(defn clear-stores-and-refresh!
-  []
-  (p/let [_ (idb/clear-local-storage-and-idb!)]
-    (let [{:keys [me logged? repos]} (get-me-and-repos)]
-      (js/window.location.reload))))
-
 (defn restore-and-setup!
 (defn restore-and-setup!
   [me repos logged?]
   [me repos logged?]
   (let [interval (atom nil)
   (let [interval (atom nil)
@@ -97,7 +87,7 @@
                                false)
                                false)
                               (store-schema!))
                               (store-schema!))
 
 
-                            (nfs/ask-permission-if-local?)
+                            (state/pub-event! [:modal/nfs-ask-permission])
 
 
                             (page-handler/init-commands!)
                             (page-handler/init-commands!)
                             (when (seq (:repos me))
                             (when (seq (:repos me))
@@ -148,10 +138,11 @@
 
 
 (defn init-sentry
 (defn init-sentry
   []
   []
-  (let [cfg
-        {:dsn "https://[email protected]/5311485"
-         :release (util/format "logseq@%s" version/version)}]
-    (.init js/window.Sentry (clj->js cfg))))
+  (when-not (state/sentry-disabled?)
+    (let [cfg
+          {:dsn "https://[email protected]/5311485"
+           :release (util/format "logseq@%s" version/version)}]
+      (.init js/window.Sentry (clj->js cfg)))))
 
 
 (defn on-load-events
 (defn on-load-events
   []
   []
@@ -159,6 +150,13 @@
             (when-not config/dev? (init-sentry)))]
             (when-not config/dev? (init-sentry)))]
     (set! js/window.onload f)))
     (set! js/window.onload f)))
 
 
+(defn clear-cache!
+  []
+  (p/let [_ (idb/clear-local-storage-and-idb!)
+          _ (when (util/electron?)
+              (ipc/ipc "clearCache"))]
+    (js/window.location.reload)))
+
 (defn start!
 (defn start!
   [render]
   [render]
   (let [{:keys [me logged? repos]} (get-me-and-repos)]
   (let [{:keys [me logged? repos]} (get-me-and-repos)]
@@ -173,12 +171,18 @@
        (notification/show! "Sorry, it seems that your browser doesn't support IndexedDB, we recommend to use latest Chrome(Chromium) or Firefox(Non-private mode)." :error false)
        (notification/show! "Sorry, it seems that your browser doesn't support IndexedDB, we recommend to use latest Chrome(Chromium) or Firefox(Non-private mode)." :error false)
        (state/set-indexedb-support! false)))
        (state/set-indexedb-support! false)))
 
 
+    (events/run!)
+
     (p/let [repos (get-repos)]
     (p/let [repos (get-repos)]
       (state/set-repos! repos)
       (state/set-repos! repos)
       (restore-and-setup! me repos logged?))
       (restore-and-setup! me repos logged?))
+
     (reset! db/*sync-search-indice-f search/sync-search-indice!)
     (reset! db/*sync-search-indice-f search/sync-search-indice!)
     (db/run-batch-txs!)
     (db/run-batch-txs!)
     (file-handler/run-writes-chan!)
     (file-handler/run-writes-chan!)
-    (editor-handler/periodically-save!)
+    (shortcut/install-shortcuts!)
     (when (util/electron?)
     (when (util/electron?)
       (el/listen!))))
       (el/listen!))))
+
+(defn stop! []
+  (prn "stop!"))

+ 72 - 175
src/main/frontend/handler/block.cljs

@@ -1,5 +1,6 @@
 (ns frontend.handler.block
 (ns frontend.handler.block
   (:require [frontend.util :as util]
   (:require [frontend.util :as util]
+            [frontend.util.property :as property]
             [clojure.walk :as walk]
             [clojure.walk :as walk]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.state :as state]
             [frontend.state :as state]
@@ -8,91 +9,12 @@
             [frontend.config :as config]
             [frontend.config :as config]
             [datascript.core :as d]
             [datascript.core :as d]
             [clojure.set :as set]
             [clojure.set :as set]
-            [medley.core :as medley]))
-
-(defn blocks->vec-tree [col]
-  (let [col (map (fn [h] (cond->
-                          h
-                           (not (:block/dummy? h))
-                           (dissoc h :block/meta))) col)
-        parent? (fn [item children]
-                  (and (seq children)
-                       (every? #(< (:block/level item) (:block/level %)) children)))
-        get-all-refs (fn [block]
-                       (let [refs (if-let [refs (seq (:block/refs-with-children block))]
-                                    refs
-                                    (concat
-                                     (:block/ref-pages block)
-                                     (:block/tags block)))]
-                         (distinct refs)))]
-    (loop [col (reverse col)
-           children (list)]
-      (if (empty? col)
-        children
-        (let [[item & others] col
-              cur-level (:block/level item)
-              bottom-level (:block/level (first children))
-              pre-block? (:block/pre-block? item)
-              item (assoc item :block/refs-with-children (->> (get-all-refs item)
-                                                              (remove nil?)))]
-          (cond
-            (empty? children)
-            (recur others (list item))
-
-            (<= bottom-level cur-level)
-            (recur others (conj children item))
-
-            pre-block?
-            (recur others (cons item children))
-
-            (> bottom-level cur-level)      ; parent
-            (let [[children other-children] (split-with (fn [h]
-                                                          (> (:block/level h) cur-level))
-                                                        children)
-                  refs-with-children (->> (mapcat get-all-refs (cons item children))
-                                          (remove nil?)
-                                          distinct)
-                  children (cons
-                            (assoc item
-                                   :block/children children
-                                   :block/refs-with-children refs-with-children)
-                            other-children)]
-              (recur others children))))))))
-
-;; recursively with children content for tree
-(defn get-block-content-rec
-  ([block]
-   (get-block-content-rec block (fn [block] (:block/content block))))
-  ([block transform-fn]
-   (let [contents (atom [])
-         _ (walk/prewalk
-            (fn [form]
-              (when (map? form)
-                (when-let [content (:block/content form)]
-                  (swap! contents conj (transform-fn form))))
-              form)
-            block)]
-     (apply util/join-newline @contents))))
-
-;; with children content
-(defn get-block-full-content
-  ([repo block-id]
-   (get-block-full-content repo block-id (fn [block] (:block/content block))))
-  ([repo block-id transform-fn]
-   (let [blocks (db/get-block-and-children-no-cache repo block-id)]
-     (->> blocks
-          (map transform-fn)
-          (apply util/join-newline)))))
-
-(defn get-block-end-pos-rec
-  [repo block]
-  (let [children (:block/children block)]
-    (if (seq children)
-      (get-block-end-pos-rec repo (last children))
-      (if-let [end-pos (get-in block [:block/meta :end-pos])]
-        end-pos
-        (when-let [block (db/entity repo [:block/uuid (:block/uuid block)])]
-          (get-in block [:block/meta :end-pos]))))))
+            [medley.core :as medley]
+            [frontend.format.block :as block]
+            [frontend.debug :as debug]
+            [clojure.string :as string]
+            [frontend.text :as text]
+            [frontend.handler.common :as common-handler]))
 
 
 (defn get-block-ids
 (defn get-block-ids
   [block]
   [block]
@@ -106,107 +28,50 @@
            block)]
            block)]
     @ids))
     @ids))
 
 
-(defn collapse-block!
-  [block]
-  (let [repo (:block/repo block)]
-    (db/transact! repo
-                  [{:block/uuid (:block/uuid block)
-                    :block/collapsed? true}])))
-
-(defn collapse-blocks!
-  [block-ids]
-  (let [repo (state/get-current-repo)]
-    (db/transact! repo
-                  (map
-                   (fn [id]
-                     {:block/uuid id
-                      :block/collapsed? true})
-                   block-ids))))
-
-(defn expand-block!
-  [block]
-  (let [repo (:block/repo block)]
-    (db/transact! repo
-                  [{:block/uuid (:block/uuid block)
-                    :block/collapsed? false}])))
-
-(defn expand-blocks!
-  [block-ids]
-  (let [repo (state/get-current-repo)]
-    (db/transact! repo
-                  (map
-                   (fn [id]
-                     {:block/uuid id
-                      :block/collapsed? false})
-                   block-ids))))
-
-(defn pre-block-with-only-title?
-  [repo block-id]
-  (when-let [block (db/entity repo [:block/uuid block-id])]
-    (let [properties (:page/properties (:block/page block))
-          property-names (keys properties)]
-      (and (every? #(contains? #{:title :filters} %) property-names)
-           (let [ast (mldoc/->edn (:block/content block) (mldoc/default-config (:block/format block)))]
-             (and
-              (= "Properties" (ffirst (first ast)))
-              (or
-               (empty? (rest ast))
-               (every? (fn [[[typ break-lines]] _]
-                         (and (= typ "Paragraph")
-                              (every? #(= % ["Break_Line"]) break-lines))) (rest ast)))))))))
-
+;; TODO: should we remove this dummy block and use the page's root block instead?
 (defn with-dummy-block
 (defn with-dummy-block
   ([blocks format]
   ([blocks format]
    (with-dummy-block blocks format {} {}))
    (with-dummy-block blocks format {} {}))
   ([blocks format default-option {:keys [journal? page-name]
   ([blocks format default-option {:keys [journal? page-name]
                                   :or {journal? false}}]
                                   :or {journal? false}}]
    (let [format (or format (state/get-preferred-format) :markdown)
    (let [format (or format (state/get-preferred-format) :markdown)
-         blocks (if (and journal?
-                         (seq blocks)
-                         (when-let [title (second (first (:block/title (first blocks))))]
-                           (date/valid-journal-title? title)))
-                  (rest blocks)
-                  blocks)
          blocks (vec blocks)]
          blocks (vec blocks)]
-     (cond
-       (and (seq blocks)
-            (or (and (> (count blocks) 1)
-                     (:block/pre-block? (first blocks)))
-                (and (>= (count blocks) 1)
-                     (not (:block/pre-block? (first blocks))))))
+     (if (seq blocks)
        blocks
        blocks
-
-       :else
-       (let [last-block (last blocks)
-             end-pos (get-in last-block [:block/meta :end-pos] 0)
-             dummy (merge last-block
-                          (let [uuid (d/squuid)]
-                            {:block/uuid uuid
-                             :block/title ""
-                             :block/content (config/default-empty-block format)
-                             :block/format format
-                             :block/level 2
-                             :block/priority nil
-                             :block/anchor (str uuid)
-                             :block/meta {:start-pos end-pos
-                                          :end-pos end-pos}
-                             :block/body nil
-                             :block/dummy? true
-                             :block/marker nil
-                             :block/pre-block? false})
-                          default-option)]
-         (conj blocks dummy))))))
+       (let [page-block (when page-name (db/pull [:block/name (string/lower-case page-name)]))
+             create-title-property? (util/include-windows-reserved-chars? page-name)
+             content (if create-title-property?
+                       (let [title (or (:block/original-name page-block)
+                                       (:block/name page-block))
+                             properties (common-handler/get-page-default-properties title)]
+                         (property/build-properties-str format properties))
+                       "")
+             page-id {:db/id (:db/id page-block)}
+             dummy (merge {:block/uuid (db/new-block-id)
+                           :block/left page-id
+                           :block/parent page-id
+                           :block/page page-id
+                           :block/title ""
+                           :block/content content
+                           :block/format format
+                           :block/dummy? true}
+                          default-option)
+             dummy (if (:db/id (:block/file dummy))
+                     dummy
+                     (dissoc dummy :block/file))]
+         [dummy])))))
 
 
 (defn filter-blocks
 (defn filter-blocks
   [repo ref-blocks filters group-by-page?]
   [repo ref-blocks filters group-by-page?]
   (let [ref-pages (->> (if group-by-page?
   (let [ref-pages (->> (if group-by-page?
                          (mapcat last ref-blocks)
                          (mapcat last ref-blocks)
                          ref-blocks)
                          ref-blocks)
-                       (mapcat (fn [b] (concat (:block/ref-pages b) (:block/children-refs b))))
+                       (mapcat (fn [b] (concat (:block/refs b) (:block/children-refs b))))
+                       (concat (when group-by-page? (map first ref-blocks)))
                        (distinct)
                        (distinct)
                        (map :db/id)
                        (map :db/id)
-                       (db/pull-many repo '[:db/id :page/name]))
-        ref-pages (zipmap (map :page/name ref-pages) (map :db/id ref-pages))
+                       (db/pull-many repo '[:db/id :block/name]))
+        ref-pages (zipmap (map :block/name ref-pages) (map :db/id ref-pages))
         exclude-ids (->> (map (fn [page] (get ref-pages page)) (get filters false))
         exclude-ids (->> (map (fn [page] (get ref-pages page)) (get filters false))
                          (remove nil?)
                          (remove nil?)
                          (set))
                          (set))
@@ -219,19 +84,51 @@
                        (cond->> ref-blocks
                        (cond->> ref-blocks
                          (seq exclude-ids)
                          (seq exclude-ids)
                          (remove (fn [block]
                          (remove (fn [block]
-                                   (let [ids (set (concat (map :db/id (:block/ref-pages block))
-                                                          (map :db/id (:block/children-refs block))))]
+                                   (let [ids (set (concat (map :db/id (:block/refs block))
+                                                          (map :db/id (:block/children-refs block))
+                                                          [(:db/id (:block/page block))]))]
                                      (seq (set/intersection exclude-ids ids)))))
                                      (seq (set/intersection exclude-ids ids)))))
 
 
                          (seq include-ids)
                          (seq include-ids)
                          (remove (fn [block]
                          (remove (fn [block]
-                                   (let [ids (set (concat (map :db/id (:block/ref-pages block))
+                                   (let [page-block-id (:db/id (:block/page block))
+                                         ids (set (concat (map :db/id (:block/refs block))
                                                           (map :db/id (:block/children-refs block))))]
                                                           (map :db/id (:block/children-refs block))))]
-                                     (empty? (set/intersection include-ids ids)))))
-                         ))]
+                                     (if (and (contains? include-ids page-block-id)
+                                              (= 1 (count include-ids)))
+                                       (not= page-block-id (first include-ids))
+                                       (empty? (set/intersection include-ids (set (conj ids page-block-id))))))))))]
         (if group-by-page?
         (if group-by-page?
           (->> (map (fn [[p ref-blocks]]
           (->> (map (fn [[p ref-blocks]]
                       [p (filter-f ref-blocks)]) ref-blocks)
                       [p (filter-f ref-blocks)]) ref-blocks)
                (remove #(empty? (second %))))
                (remove #(empty? (second %))))
           (->> (filter-f ref-blocks)
           (->> (filter-f ref-blocks)
                (remove nil?)))))))
                (remove nil?)))))))
+
+;; TODO: reduced version
+(defn walk-block
+  [block check? transform]
+  (let [result (atom nil)]
+    (walk/postwalk
+     (fn [x]
+       (if (check? x)
+         (reset! result (transform x))
+         x))
+     (:block/body block))
+    @result))
+
+(defn get-timestamp
+  [block typ]
+  (walk-block block
+              (fn [x]
+                (and (block/timestamp-block? x)
+                     (= typ (first (second x)))))
+              #(second (second %))))
+
+(defn get-scheduled-ast
+  [block]
+  (get-timestamp block "Scheduled"))
+
+(defn get-deadline-ast
+  [block]
+  (get-timestamp block "Deadline"))

+ 11 - 4
src/main/frontend/handler/common.cljs

@@ -4,7 +4,7 @@
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
             [promesa.core :as p]
             [promesa.core :as p]
             [frontend.util :as util]
             [frontend.util :as util]
-            [frontend.text :as text]
+            [frontend.util.property :as property]
             [frontend.git :as git]
             [frontend.git :as git]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.encrypt :as e]
             [frontend.encrypt :as e]
@@ -15,7 +15,8 @@
             [cljs-time.format :as tf]
             [cljs-time.format :as tf]
             [frontend.config :as config]
             [frontend.config :as config]
             ["ignore" :as Ignore]
             ["ignore" :as Ignore]
-            ["/frontend/utils" :as utils]))
+            ["/frontend/utils" :as utils]
+            [frontend.date :as date]))
 
 
 (defn get-ref
 (defn get-ref
   [repo-url]
   [repo-url]
@@ -55,8 +56,8 @@
                  (js/console.dir error)))))))
                  (js/console.dir error)))))))
 
 
 (defn copy-to-clipboard-without-id-property!
 (defn copy-to-clipboard-without-id-property!
-  [content]
-  (util/copy-to-clipboard! (text/remove-id-property content)))
+  [format content]
+  (util/copy-to-clipboard! (property/remove-id-property format content)))
 
 
 (defn config-with-document-mode
 (defn config-with-document-mode
   [config]
   [config]
@@ -160,3 +161,9 @@
                    (do (log/error :token/failed-get-token token-m)
                    (do (log/error :token/failed-get-token token-m)
                        (reject)))))
                        (reject)))))
              nil))))))))
              nil))))))))
+
+(defn get-page-default-properties
+  [page-name]
+  {:title page-name
+   ;; :date (date/get-date-time-string)
+   })

+ 5 - 24
src/main/frontend/handler/config.cljs

@@ -1,37 +1,18 @@
 (ns frontend.handler.config
 (ns frontend.handler.config
   (:require [frontend.state :as state]
   (:require [frontend.state :as state]
             [frontend.handler.file :as file-handler]
             [frontend.handler.file :as file-handler]
-            [borkdude.rewrite-edn :as rewrite]
             [frontend.config :as config]
             [frontend.config :as config]
-            [frontend.db :as db]
             [clojure.string :as string]))
             [clojure.string :as string]))
 
 
 (defn set-config!
 (defn set-config!
   [k v]
   [k v]
-  (when-let [repo (state/get-current-repo)]
-    (let [path (config/get-config-path)]
-      (when-let [config (db/get-file-no-sub path)]
-        (let [config (try
-                       (rewrite/parse-string config)
-                       (catch js/Error e
-                         (println "Parsing config file failed: ")
-                         (js/console.dir e)
-                         {}))
-              ks (if (vector? k) k [k])
-              new-config (rewrite/assoc-in config ks v)]
-          (state/set-config! repo new-config)
-          (let [new-content (str new-config)]
-            (file-handler/set-file-content! repo path new-content)))))))
+  (let [path (config/get-config-path)]
+    (file-handler/edn-file-set-key-value path k v state/set-config!)))
 
 
 (defn toggle-ui-show-brackets! []
 (defn toggle-ui-show-brackets! []
   (let [show-brackets? (state/show-brackets?)]
   (let [show-brackets? (state/show-brackets?)]
     (set-config! :ui/show-brackets? (not show-brackets?))))
     (set-config! :ui/show-brackets? (not show-brackets?))))
 
 
-(defn set-project!
-  [project]
-  (when-not (string/blank? project)
-    (set-config! [:project :name] project)))
-
-(defn set-preferred-workflow!
-  [workflow]
-  (set-config! :preferred-workflow (name workflow)))
+(defn toggle-logical-outdenting! []
+  (let [logical-outdenting? (state/logical-outdenting?)]
+    (set-config! :editor/logical-outdenting? (not logical-outdenting?))))

+ 42 - 414
src/main/frontend/handler/dnd.cljs

@@ -1,357 +1,25 @@
 (ns frontend.handler.dnd
 (ns frontend.handler.dnd
-  (:require [frontend.handler.notification :as notification]
-            [frontend.handler.repo :as repo-handler]
-            [frontend.handler.block :as block-handler]
-            [frontend.config :as config]
-            [frontend.state :as state]
-            [frontend.util :as util :refer-macros [profile]]
+  (:require [frontend.state :as state]
             [frontend.db :as db]
             [frontend.db :as db]
-            [clojure.walk :as walk]
-            [clojure.string :as string]
-            [frontend.utf8 :as utf8]
-            [cljs-time.coerce :as tc]
-            [cljs-time.core :as t]))
-
-(defn- remove-block-child!
-  [target-block parent-block]
-  (let [child-ids (set (block-handler/get-block-ids target-block))]
-    (block-handler/get-block-content-rec
-     parent-block
-     (fn [{:block/keys [uuid level content]}]
-       (if (contains? child-ids uuid)
-         ""
-         content)))))
-
-(defn- recompute-block-level
-  [to-block nested?]
-  (+ (:block/level to-block)
-     (if nested? 1 0)))
-
-(defn- recompute-block-content-and-changes
-  [target-block to-block nested? same-repo? same-file?]
-  (let [new-level (recompute-block-level to-block nested?)
-        target-level (:block/level target-block)
-        format (:block/format target-block)
-        pattern (config/get-block-pattern format)
-        block-changes (atom [])
-        all-content (block-handler/get-block-content-rec
-                     target-block
-                     (fn [{:block/keys [uuid level content]
-                           :as block}]
-                       (let [new-level (+ new-level (- level target-level))
-                             new-content (string/replace-first content
-                                                               (apply str (repeat level pattern))
-                                                               (apply str (repeat new-level pattern)))
-                             block (cond->
-                                    {:block/uuid uuid
-                                     :block/level new-level
-                                     :block/content new-content
-                                     :block/page (:block/page to-block)}
-
-                                     (not same-repo?)
-                                     (merge (dissoc block [:block/level :block/content]))
-
-                                     (not same-file?)
-                                     (merge {:block/page (:block/page to-block)
-                                             :block/file (:block/file to-block)}))]
-                         (swap! block-changes conj block)
-                         new-content)))]
-    [all-content @block-changes]))
-
-(defn- move-parent-to-child?
-  [target-block to-block]
-  (let [to-block-id (:block/uuid to-block)
-        result (atom false)
-        _ (walk/postwalk
-           (fn [form]
-             (when (map? form)
-               (when-let [id (:block/uuid form)]
-                 (when (= id to-block-id)
-                   (reset! result true))))
-             form)
-           target-block)]
-    @result))
-
-(defn- compute-target-child?
-  [target-block to-block]
-  (let [target-block-id (:block/uuid target-block)
-        result (atom false)
-        _ (walk/postwalk
-           (fn [form]
-             (when (map? form)
-               (when-let [id (:block/uuid form)]
-                 (when (= id target-block-id)
-                   (reset! result true))))
-             form)
-           to-block)]
-    @result))
-
-(defn rebuild-dnd-blocks
-  [repo file target-child? start-pos target-blocks offset-block-uuid {:keys [delete? same-file?]
-                                                                      :or {delete? false
-                                                                           same-file? true}}]
-  (when (seq target-blocks)
-    (let [file-id (:db/id file)
-          target-block-ids (set (map :block/uuid target-blocks))
-          after-blocks (->> (db/get-file-after-blocks repo file-id start-pos)
-                            (remove (fn [h] (contains? target-block-ids (:block/uuid h)))))
-
-          after-blocks (cond
-                         delete?
-                         after-blocks
-
-                         (and offset-block-uuid
-                              (not (contains? (set (map :block/uuid after-blocks)) offset-block-uuid)))
-                         (concat target-blocks after-blocks)
-
-                         offset-block-uuid
-                         (let [[before after] (split-with (fn [h] (not= (:block/uuid h)
-                                                                        offset-block-uuid)) after-blocks)]
-                           (concat (conj (vec before) (first after))
-                                   target-blocks
-                                   (rest after)))
-                         :else
-                         (concat target-blocks after-blocks))
-          after-blocks (remove nil? after-blocks)
-          ;; _ (prn {:start-pos start-pos
-          ;;         :target-blocks target-blocks
-          ;;         :after-blocks (map (fn [block]
-          ;;                                (:block/content block))
-          ;;                           after-blocks)})
-          last-start-pos (atom start-pos)
-          result (mapv
-                  (fn [{:block/keys [uuid meta content level page] :as block}]
-                    (let [content (str (util/trim-safe content) "\n")
-                          target-block? (contains? target-block-ids uuid)
-                          content-length (if target-block?
-                                           (utf8/length (utf8/encode content))
-                                           (- (:end-pos meta) (:start-pos meta)))
-                          new-end-pos (+ @last-start-pos content-length)
-                          new-meta {:start-pos @last-start-pos
-                                    :end-pos new-end-pos}]
-                      (reset! last-start-pos new-end-pos)
-                      (let [data {:block/uuid uuid
-                                  :block/meta new-meta}]
-                        (cond
-                          (and target-block? (not same-file?))
-                          (merge
-                           (dissoc block :block/idx :block/dummy?)
-                           data)
-
-                          target-block?
-                          (merge
-                           data
-                           {:block/level level
-                            :block/content content
-                            :block/page page})
-
-                          :else
-                          data))))
-                  after-blocks)]
-      result)))
-
-(defn- get-start-pos
-  [block]
-  (get-in block [:block/meta :start-pos]))
-
-(defn- get-end-pos
-  [block]
-  (get-in block [:block/meta :end-pos]))
-
-(defn- compute-direction
-  [target-block top-block nested? top? target-child?]
-  (cond
-    (= top-block target-block)
-    :down
-
-    (and target-child? nested?)
-    :up
-
-    (and target-child? (not top?))
-    :down
-
-    :else
-    :up))
-
-(defn- compute-after-blocks-in-same-file
-  [repo target-block to-block direction top? nested? target-child? target-file original-top-block-start-pos block-changes]
-  (cond
-    top?
-    (rebuild-dnd-blocks repo target-file target-child?
-                        original-top-block-start-pos
-                        block-changes
-                        nil
-                        {})
-
-    (= direction :up)
-    (let [offset-block-id (if nested?
-                            (:block/uuid to-block)
-                            (last (block-handler/get-block-ids to-block)))
-          offset-end-pos (get-end-pos
-                          (db/entity repo [:block/uuid offset-block-id]))]
-      (rebuild-dnd-blocks repo target-file target-child?
-                          offset-end-pos
-                          block-changes
-                          nil
-                          {}))
-
-    (= direction :down)
-    (let [offset-block-id (if nested?
-                            (:block/uuid to-block)
-                            (last (block-handler/get-block-ids to-block)))
-          target-start-pos (get-start-pos target-block)]
-      (rebuild-dnd-blocks repo target-file target-child?
-                          target-start-pos
-                          block-changes
-                          offset-block-id
-                          {}))))
-
-;; TODO: still could be different pages, e.g. move a block from one journal to another journal
-(defn- move-block-in-same-file
-  [repo target-block to-block top-block bottom-block nested? top? target-child? direction target-content target-file original-top-block-start-pos block-changes]
-  (if (move-parent-to-child? target-block to-block)
-    nil
-    (let [old-file-content (db/get-file (:file/path (db/entity (:db/id (:block/file target-block)))))
-          old-file-content (utf8/encode old-file-content)
-          subs (fn [start-pos end-pos] (utf8/substring old-file-content start-pos end-pos))
-          bottom-content (block-handler/get-block-content-rec bottom-block)
-          top-content (remove-block-child! bottom-block top-block)
-          top-area (subs 0 (get-start-pos top-block))
-          bottom-area (subs
-                       (cond
-                         (and nested? (= direction :down))
-                         (get-end-pos bottom-block)
-                         target-child?
-                         (block-handler/get-block-end-pos-rec repo top-block)
-                         :else
-                         (block-handler/get-block-end-pos-rec repo bottom-block))
-                       nil)
-          between-area (if (= direction :down)
-                         (subs (block-handler/get-block-end-pos-rec repo target-block) (get-start-pos to-block))
-                         (subs (block-handler/get-block-end-pos-rec repo to-block) (get-start-pos target-block)))
-          up-content (when (= direction :up)
-                       (cond
-                         nested?
-                         (util/join-newline (:block/content top-block)
-                                            target-content
-                                            (if target-child?
-                                              (remove-block-child! target-block (:block/children to-block))
-                                              (block-handler/get-block-content-rec (:block/children top-block))))
-                         (and top? target-child?)
-                         (util/join-newline target-content (remove-block-child! target-block to-block))
-
-                         top?
-                         (util/join-newline target-content top-content)
-
-                         :else
-                         (let [top-content (if target-child?
-                                             (remove-block-child! target-block to-block)
-                                             top-content)]
-                           (util/join-newline top-content target-content))))
-          down-content (when (= direction :down)
-                         (cond
-                           nested?
-                           (util/join-newline (:block/content bottom-block)
-                                              target-content)
-                           target-child?
-                           (util/join-newline top-content target-content)
-
-                           :else
-                           (util/join-newline bottom-content target-content)))
-          ;; _ (prn {:direction direction
-          ;;         :nested? nested?
-          ;;         :top? top?
-          ;;         :target-child? target-child?
-          ;;         :top-area top-area
-          ;;         :up-content up-content
-          ;;         :between-area between-area
-          ;;         :down-content down-content
-          ;;         :bottom-area bottom-area
-          ;;         })
-          new-file-content (string/trim
-                            (util/join-newline
-                             top-area
-                             up-content
-                             between-area
-                             down-content
-                             bottom-area))
-          after-blocks (->> (compute-after-blocks-in-same-file repo target-block to-block direction top? nested? target-child? target-file original-top-block-start-pos block-changes)
-                            (remove nil?))
-          path (:file/path (db/entity repo (:db/id (:block/file to-block))))]
-      (profile
-       "Move block in the same file: "
-       (repo-handler/transact-react-and-alter-file!
-        repo
-        (concat
-         after-blocks)
-        {:key :block/change
-         :data block-changes}
-        [[path new-file-content]]))
-      ;; (alter-file repo
-      ;;             path
-      ;;             new-file-content
-      ;;             {:re-render-root? true})
-)))
-
-(defn- move-block-in-different-files
-  [repo target-block to-block top-block bottom-block nested? top? target-child? direction target-content target-file original-top-block-start-pos block-changes]
-  (let [target-file (db/entity repo (:db/id (:block/file target-block)))
-        target-file-path (:file/path target-file)
-        target-file-content (db/get-file repo target-file-path)
-        to-file (db/entity repo (:db/id (:block/file to-block)))
-        to-block (assoc to-block :block/meta (:block/meta (db/entity (:db/id to-block))))
-        to-file-path (:file/path to-file)
-        target-block-end-pos (block-handler/get-block-end-pos-rec repo target-block)
-        to-block-start-pos (get-start-pos to-block)
-        to-block-end-pos (block-handler/get-block-end-pos-rec repo to-block)
-        new-target-file-content (utf8/delete! target-file-content
-                                              (get-start-pos target-block)
-                                              target-block-end-pos)
-        to-file-content (utf8/encode (db/get-file repo to-file-path))
-        new-to-file-content (let [separate-pos (cond nested?
-                                                     (get-end-pos to-block)
-                                                     top?
-                                                     to-block-start-pos
-                                                     :else
-                                                     to-block-end-pos)]
-                              (string/trim
-                               (util/join-newline
-                                (utf8/substring to-file-content 0 separate-pos)
-                                target-content
-                                (utf8/substring to-file-content separate-pos))))
-        target-after-blocks (rebuild-dnd-blocks repo target-file target-child?
-                                                (get-start-pos target-block)
-                                                block-changes nil {:delete? true})
-        to-after-blocks (cond
-                          top?
-                          (rebuild-dnd-blocks repo to-file target-child?
-                                              (get-start-pos to-block)
-                                              block-changes
-                                              nil
-                                              {:same-file? false})
-
-                          :else
-                          (let [offset-block-id (if nested?
-                                                  (:block/uuid to-block)
-                                                  (last (block-handler/get-block-ids to-block)))
-                                offset-end-pos (get-end-pos
-                                                (db/entity repo [:block/uuid offset-block-id]))]
-                            (rebuild-dnd-blocks repo to-file target-child?
-                                                offset-end-pos
-                                                block-changes
-                                                nil
-                                                {:same-file? false})))]
-    (profile
-     "Move block between different files: "
-     (repo-handler/transact-react-and-alter-file!
-      repo
-      (concat
-       target-after-blocks
-       to-after-blocks)
-      {:key :block/change
-       :data (conj block-changes target-block)}
-      [[target-file-path new-target-file-content]
-       [to-file-path new-to-file-content]]))))
+            [frontend.modules.outliner.core :as outliner-core]
+            [frontend.modules.outliner.tree :as tree]
+            [lambdaisland.glogi :as log]
+            [frontend.debug :as debug]))
+
+
+(defn- moveable?
+  [current-block target-block]
+  (let [current-block-uuid (:block/uuid current-block)]
+    (or
+     (not= (:block/page current-block) (:block/page target-block))
+     (and
+      (not= current-block-uuid (:block/uuid target-block))
+      (loop [loc target-block]
+        (if-let [parent (db/pull (:db/id (:block/parent loc)))]
+          (if (= (:block/uuid parent) current-block-uuid)
+            false
+            (recur parent))
+          true))))))
 
 
 (defn move-block
 (defn move-block
   "There can be at least 3 possible situations:
   "There can be at least 3 possible situations:
@@ -364,65 +32,25 @@
      we don't handle this now. TODO: transform between different formats in mldoc.
      we don't handle this now. TODO: transform between different formats in mldoc.
   2. Sometimes we might need to move a parent block to it's own child.
   2. Sometimes we might need to move a parent block to it's own child.
   "
   "
-  [target-block to-block target-dom-id top? nested?]
-  (when (and
-         target-block
-         to-block
-         (:block/format target-block)
-         (:block/format to-block)
-         (not (:block/dummy? to-block))
-         (not (:block/dummy? target-block)))
-    (cond
-      (not= (:block/format target-block)
-            (:block/format to-block))
-      (notification/show!
-       (util/format "Sorry, you can't move a block of format %s to another file of format %s."
-                    (:block/format target-block)
-                    (:block/format to-block))
-       :error)
-
-      (= (:block/uuid target-block) (:block/uuid to-block))
-      nil
-
-      :else
-      (let [pattern (config/get-block-pattern (:block/format to-block))
-            target-block-repo (:block/repo target-block)
-            to-block-repo (:block/repo to-block)
-            target-block (assoc target-block
-                                :block/meta
-                                (:block/meta (db/entity target-block-repo [:block/uuid (:block/uuid target-block)])))
-            to-block (assoc to-block
-                            :block/meta
-                            (:block/meta (db/entity [:block/uuid (:block/uuid to-block)])))
-            same-repo? (= target-block-repo to-block-repo)
-            target-file (:block/file target-block)
-            same-file? (and
-                        same-repo?
-                        (= (:db/id target-file)
-                           (:db/id (:block/file to-block))))
-            [top-block bottom-block] (if same-file?
-                                       (if (< (get-start-pos target-block)
-                                              (get-start-pos to-block))
-                                         [target-block to-block]
-                                         [to-block target-block])
-                                       [nil nil])
-            target-child? (compute-target-child? target-block to-block)
-            direction (compute-direction target-block top-block nested? top? target-child?)
-            original-top-block-start-pos (get-start-pos top-block)
-            [target-content block-changes] (recompute-block-content-and-changes target-block to-block nested? same-repo? same-file?)]
-        (cond
-          same-file?
-          (move-block-in-same-file target-block-repo target-block to-block top-block bottom-block nested? top? target-child? direction target-content target-file original-top-block-start-pos block-changes)
-
-          ;; same repo but different files
-          same-repo?
-          (move-block-in-different-files target-block-repo target-block to-block top-block bottom-block nested? top? target-child? direction target-content target-file original-top-block-start-pos block-changes)
-
-          ;; different repos
-          :else
-          nil)
-
-        (when (state/get-git-auto-push?)
-          (doseq [repo (->> #{target-block-repo to-block-repo}
-                            (remove nil?))]
-            (repo-handler/push repo nil)))))))
+  [current-block target-block top? nested?]
+  (when (and (every? map? [current-block target-block])
+             (moveable? current-block target-block))
+    (let [[current-node target-node]
+          (mapv outliner-core/block [current-block target-block])]
+      (cond
+        top?
+        (let [first-child?
+              (= (tree/-get-parent-id target-node)
+                (tree/-get-left-id target-node))]
+          (if first-child?
+            (let [parent (tree/-get-parent target-node)]
+              (outliner-core/move-subtree current-node parent false))
+            (outliner-core/move-subtree current-node target-node true)))
+        nested?
+        (outliner-core/move-subtree current-node target-node false)
+
+        :else
+        (outliner-core/move-subtree current-node target-node true))
+      (let [repo (state/get-current-repo)]
+        (db/refresh! repo {:key :block/change
+                           :data [(:data current-node) (:data target-node)]})))))

+ 4 - 10
src/main/frontend/handler/draw.cljs

@@ -6,13 +6,8 @@
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.handler.file :as file-handler]
             [frontend.handler.file :as file-handler]
-            [frontend.handler.git :as git-handler]
             [frontend.date :as date]
             [frontend.date :as date]
-            [frontend.config :as config]
-            [frontend.storage :as storage]
-            [clojure.string :as string]
-            [cljs-time.core :as t]
-            [cljs-time.coerce :as tc]))
+            [frontend.config :as config]))
 
 
 (defn create-draws-directory!
 (defn create-draws-directory!
   [repo]
   [repo]
@@ -33,12 +28,11 @@
          (p/do!
          (p/do!
           (create-draws-directory! repo)
           (create-draws-directory! repo)
           (fs/write-file! repo repo-dir path data nil)
           (fs/write-file! repo repo-dir path data nil)
-          (git-handler/git-add repo path)
           (db/transact! repo
           (db/transact! repo
                         [{:file/path path
                         [{:file/path path
-                          :page/name file
-                          :page/file [:file/path path]
-                          :page/journal? false}]))
+                          :block/name file
+                          :block/file [:file/path path]
+                          :block/journal? false}]))
          (p/catch (fn [error]
          (p/catch (fn [error]
                     (prn "Write file failed, path: " path ", data: " data)
                     (prn "Write file failed, path: " path ", data: " data)
                     (js/console.dir error))))))))
                     (js/console.dir error))))))))

文件差异内容过多而无法显示
+ 382 - 628
src/main/frontend/handler/editor.cljs


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

@@ -18,14 +18,14 @@
        (let [target (.-target e)]
        (let [target (.-target e)]
          (if (d/has-class? target "bottom-action") ;; FIXME: not particular case
          (if (d/has-class? target "bottom-action") ;; FIXME: not particular case
            (.preventDefault e)
            (.preventDefault e)
-           (let [{:keys [on-hide format value block id repo dummy?]} (editor-handler/get-state state)]
+           (let [{:keys [on-hide format value block id repo dummy?]} (editor-handler/get-state)]
              (when on-hide
              (when on-hide
                (on-hide value event))
                (on-hide value event))
              (when
              (when
               (or (= event :esc)
               (or (= event :esc)
                   (= event :visibilitychange)
                   (= event :visibilitychange)
                   (and (= event :click)
                   (and (= event :click)
-                       (not (editor-handler/in-auto-complete? (gobj/get target "id")))))
+                       (not (editor-handler/auto-complete?))))
                (state/clear-edit!))))))
                (state/clear-edit!))))))
      :node (gdom/getElement id)
      :node (gdom/getElement id)
     ;; :visibilitychange? true
     ;; :visibilitychange? true

+ 5 - 33
src/main/frontend/handler/editor/lifecycle.cljs

@@ -1,10 +1,8 @@
 (ns frontend.handler.editor.lifecycle
 (ns frontend.handler.editor.lifecycle
   (:require [frontend.state :as state]
   (:require [frontend.state :as state]
-            [lambdaisland.glogi :as log]
             [goog.dom :as gdom]
             [goog.dom :as gdom]
             [goog.object :as gobj]
             [goog.object :as gobj]
             [clojure.string :as string]
             [clojure.string :as string]
-            [frontend.util :as util :refer-macros [profile]]
             [cljs-drag-n-drop.core :as dnd]
             [cljs-drag-n-drop.core :as dnd]
             [frontend.handler.editor.keyboards :as keyboards-handler]
             [frontend.handler.editor.keyboards :as keyboards-handler]
             [frontend.handler.page :as page-handler]
             [frontend.handler.page :as page-handler]
@@ -22,11 +20,7 @@
         input (gdom/getElement id)]
         input (gdom/getElement id)]
     (when block-parent-id
     (when block-parent-id
       (state/set-editing-block-dom-id! block-parent-id))
       (state/set-editing-block-dom-id! block-parent-id))
-    (if (= :indent-outdent (state/get-editor-op))
-      (when input
-        (when-let [pos (state/get-edit-pos)]
-          (util/set-caret-pos! input pos)))
-      (editor-handler/restore-cursor-pos! id content dummy?))
+    (editor-handler/restore-cursor-pos! id content dummy?)
 
 
     (when input
     (when input
       (dnd/subscribe!
       (dnd/subscribe!
@@ -50,7 +44,7 @@
 
 
 (defn will-unmount
 (defn will-unmount
   [state]
   [state]
-  (let [{:keys [id value format block repo dummy? config]} (get-state state)
+  (let [{:keys [id value format block repo dummy? config]} (get-state)
         file? (:file? config)]
         file? (:file? config)]
     (when-let [input (gdom/getElement id)]
     (when-let [input (gdom/getElement id)]
       ;; (.removeEventListener input "paste" (fn [event]
       ;; (.removeEventListener input "paste" (fn [event]
@@ -64,31 +58,9 @@
               input
               input
               :upload-images))))
               :upload-images))))
     (editor-handler/clear-when-saved!)
     (editor-handler/clear-when-saved!)
-    (if file?
-      (let [path (:file-path config)
-            content (db/get-file-no-sub path)
-            value (some-> (gdom/getElement path)
-                          (gobj/get "value"))]
-        (when (and
-               (not (string/blank? value))
-               (not= (string/trim value) (string/trim content)))
-          (let [old-page-name (db/get-file-page path false)
-                journal? (date/valid-journal-title? path)]
-            (p/let [[journal? new-name] (page-handler/rename-when-alter-title-property! old-page-name path format content value)]
-              (if (and journal? new-name (not= old-page-name (string/lower-case new-name)))
-                (notification/show! "Journal title can't be changed." :warning)
-                (let [new-name (if journal? (date/journal-title->default new-name) new-name)
-                      new-path (if new-name
-                                (if (and
-                                     new-name old-page-name
-                                     (= (string/lower-case new-name) (string/lower-case old-page-name)))
-                                  path
-                                  (page-handler/compute-new-file-path path new-name))
-                                path)]
-                  (file/alter-file (state/get-current-repo) new-path (string/trim value)
-                                   {:re-render-root? true})))))))
-      (when-not (contains? #{:insert :indent-outdent :auto-save} (state/get-editor-op))
-        (editor-handler/save-block! (get-state state) value))))
+    ;; TODO: ugly
+    (when-not (contains? #{:insert :indent-outdent :auto-save :undo :redo} (state/get-editor-op))
+      (editor-handler/save-block! (get-state) value)))
   state)
   state)
 
 
 (def lifecycle
 (def lifecycle

+ 80 - 0
src/main/frontend/handler/events.cljs

@@ -0,0 +1,80 @@
+(ns frontend.handler.events
+  (:refer-clojure :exclude [run!])
+  (:require [frontend.state :as state]
+            [clojure.core.async :as async]
+            [frontend.spec :as spec]
+            [frontend.ui :as ui]
+            [frontend.util :as util :refer-macros [profile]]
+            [frontend.config :as config]
+            [frontend.handler.notification :as notification]
+            [frontend.components.encryption :as encryption]
+            [frontend.fs.nfs :as nfs]))
+
+;; TODO: should we move all events here?
+
+(defn show-install-error!
+  [repo-url title]
+  (spec/validate :repos/url repo-url)
+  (notification/show!
+   [:p.content
+    title
+    " "
+    [:span.mr-2
+     (util/format
+      "Please make sure that you've installed the logseq app for the repo %s on GitHub. "
+      repo-url)
+     (ui/button
+      "Install Logseq on GitHub"
+      :href (str "https://github.com/apps/" config/github-app-name "/installations/new"))]]
+   :error
+   false))
+
+(defmulti handle first)
+
+(defmethod handle :repo/install-error [[_ repo-url title]]
+  (show-install-error! repo-url title))
+
+(defmethod handle :modal/encryption-setup-dialog [[_ repo-url close-fn]]
+  (state/set-modal!
+   (encryption/encryption-setup-dialog repo-url close-fn)))
+
+(defmethod handle :modal/encryption-input-secret-dialog [[_ repo-url db-encrypted-secret close-fn]]
+  (state/set-modal!
+   (encryption/encryption-input-secret-dialog
+    repo-url
+    db-encrypted-secret
+    close-fn)))
+
+
+(defn get-local-repo
+  []
+  (when-let [repo (state/get-current-repo)]
+    (when (config/local-db? repo)
+      repo)))
+
+(defn ask-permission
+  [repo]
+  (when-not (util/electron?)
+    (fn [close-fn]
+      [:div
+       [:p
+        "Grant native filesystem permission for directory: "
+        [:b (config/get-local-dir repo)]]
+       (ui/button
+        "Grant"
+        :on-click (fn []
+                    (nfs/check-directory-permission! repo)
+                    (close-fn)))])))
+
+(defmethod handle :modal/nfs-ask-permission []
+  (when-let [repo (get-local-repo)]
+    (state/set-modal! (ask-permission repo))))
+
+(defn run!
+  []
+  (let [chan (state/get-events-chan)]
+    (async/go-loop []
+      (let [payload (async/<! chan)]
+        (handle payload))
+      (recur))
+    chan))

+ 0 - 102
src/main/frontend/handler/expand.cljs

@@ -1,102 +0,0 @@
-(ns frontend.handler.expand
-  (:require [dommy.core :as d]
-            [goog.dom :as gdom]
-            [goog.object :as gobj]
-            [frontend.util :as util]
-            [frontend.state :as state]
-            [frontend.handler.block :as block-handler]))
-
-(defn- hide!
-  [element]
-  (d/set-style! element :display "none"))
-
-(defn- show!
-  [element]
-  (d/set-style! element :display ""))
-
-(defn collapse!
-  [block]
-  (let [has-title? (seq (:block/title block))
-        uuid (:block/uuid block)
-        nodes (array-seq (js/document.getElementsByClassName (str uuid)))]
-    (doseq [node nodes]
-      (d/add-class! node "collapsed")
-      (when has-title?
-        (when-let [e (.querySelector node ".block-body")]
-         (hide! e)))
-      (when-let [e (.querySelector node ".block-children")]
-        (hide! e)
-        (let [elements (d/by-class node "ls-block")]
-          (doseq [element elements]
-            (hide! element))))
-      (block-handler/collapse-block! block))))
-
-(defn expand!
-  [block]
-  (let [uuid (:block/uuid block)
-        nodes (array-seq (js/document.getElementsByClassName (str uuid)))]
-    (doseq [node nodes]
-      (when-let [e (.querySelector node ".block-body")]
-        (show! e))
-      (when-let [e (.querySelector node ".block-children")]
-        (let [elements (d/by-class node "ls-block")]
-          (doseq [element elements]
-            (show! element)))
-        (show! e))
-      (block-handler/expand-block! block))))
-
-(defn set-bullet-closed!
-  [element]
-  (when element
-    (when-let [node (.querySelector element ".bullet-container")]
-      (d/add-class! node "bullet-closed"))))
-
-;; Collapse acts like TOC
-;; There are three modes to cycle:
-;; 1. Collapse all blocks which levels are greater than 2
-;; 2. Hide all block's body (user can still see the block title)
-;; 3. Show everything
-(defn cycle!
-  []
-  (let [mode (state/next-collapse-mode)
-        get-blocks (fn []
-                     (let [elements (d/by-class "ls-block")
-                           result (group-by (fn [e]
-                                              (let [level (d/attr e "level")]
-                                                (and level
-                                                     (> (util/parse-int level) 2)))) elements)]
-                       [(get result true) (get result false)]))]
-    (case mode
-      :show-all
-      (do
-        (doseq [element (d/by-class "ls-block")]
-          (show! element))
-        (let [elements (d/by-class "block-body")]
-          (doseq [element elements]
-            (show! element)))
-        (doseq [element (d/by-class "bullet-closed")]
-          (d/remove-class! element "bullet-closed"))
-        (doseq [element (d/by-class "block-children")]
-          (show! element)))
-
-      :hide-block-body
-      (let [elements (d/by-class "block-body")]
-        (doseq [element elements]
-          (d/set-style! element :display "none")
-          (when-let [parent (util/rec-get-block-node element)]
-            (set-bullet-closed! parent))))
-
-      :hide-block-children
-      (let [[elements top-level-elements] (get-blocks)
-            level-2-elements (filter (fn [e]
-                                       (let [level (d/attr e "level")]
-                                         (and level
-                                              (= (util/parse-int level) 2)
-                                              (not (d/has-class? e "pre-block")))))
-                                     top-level-elements)]
-        (doseq [element elements]
-          (hide! element))
-        (doseq [element level-2-elements]
-          (when (= "true" (d/attr element "haschild"))
-            (set-bullet-closed! element)))))
-    (state/cycle-collapse!)))

+ 160 - 66
src/main/frontend/handler/export.cljs

@@ -13,13 +13,46 @@
             [frontend.text :as text]
             [frontend.text :as text]
             [frontend.handler.common :as common-handler]
             [frontend.handler.common :as common-handler]
             [frontend.extensions.zip :as zip]
             [frontend.extensions.zip :as zip]
+            [frontend.modules.file.core :as outliner-file]
+            [frontend.modules.outliner.tree :as outliner-tree]
             [promesa.core :as p]))
             [promesa.core :as p]))
 
 
+
+(defn- get-page-content
+  [page]
+  (outliner-file/tree->file-content
+   (outliner-tree/blocks->vec-tree
+    (db/get-page-blocks-no-cache page) page) {:init-level 1}))
+
+(defn- get-page-content-debug
+  [page]
+  (outliner-file/tree->file-content
+   (outliner-tree/blocks->vec-tree
+    (db/get-page-blocks-no-cache page) page) {:init-level 1
+                                              :heading-to-list? true}))
+
+(defn- get-file-content
+  [file-path]
+  (let [page-name
+        (ffirst (d/q '[:find ?pn
+                       :where
+                       [?e :file/path file-path]
+                       [?p :block/file ?e]
+                       [?p :block/name ?pn]] (db/get-conn)))]
+    (get-page-content page-name)))
+
+(defn- get-blocks-contents
+  [repo root-block-uuid]
+  (->
+   (db/get-block-and-children repo root-block-uuid)
+   (outliner-tree/blocks->vec-tree (str root-block-uuid))
+   (outliner-file/tree->file-content {:init-level 1})))
+
 (defn copy-block!
 (defn copy-block!
   [block-id]
   [block-id]
   (when-let [block (db/pull [:block/uuid block-id])]
   (when-let [block (db/pull [:block/uuid block-id])]
     (let [content (:block/content block)]
     (let [content (:block/content block)]
-      (common-handler/copy-to-clipboard-without-id-property! content))))
+      (common-handler/copy-to-clipboard-without-id-property! (:block/format block) content))))
 
 
 (defn copy-block-as-json!
 (defn copy-block-as-json!
   [block-id]
   [block-id]
@@ -60,22 +93,21 @@
 
 
 (defn download-file!
 (defn download-file!
   [file-path]
   [file-path]
-  (when-let [repo (state/get-current-repo)]
-    (when-let [content (db/get-file repo file-path)]
-      (let [data (js/Blob. ["\ufeff" (array content)] ; prepend BOM
-                           (clj->js {:type "text/plain;charset=utf-8,"}))]
-        (let [anchor (gdom/getElement "download")
-              url (js/window.URL.createObjectURL data)]
-          (.setAttribute anchor "href" url)
-          (.setAttribute anchor "download" file-path)
-          (.click anchor))))))
+  (when-let [content (get-file-content file-path)]
+    (let [data (js/Blob. ["\ufeff" (array content)] ; prepend BOM
+                         (clj->js {:type "text/plain;charset=utf-8,"}))]
+      (let [anchor (gdom/getElement "download")
+            url (js/window.URL.createObjectURL data)]
+        (.setAttribute anchor "href" url)
+        (.setAttribute anchor "download" file-path)
+        (.click anchor)))))
 
 
 (defn export-repo-as-html!
 (defn export-repo-as-html!
   [repo]
   [repo]
   (when-let [db (db/get-conn repo)]
   (when-let [db (db/get-conn repo)]
-    (let [db           (if (state/all-pages-public?)
-                         (db/clean-export! db)
-                         (db/filter-only-public-pages-and-blocks db))
+    (let [[db asset-filenames]           (if (state/all-pages-public?)
+                                           (db/clean-export! db)
+                                           (db/filter-only-public-pages-and-blocks db))
           db-str       (db/db->string db)
           db-str       (db/db->string db)
           state        (select-keys @state/state
           state        (select-keys @state/state
                                     [:ui/theme :ui/cycle-collapse
                                     [:ui/theme :ui/cycle-collapse
@@ -88,39 +120,50 @@
           html-str     (str "data:text/html;charset=UTF-8,"
           html-str     (str "data:text/html;charset=UTF-8,"
                             (js/encodeURIComponent raw-html-str))]
                             (js/encodeURIComponent raw-html-str))]
       (if (util/electron?)
       (if (util/electron?)
-        (js/window.apis.exportPublishAssets raw-html-str (config/get-custom-css-path))
+        (js/window.apis.exportPublishAssets raw-html-str (config/get-custom-css-path) (config/get-repo-dir repo) (clj->js asset-filenames))
+
         (when-let [anchor (gdom/getElement "download-as-html")]
         (when-let [anchor (gdom/getElement "download-as-html")]
           (.setAttribute anchor "href" html-str)
           (.setAttribute anchor "href" html-str)
           (.setAttribute anchor "download" "index.html")
           (.setAttribute anchor "download" "index.html")
           (.click anchor))))))
           (.click anchor))))))
 
 
+(defn- get-file-contents
+  ([repo]
+   (get-file-contents repo {:init-level 1}))
+  ([repo file-opts]
+   (let [conn (db/get-conn repo)]
+     (->> (d/q '[:find ?n ?fp
+                 :where
+                 [?e :block/file ?f]
+                 [?f :file/path ?fp]
+                 [?e :block/name ?n]] conn)
+          (mapv (fn [[page-name file-path]]
+                  [file-path
+                   (outliner-file/tree->file-content
+                    (outliner-tree/blocks->vec-tree
+                     (db/get-page-blocks-no-cache page-name) page-name)
+                    file-opts)]))))))
+
 (defn export-repo-as-zip!
 (defn export-repo-as-zip!
   [repo]
   [repo]
-  (let [files (db/get-file-contents repo)
+  (let [files (get-file-contents repo)
         [owner repo-name] (util/get-git-owner-and-repo repo)
         [owner repo-name] (util/get-git-owner-and-repo repo)
         repo-name (str owner "-" repo-name)]
         repo-name (str owner "-" repo-name)]
     (when (seq files)
     (when (seq files)
-      (p/let [zipfile (zip/make-zip repo-name files)]
+      (p/let [zipfile (zip/make-zip repo-name files repo)]
         (when-let [anchor (gdom/getElement "download-as-zip")]
         (when-let [anchor (gdom/getElement "download-as-zip")]
           (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
           (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
           (.setAttribute anchor "download" (.-name zipfile))
           (.setAttribute anchor "download" (.-name zipfile))
           (.click anchor))))))
           (.click anchor))))))
 
 
-(defn- get-file-contents-with-suffix
+(defn- get-md-file-contents
   [repo]
   [repo]
   (let [conn (db/get-conn repo)]
   (let [conn (db/get-conn repo)]
-    (->>
-     (filterv (fn [[path _]]
-                (or (string/ends-with? path ".md")))
-              (db/get-file-contents repo))
-     (mapv (fn [[path content]] {:path path :content content
-                                 :names (d/q '[:find [?n ?n2]
-                                               :in $ ?p
-                                               :where [?e :file/path ?p]
-                                               [?e2 :page/file ?e]
-                                               [?e2 :page/name ?n]
-                                               [?e2 :page/original-name ?n2]] conn path)
-                                 :format (f/get-format path)})))))
+    (filter (fn [[path _]]
+              (let [path (string/lower-case path)]
+                (or (string/ends-with? path ".md")
+                    (string/ends-with? path ".markdown"))))
+            (get-file-contents repo {:init-level 1 :heading-to-list? true}))))
 
 
 (defn- get-embed-and-refs-blocks-pages-aux []
 (defn- get-embed-and-refs-blocks-pages-aux []
   (let [mem (atom {})]
   (let [mem (atom {})]
@@ -130,12 +173,12 @@
                     (let [[ref-blocks ref-pages]
                     (let [[ref-blocks ref-pages]
                           (->> (if is-block?
                           (->> (if is-block?
                                  [page-or-block]
                                  [page-or-block]
-                                 (db/get-page-blocks
-                                  repo page-or-block {:use-cache? false
-                                                      :pull-keys '[:block/ref-pages :block/ref-blocks]}))
-                               (filterv #(or (:block/ref-blocks %) (:block/ref-pages %)))
-                               (mapv (fn [b] [(:block/ref-blocks b), (:block/ref-pages b)]))
-                               (apply mapv vector)
+                                 (db/get-page-blocks-no-cache
+                                  repo page-or-block {:pull-keys '[:block/refs]}))
+                               (filterv :block/refs)
+                               (mapcat :block/refs)
+                               (group-by #(boolean (:block/page (db/entity (:db/id %)))))
+                               ((fn [g] [(get g true []) (get g false [])]))
                                (mapv #(vec (distinct (flatten (remove nil? %))))))
                                (mapv #(vec (distinct (flatten (remove nil? %))))))
                           ref-block-ids
                           ref-block-ids
                           (->> ref-blocks
                           (->> ref-blocks
@@ -152,6 +195,7 @@
                           ref-pages
                           ref-pages
                           (->> ref-page-ids
                           (->> ref-page-ids
                                (db/pull-many repo '[*])
                                (db/pull-many repo '[*])
+                               (filterv :block/name)
                                (flatten))
                                (flatten))
                           [next-ref-blocks1 next-ref-pages1]
                           [next-ref-blocks1 next-ref-pages1]
                           (->> ref-blocks
                           (->> ref-blocks
@@ -159,7 +203,7 @@
                                (apply mapv vector))
                                (apply mapv vector))
                           [next-ref-blocks2 next-ref-pages2]
                           [next-ref-blocks2 next-ref-pages2]
                           (->> ref-pages
                           (->> ref-pages
-                               (mapv #(f repo (:page/name %) false exclude-blocks (set (concat ref-page-ids exclude-pages))))
+                               (mapv #(f repo (:block/name %) false exclude-blocks (set (concat ref-page-ids exclude-pages))))
                                (apply mapv vector))
                                (apply mapv vector))
                           result
                           result
                           [(->> (concat ref-block-ids next-ref-blocks1 next-ref-blocks2)
                           [(->> (concat ref-block-ids next-ref-blocks1 next-ref-blocks2)
@@ -184,26 +228,20 @@
              (d/q '[:find ?n ?n2 (pull ?p [:file/path])
              (d/q '[:find ?n ?n2 (pull ?p [:file/path])
                     :in $ [?e ...]
                     :in $ [?e ...]
                     :where
                     :where
-                    [?e :page/file ?p]
-                    [?e :page/name ?n]
-                    [?e :page/original-name ?n2]] (db/get-conn repo))
+                    [?e :block/file ?p]
+                    [?e :block/name ?n]
+                    [?e :block/original-name ?n2]] (db/get-conn repo))
              (mapv (fn [[name origin-name file-path]]
              (mapv (fn [[name origin-name file-path]]
                      (if (= name origin-name)
                      (if (= name origin-name)
                        [[name file-path]]
                        [[name file-path]]
                        [[name file-path] [origin-name file-path]])))
                        [[name file-path] [origin-name file-path]])))
              (apply concat)
              (apply concat)
-             (mapv (fn [[page-name file-path]] [page-name (:file/path file-path)]))
-             (d/q '[:find ?n ?c
-                    :in $ [[?n ?p] ...]
-                    :where
-                    [?e :file/path ?p]
-                    [?e :file/content ?c]] @(db/get-files-conn repo)))
+             (mapv (fn [[page-name file-path]]
+                     [page-name (get-page-content page-name)])))
         embed-blocks
         embed-blocks
         (mapv (fn [b] [(str (:block/uuid b))
         (mapv (fn [b] [(str (:block/uuid b))
-                       [(apply str
-                               (mapv #(:block/content %)
-                                     (db/get-block-and-children repo (:block/uuid b))))
-                        (:block/title b)]])
+                       [(get-blocks-contents repo (:block/uuid b))
+                        (:block/content b)]])
               blocks)]
               blocks)]
     {:embed_blocks embed-blocks
     {:embed_blocks embed-blocks
      :embed_pages pages-name-and-content}))
      :embed_pages pages-name-and-content}))
@@ -214,10 +252,10 @@
         (->>
         (->>
          (d/q '[:find ?pn ?pon ?bt ?bc ?bid ?e ?rb
          (d/q '[:find ?pn ?pon ?bt ?bc ?bid ?e ?rb
                 :where
                 :where
-                [?e :block/ref-blocks ?rb]
+                [?e :block/refs ?rb]
                 [?e :block/page ?p]
                 [?e :block/page ?p]
-                [?p :page/name ?pn]
-                [?p :page/original-name ?pon]
+                [?p :block/name ?pn]
+                [?p :block/original-name ?pon]
                 [?rb :block/title ?bt]
                 [?rb :block/title ?bt]
                 [?rb :block/content ?bc]
                 [?rb :block/content ?bc]
                 [?rb :block/uuid ?bid]] (db/get-conn repo)))
                 [?rb :block/uuid ?bid]] (db/get-conn repo)))
@@ -240,19 +278,19 @@
         page-refs
         page-refs
         (->> (d/q '[:find ?pn ?pon ?rpn ?rpon ?fp ?e
         (->> (d/q '[:find ?pn ?pon ?rpn ?rpon ?fp ?e
                     :where
                     :where
-                    [?e :block/ref-pages ?rp]
+                    [?e :block/refs ?rp]
                     [?e :block/page ?p]
                     [?e :block/page ?p]
-                    [?p :page/name ?pn]
-                    [?p :page/original-name ?pon]
-                    [?rp :page/name ?rpn]
-                    [?rp :page/original-name ?rpon]
-                    [?rp :page/file ?pf]
+                    [?p :block/name ?pn]
+                    [?p :block/original-name ?pon]
+                    [?rp :block/name ?rpn]
+                    [?rp :block/original-name ?rpon]
+                    [?rp :block/file ?pf]
                     [?pf :file/path ?fp]] (db/get-conn repo))
                     [?pf :file/path ?fp]] (db/get-conn repo))
              (d/q '[:find ?pn ?pon ?rpn ?rpon ?fc ?be
              (d/q '[:find ?pn ?pon ?rpn ?rpon ?fc ?be
                     :in $ [[?pn ?pon ?rpn ?rpon ?fp ?be] ...]
                     :in $ [[?pn ?pon ?rpn ?rpon ?fp ?be] ...]
                     :where
                     :where
                     [?e :file/path ?fp]
                     [?e :file/path ?fp]
-                    [?e :file/content ?fc]] @(db/get-files-conn repo)))
+                    [?e :file/content ?fc]] (db/get-conn repo)))
         page-page-refs
         page-page-refs
         (->>
         (->>
          page-refs
          page-refs
@@ -347,12 +385,10 @@
   (let [[block-refs page-refs]
   (let [[block-refs page-refs]
         (get-page&block-refs-aux repo page false page&block-refs #{} #{})]
         (get-page&block-refs-aux repo page false page&block-refs #{} #{})]
     {:embed_blocks
     {:embed_blocks
-     (mapv (fn [[title _content uuid id]]
+     (mapv (fn [[_title content uuid id]]
              [(str uuid)
              [(str uuid)
-              [(apply str
-                      (mapv #(:block/content %)
-                            (db/get-block-and-children repo uuid)))
-               title]])
+              [(get-blocks-contents repo uuid)
+               content]])
            block-refs)
            block-refs)
      :embed_pages (vec page-refs)}))
      :embed_pages (vec page-refs)}))
 
 
@@ -372,6 +408,31 @@
                                              (clj->js (f (first names)))))])))
                                              (clj->js (f (first names)))))])))
          (remove nil?))))
          (remove nil?))))
 
 
+(defn- convert-md-files-unordered-list-or-heading
+  [repo files heading-to-list?]
+  (->> files
+       (mapv (fn [{:keys [path content names format]}]
+               (when (first names)
+                 [path (fp/exportMarkdown f/mldoc-record content
+                                          (f/get-default-config format heading-to-list? true)
+                                          nil)])))
+       (remove nil?)))
+
+(defn- get-file-contents-with-suffix
+  [repo]
+  (let [conn (db/get-conn repo)
+        md-files (get-md-file-contents repo)]
+    (->>
+     md-files
+     (map (fn [[path content]] {:path path :content content
+                                :names (d/q '[:find [?n ?n2]
+                                              :in $ ?p
+                                              :where [?e :file/path ?p]
+                                              [?e2 :block/file ?e]
+                                              [?e2 :block/name ?n]
+                                              [?e2 :block/original-name ?n2]] conn path)
+                                :format (f/get-format path)})))))
+
 (defn export-repo-as-markdown!
 (defn export-repo-as-markdown!
   [repo]
   [repo]
   (when-let [repo (state/get-current-repo)]
   (when-let [repo (state/get-current-repo)]
@@ -380,7 +441,7 @@
             files
             files
             (export-files-as-markdown repo files heading-to-list?)
             (export-files-as-markdown repo files heading-to-list?)
             zip-file-name (str repo "_markdown_" (quot (util/time-ms) 1000))]
             zip-file-name (str repo "_markdown_" (quot (util/time-ms) 1000))]
-        (p/let [zipfile (zip/make-zip zip-file-name files)]
+        (p/let [zipfile (zip/make-zip zip-file-name files repo)]
           (when-let [anchor (gdom/getElement "export-as-markdown")]
           (when-let [anchor (gdom/getElement "export-as-markdown")]
             (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
             (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
             (.setAttribute anchor "download" (.-name zipfile))
             (.setAttribute anchor "download" (.-name zipfile))
@@ -391,7 +452,7 @@
   (when-let [repo (state/get-current-repo)]
   (when-let [repo (state/get-current-repo)]
     (when-let [file (db/get-page-file page-name)]
     (when-let [file (db/get-page-file page-name)]
       (when-let [path (:file/path file)]
       (when-let [path (:file/path file)]
-        (when-let [content (db/get-file path)]
+        (when-let [content (get-page-content page-name)]
           (let [names [page-name]
           (let [names [page-name]
                 format (f/get-format path)
                 format (f/get-format path)
                 files [{:path path :content content :names names :format format}]]
                 files [{:path path :content content :names names :format format}]]
@@ -404,3 +465,36 @@
                   (.setAttribute anchor "href" url)
                   (.setAttribute anchor "href" url)
                   (.setAttribute anchor "download" path)
                   (.setAttribute anchor "download" path)
                   (.click anchor))))))))))
                   (.click anchor))))))))))
+
+(defn convert-page-markdown-unordered-list-or-heading!
+  [page-name]
+  (when-let [repo (state/get-current-repo)]
+    (when-let [file (db/get-page-file page-name)]
+      (when-let [path (:file/path file)]
+        (when-let [content (get-page-content page-name)]
+          (let [names [page-name]
+                format (f/get-format path)
+                files [{:path path :content content :names names :format format}]]
+            (let [files (convert-md-files-unordered-list-or-heading repo files (state/export-heading-to-list?))]
+              (let [data (js/Blob. [(second (first files))]
+                                   (clj->js {:type "text/plain;charset=utf-8,"}))]
+                (let [anchor (gdom/getElement "convert-markdown-to-unordered-list-or-heading")
+                      url (js/window.URL.createObjectURL data)]
+                  (.setAttribute anchor "href" url)
+                  (.setAttribute anchor "download" path)
+                  (.click anchor))))))))))
+
+(defn convert-repo-markdown-v2!
+  [repo]
+  (when repo
+    (when-let [files (get-md-file-contents repo)]
+      (let [zip-file-name (-> (string/replace repo config/local-db-prefix "")
+                              (string/replace #"^/+" ""))
+            zip-file-name (str zip-file-name
+                               "_markdown_"
+                               (quot (util/time-ms) 1000))]
+        (p/let [zipfile (zip/make-zip zip-file-name files repo)]
+          (when-let [anchor (gdom/getElement "convert-markdown-to-unordered-list-or-heading")]
+            (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
+            (.setAttribute anchor "download" (.-name zipfile))
+            (.click anchor)))))))

+ 6 - 7
src/main/frontend/handler/external.cljs

@@ -7,12 +7,11 @@
             [frontend.date :as date]
             [frontend.date :as date]
             [frontend.config :as config]
             [frontend.config :as config]
             [clojure.string :as string]
             [clojure.string :as string]
-            [promesa.core :as p]
             [frontend.db :as db]))
             [frontend.db :as db]))
 
 
 (defonce debug-files (atom nil))
 (defonce debug-files (atom nil))
 (defn index-files!
 (defn index-files!
-  [repo files git-add-cb]
+  [repo files finish-handler]
   (let [titles (->> files
   (let [titles (->> files
                     (map :title)
                     (map :title)
                     (map :text)
                     (map :text)
@@ -22,7 +21,7 @@
                            journal? (date/valid-journal-title? title)]
                            journal? (date/valid-journal-title? title)]
                        (when-let [text (:text file)]
                        (when-let [text (:text file)]
                          (let [path (str (if journal?
                          (let [path (str (if journal?
-                                           config/default-journals-directory
+                                           (config/get-journals-directory)
                                            (config/get-pages-directory))
                                            (config/get-pages-directory))
                                          "/"
                                          "/"
                                          (if journal?
                                          (if journal?
@@ -39,14 +38,14 @@
       (file-handler/alter-files repo files {:add-history? false
       (file-handler/alter-files repo files {:add-history? false
                                             :update-db? false
                                             :update-db? false
                                             :update-status? false
                                             :update-status? false
-                                            :git-add-cb git-add-cb}))
+                                            :finish-handler finish-handler}))
     (let [journal-pages-tx (let [titles (filter date/valid-journal-title? titles)]
     (let [journal-pages-tx (let [titles (filter date/valid-journal-title? titles)]
                              (map
                              (map
                               (fn [title]
                               (fn [title]
                                 (let [page-name (string/lower-case title)]
                                 (let [page-name (string/lower-case title)]
-                                  {:page/name page-name
-                                   :page/journal? true
-                                   :page/journal-day (date/journal-title->int title)}))
+                                  {:block/name page-name
+                                   :block/journal? true
+                                   :block/journal-day (date/journal-title->int title)}))
                               titles))]
                               titles))]
       (when (seq journal-pages-tx)
       (when (seq journal-pages-tx)
         (db/transact! repo journal-pages-tx)))))
         (db/transact! repo journal-pages-tx)))))

+ 160 - 116
src/main/frontend/handler/extract.cljs

@@ -6,12 +6,18 @@
             [clojure.set :as set]
             [clojure.set :as set]
             [frontend.utf8 :as utf8]
             [frontend.utf8 :as utf8]
             [frontend.date :as date]
             [frontend.date :as date]
+            [frontend.text :as text]
+            [frontend.util.property :as property]
             [clojure.string :as string]
             [clojure.string :as string]
             [frontend.format.mldoc :as mldoc]
             [frontend.format.mldoc :as mldoc]
             [frontend.format.block :as block]
             [frontend.format.block :as block]
             [frontend.format :as format]
             [frontend.format :as format]
             [cljs-time.core :as t]
             [cljs-time.core :as t]
-            [cljs-time.coerce :as tc]))
+            [cljs-time.coerce :as tc]
+            [medley.core :as medley]
+            [clojure.walk :as walk]
+            [frontend.state :as state]
+            [frontend.config :as config]))
 
 
 (defn- extract-page-list
 (defn- extract-page-list
   [content]
   [content]
@@ -22,133 +28,163 @@
          (map string/lower-case)
          (map string/lower-case)
          (distinct))))
          (distinct))))
 
 
+
+
+(defn get-page-name
+  [file ast]
+  ;; headline
+  (let [ast (map first ast)]
+    (if (string/includes? file "pages/contents.")
+      "Contents"
+      (let [first-block (last (first (filter block/heading-block? ast)))
+            property-name (when (and (contains? #{"Properties" "Property_Drawer"} (ffirst ast))
+                                     (not (string/blank? (:title (last (first ast))))))
+                            (:title (last (first ast))))
+            first-block-name (let [title (last (first (:title first-block)))]
+                               (and first-block
+                                    (string? title)
+                                    title))
+            file-name (when-let [file-name (last (string/split file #"/"))]
+                        (-> (first (util/split-last "." file-name))
+                            (string/replace "." "/")))]
+        (or property-name
+            (if (= (state/page-name-order) "heading")
+              (or first-block-name file-name)
+              (or file-name first-block-name)))))))
+
 ;; TODO: performance improvement
 ;; TODO: performance improvement
 (defn- extract-pages-and-blocks
 (defn- extract-pages-and-blocks
-  [repo-url format ast properties file content utf8-content journal? pages-fn]
+  [repo-url format ast properties file content utf8-content journal?]
   (try
   (try
     (let [now (tc/to-long (t/now))
     (let [now (tc/to-long (t/now))
-          blocks (block/extract-blocks ast (utf8/length utf8-content) utf8-content)
-          pages (pages-fn blocks ast)
+          page (get-page-name file ast)
+          [page page-name journal-day] (block/convert-page-if-journal page)
+          blocks (->> (block/extract-blocks ast content false format)
+                      (block/with-parent-and-left [:block/name (string/lower-case page)]))
           ref-pages (atom #{})
           ref-pages (atom #{})
           ref-tags (atom #{})
           ref-tags (atom #{})
-          blocks (doall
-                  (mapcat
-                   (fn [[page blocks]]
-                     (if page
-                       (map (fn [block]
-                              (let [block-ref-pages (seq (:block/ref-pages block))
-                                    block-path-ref-pages (seq (:block/path-ref-pages block))]
-                                (when block-ref-pages
-                                  (swap! ref-pages set/union (set block-ref-pages)))
-                                (-> block
-                                    (dissoc :ref-pages)
-                                    (assoc :block/content (db/get-block-content utf8-content block)
-                                           :block/file [:file/path file]
-                                           :block/format format
-                                           :block/page [:page/name (string/lower-case page)]
-                                           :block/ref-pages (mapv
-                                                             (fn [page]
-                                                               (block/page-with-journal page))
-                                                             block-ref-pages)
-                                           :block/path-ref-pages block-path-ref-pages))))
-                            blocks)))
-                   (remove nil? pages)))
-          pages (doall
-                 (map
-                  (fn [page]
-                    (let [page-file? (= page (string/lower-case file))
-                          aliases (and (:alias properties)
-                                       (seq (remove #(= page %)
-                                                    (:alias properties))))
-                          journal-date-long (if journal?
-                                              (date/journal-title->long (string/capitalize page)))
-                          page-list (when-let [list-content (:list properties)]
-                                      (extract-page-list list-content))]
-                      (cond->
-                        (util/remove-nils
-                         {:page/name (string/lower-case page)
-                          :page/original-name page
-                          :page/file [:file/path file]
-                          :page/journal? journal?
-                          :page/journal-day (if journal?
-                                              (date/journal-title->int page)
-                                              0)})
-                        (seq properties)
-                        (assoc :page/properties properties)
+          blocks (map (fn [block]
+                        (let [block-ref-pages (seq (:block/refs block))
+                              block-path-ref-pages (seq (:block/path-refs block))]
+                          (when block-ref-pages
+                            (swap! ref-pages set/union (set block-ref-pages)))
+                          (-> block
+                              (dissoc :ref-pages)
+                              (assoc :block/file [:file/path file]
+                                     :block/format format
+                                     :block/page [:block/name (string/lower-case page)]
+                                     :block/refs block-ref-pages
+                                     :block/path-refs block-path-ref-pages))))
+                   blocks)
+          page-entity (let [page-file? (= page (string/lower-case file))
+                            aliases (and (:alias properties)
+                                         (seq (remove #(= page %)
+                                                      (:alias properties))))
+                            page-list (when-let [list-content (:list properties)]
+                                        (extract-page-list list-content))]
+                        (cond->
+                          (util/remove-nils
+                           (assoc
+                            (block/page-name->map page false)
+                            :block/file [:file/path file]))
+                          (seq properties)
+                          (assoc :block/properties properties)
 
 
-                        aliases
-                        (assoc :page/alias
-                               (map
-                                 (fn [alias]
-                                   (let [page-name (string/lower-case alias)
-                                         aliases (distinct
-                                                  (conj
-                                                   (remove #{alias} aliases)
-                                                   page))
-                                         aliases (if (seq aliases)
-                                                   (map
-                                                     (fn [alias]
-                                                       {:page/name alias})
-                                                     aliases))]
-                                     (if (seq aliases)
-                                       {:page/name page-name
-                                        :page/alias aliases}
-                                       {:page/name page-name})))
-                                 aliases))
+                          aliases
+                          (assoc :block/alias
+                                 (map
+                                   (fn [alias]
+                                     (let [page-name (string/lower-case alias)
+                                           aliases (distinct
+                                                    (conj
+                                                     (remove #{alias} aliases)
+                                                     page))
+                                           aliases (if (seq aliases)
+                                                     (map
+                                                       (fn [alias]
+                                                         {:block/name alias})
+                                                       aliases))]
+                                       (if (seq aliases)
+                                         {:block/name page-name
+                                          :block/alias aliases}
+                                         {:block/name page-name})))
+                                   aliases))
 
 
-                        (:tags properties)
-                        (assoc :page/tags (let [tags (->> (:tags properties)
-                                                          (remove string/blank?))]
-                                            (swap! ref-tags set/union (set tags))
-                                            (map (fn [tag] {:page/name (string/lower-case tag)
-                                                            :page/original-name tag})
-                                              tags))))))
-                  (->> (map first pages)
-                       (remove string/blank?))))
-          pages (concat
-                 pages
-                 (map
-                  (fn [page]
-                    {:page/original-name page
-                     :page/name (string/lower-case page)})
-                  @ref-tags)
-                 (map
-                  (fn [page]
-                    {:page/original-name page
-                     :page/name (string/lower-case page)})
-                  @ref-pages))
+                          (:tags properties)
+                          (assoc :block/tags (let [tags (->> (:tags properties)
+                                                             (remove string/blank?))]
+                                               (swap! ref-tags set/union (set tags))
+                                               (map (fn [tag] {:block/name (string/lower-case tag)
+                                                               :block/original-name tag})
+                                                 tags)))))
+          pages (->> (concat
+                      [page-entity]
+                      @ref-pages
+                      (map
+                        (fn [page]
+                          {:block/original-name page
+                           :block/name (string/lower-case page)})
+                        @ref-tags))
+                     ;; remove block references
+                     (remove vector?))
+          pages (util/distinct-by :block/name pages)
           block-ids (mapv (fn [block]
           block-ids (mapv (fn [block]
                             {:block/uuid (:block/uuid block)})
                             {:block/uuid (:block/uuid block)})
-                          (remove nil? blocks))]
-      [(remove nil? pages)
+                          (remove nil? blocks))
+          pages (remove nil? pages)
+          pages (map (fn [page] (assoc page :block/uuid (db/new-block-id))) pages)]
+      [pages
        block-ids
        block-ids
        (remove nil? blocks)])
        (remove nil? blocks)])
     (catch js/Error e
     (catch js/Error e
-      (js/console.log e))))
+      (log/error :exception e))))
 
 
 (defn extract-blocks-pages
 (defn extract-blocks-pages
-  [repo-url file content utf8-content]
-  (if (string/blank? content)
-    []
-    (let [journal? (util/journal? file)
-          format (format/get-format file)
-          ast (mldoc/->edn content
-                           (mldoc/default-config format))
-          first-block (first ast)
-          properties (let [properties (and (seq first-block)
-                                           (= "Properties" (ffirst first-block))
-                                           (last (first first-block)))]
-                       (if (and properties (seq properties))
-                         properties))]
-      (extract-pages-and-blocks
-       repo-url
-       format ast properties
-       file content utf8-content journal?
-       (fn [blocks ast]
-         [[(db/get-page-name file ast) blocks]])))))
+  ([repo-url file content]
+   (extract-blocks-pages repo-url file content (utf8/encode content)))
+  ([repo-url file content utf8-content]
+   (if (string/blank? content)
+     []
+     (let [journal? (config/journal? file)
+           format (format/get-format file)
+           ast (mldoc/->edn content
+                            (mldoc/default-config format))
+           first-block (ffirst ast)
+           properties (let [properties (and (property/properties-ast? first-block)
+                                            (->> (last first-block)
+                                                 (into {})
+                                                 (walk/keywordize-keys)))]
+                        (when (and properties (seq properties))
+                          (if (:filters properties)
+                            (update properties :filters
+                                    (fn [v]
+                                      (string/replace (or v "") "\\" "")))
+                            properties)))]
+       (extract-pages-and-blocks
+        repo-url
+        format ast properties
+        file content utf8-content journal?)))))
+
+(defn with-block-uuid
+  [pages]
+  (->> (util/distinct-by :block/name pages)
+       (map (fn [page]
+              (if (:block/uuid page)
+                page
+                (assoc page :block/uuid (db/new-block-id)))))))
+
+(defn with-ref-pages
+  [pages blocks]
+  (let [ref-pages (->> (mapcat :block/refs blocks)
+                       (filter :block/name))]
+    (->> (concat pages ref-pages)
+         (group-by :block/name)
+         vals
+         (map (partial apply merge))
+         (with-block-uuid))))
 
 
 (defn extract-all-blocks-pages
 (defn extract-all-blocks-pages
-  [repo-url files]
+  [repo-url files metadata]
   (when (seq files)
   (when (seq files)
     (let [result (->> files
     (let [result (->> files
                       (map
                       (map
@@ -160,11 +196,19 @@
                       (remove empty?))]
                       (remove empty?))]
       (when (seq result)
       (when (seq result)
         (let [[pages block-ids blocks] (apply map concat result)
         (let [[pages block-ids blocks] (apply map concat result)
-              block-ids-set (set (map (fn [{:block/keys [uuid]}] [:block/uuid uuid]) block-ids))
+              pages (with-ref-pages pages blocks)
+              blocks (map (fn [block]
+                            (let [id (:block/uuid block)
+                                  properties (get-in metadata [:block/properties id])]
+                              (update block :block/properties merge properties)))
+                          blocks)
               ;; To prevent "unique constraint" on datascript
               ;; To prevent "unique constraint" on datascript
-              pages-index (map #(select-keys % [:page/name]) pages)
+              pages-index (map #(select-keys % [:block/name]) pages)
+              block-ids-set (set (map (fn [{:block/keys [uuid]}] [:block/uuid uuid]) block-ids))
               blocks (map (fn [b]
               blocks (map (fn [b]
-                            (-> b
-                                (update :block/ref-blocks #(set/intersection (set %) block-ids-set))
-                                (update :block/embed-blocks #(set/intersection (set %) block-ids-set)))) blocks)]
+                            (update b :block/refs
+                                    (fn [refs]
+                                      (set/union
+                                       (filter :block/name refs)
+                                       (set/intersection (set refs) block-ids-set))))) blocks)]
           (apply concat [pages-index pages block-ids blocks]))))))
           (apply concat [pages-index pages block-ids blocks]))))))

+ 41 - 52
src/main/frontend/handler/file.cljs

@@ -8,7 +8,6 @@
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.git :as git]
             [frontend.git :as git]
             [frontend.handler.common :as common-handler]
             [frontend.handler.common :as common-handler]
-            [frontend.handler.git :as git-handler]
             [frontend.handler.extract :as extract-handler]
             [frontend.handler.extract :as extract-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
@@ -16,15 +15,13 @@
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.format :as format]
             [frontend.format :as format]
             [clojure.string :as string]
             [clojure.string :as string]
-            [frontend.history :as history]
-            [frontend.handler.project :as project-handler]
             [lambdaisland.glogi :as log]
             [lambdaisland.glogi :as log]
             [clojure.core.async :as async]
             [clojure.core.async :as async]
             [cljs.core.async.interop :refer-macros [<p!]]
             [cljs.core.async.interop :refer-macros [<p!]]
-            [goog.object :as gobj]
             [cljs-time.core :as t]
             [cljs-time.core :as t]
             [cljs-time.coerce :as tc]
             [cljs-time.coerce :as tc]
             [frontend.utf8 :as utf8]
             [frontend.utf8 :as utf8]
+            [borkdude.rewrite-edn :as rewrite]
             ["/frontend/utils" :as utils]))
             ["/frontend/utils" :as utils]))
 
 
 ;; TODO: extract all git ops using a channel
 ;; TODO: extract all git ops using a channel
@@ -84,14 +81,7 @@
    (let [config-content (if config-content config-content
    (let [config-content (if config-content config-content
                             (common-handler/get-config repo-url))]
                             (common-handler/get-config repo-url))]
      (when config-content
      (when config-content
-       (let [old-project (:project (state/get-config))
-             new-config (common-handler/reset-config! repo-url config-content)]
-         (when (and (not (config/local-db? repo-url))
-                    project-changed-check?)
-           (let [new-project (:project new-config)
-                 project-name (:name old-project)]
-             (when-not (= new-project old-project)
-               (project-handler/sync-project-settings! project-name new-project)))))))))
+       (common-handler/reset-config! repo-url config-content)))))
 
 
 (defn load-files
 (defn load-files
   [repo-url]
   [repo-url]
@@ -152,7 +142,8 @@
           file-content [{:file/path file}]
           file-content [{:file/path file}]
           tx (if (contains? config/mldoc-support-formats format)
           tx (if (contains? config/mldoc-support-formats format)
                (let [delete-blocks (db/delete-file-blocks! repo-url file)
                (let [delete-blocks (db/delete-file-blocks! repo-url file)
-                     [pages block-ids blocks] (extract-handler/extract-blocks-pages repo-url file content utf8-content)]
+                     [pages block-ids blocks] (extract-handler/extract-blocks-pages repo-url file content utf8-content)
+                     pages (extract-handler/with-ref-pages pages blocks)]
                  (concat file-content delete-blocks pages block-ids blocks))
                  (concat file-content delete-blocks pages block-ids blocks))
                file-content)
                file-content)
           tx (concat tx [(let [t (tc/to-long (t/now))]
           tx (concat tx [(let [t (tc/to-long (t/now))]
@@ -179,21 +170,20 @@
       (do
       (do
         (when-let [page-id (db/get-file-page-id path)]
         (when-let [page-id (db/get-file-page-id path)]
           (db/transact! repo
           (db/transact! repo
-            [[:db/retract page-id :page/alias]
-             [:db/retract page-id :page/tags]]))
+            [[:db/retract page-id :block/alias]
+             [:db/retract page-id :block/tags]]))
         (reset-file! repo path content))
         (reset-file! repo path content))
       (db/set-file-content! repo path content))
       (db/set-file-content! repo path content))
     (util/p-handle (write-file!)
     (util/p-handle (write-file!)
                    (fn [_]
                    (fn [_]
-                     (when-not from-disk?
-                       (git-handler/git-add repo path update-status?))
                      (when (= path (config/get-config-path repo))
                      (when (= path (config/get-config-path repo))
                        (restore-config! repo true))
                        (restore-config! repo true))
                      (when (= path (config/get-custom-css-path repo))
                      (when (= path (config/get-custom-css-path repo))
                        (ui-handler/add-style-if-exists!))
                        (ui-handler/add-style-if-exists!))
                      (when re-render-root? (ui-handler/re-render-root!))
                      (when re-render-root? (ui-handler/re-render-root!))
-                     (when (and add-history? original-content)
-                       (history/add-history! repo [[path original-content content]])))
+                     ;; (when (and add-history? original-content)
+                     ;;   (history/add-history! repo [[path original-content content]]))
+                     )
                    (fn [error]
                    (fn [error]
                      (println "Write file failed, path: " path ", content: " content)
                      (println "Write file failed, path: " path ", content: " content)
                      (log/error :write/failed error)))))
                      (log/error :write/failed error)))))
@@ -216,7 +206,7 @@
                                    :path-params {:path path}}))))))
                                    :path-params {:path path}}))))))
 
 
 (defn alter-files
 (defn alter-files
-  [repo files {:keys [add-history? update-status? git-add-cb reset? update-db? chan chan-callback resolved-handler]
+  [repo files {:keys [add-history? update-status? finish-handler reset? update-db? chan chan-callback resolved-handler]
                :or {add-history? true
                :or {add-history? true
                     update-status? true
                     update-status? true
                     reset? false
                     reset? false
@@ -240,7 +230,7 @@
           (chan-callback))))))
           (chan-callback))))))
 
 
 (defn alter-files-handler!
 (defn alter-files-handler!
-  [repo files {:keys [add-history? update-status? git-add-cb reset? chan]
+  [repo files {:keys [add-history? update-status? finish-handler reset? chan]
                :or {add-history? true
                :or {add-history? true
                     update-status? true
                     update-status? true
                     reset? false}} file->content]
                     reset? false}} file->content]
@@ -253,29 +243,19 @@
                                         (log/error :write-file/failed {:path path
                                         (log/error :write-file/failed {:path path
                                                                        :content content
                                                                        :content content
                                                                        :error error}))))))
                                                                        :error error}))))))
-        git-add-f (fn []
-                    (let [add-helper
-                          (fn []
-                            (map
-                              (fn [[path content]]
-                                (git-handler/git-add repo path update-status?))
-                              files))]
-                      (-> (p/all (add-helper))
-                          (p/then (fn [_]
-                                    (when git-add-cb
-                                      (git-add-cb))))
-                          (p/catch (fn [error]
-                                     (println "Git add failed:")
-                                     (js/console.error error)))))
-                    (ui-handler/re-render-file!)
-                    (when add-history?
-                      (let [files-tx (mapv (fn [[path content]]
-                                             (let [original-content (get file->content path)]
-                                               [path original-content content])) files)]
-                        (history/add-history! repo files-tx))))]
+        finish-handler (fn []
+                         (when finish-handler
+                           (finish-handler))
+                         (ui-handler/re-render-file!)
+                         ;; (when add-history?
+                         ;;   (let [files-tx (mapv (fn [[path content]]
+                         ;;                          (let [original-content (get file->content path)]
+                         ;;                            [path original-content content])) files)]
+                         ;;     (history/add-history! repo files-tx)))
+                         )]
     (-> (p/all (map write-file-f files))
     (-> (p/all (map write-file-f files))
         (p/then (fn []
         (p/then (fn []
-                  (git-add-f)
+                  (finish-handler)
                   (when chan
                   (when chan
                     (async/put! chan true))))
                     (async/put! chan true))))
         (p/catch (fn [error]
         (p/catch (fn [error]
@@ -288,7 +268,7 @@
   (when-not (string/blank? file)
   (when-not (string/blank? file)
     (->
     (->
      (p/let [_ (or (config/local-db? repo) (git/remove-file repo file))
      (p/let [_ (or (config/local-db? repo) (git/remove-file repo file))
-             result (fs/unlink! (config/get-repo-path repo file) nil)]
+             _ (fs/unlink! (config/get-repo-path repo file) nil)]
        (when-let [file (db/entity repo [:file/path file])]
        (when-let [file (db/entity repo [:file/path file])]
          (common-handler/check-changed-files-status)
          (common-handler/check-changed-files-status)
          (let [file-id (:db/id file)
          (let [file-id (:db/id file)
@@ -302,13 +282,6 @@
      (p/catch (fn [err]
      (p/catch (fn [err]
                 (js/console.error "error: " err))))))
                 (js/console.error "error: " err))))))
 
 
-(defn re-index!
-  [file]
-  (when-let [repo (state/get-current-repo)]
-    (let [path (:file/path file)
-          content (db/get-file path)]
-      (alter-file repo path content {:re-render-root? true}))))
-
 ;; TODO: batch writes, how to deal with file history?
 ;; TODO: batch writes, how to deal with file history?
 (defn run-writes-chan!
 (defn run-writes-chan!
   []
   []
@@ -339,5 +312,21 @@
     (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" config/app-name))
     (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" config/app-name))
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
       (when-not file-exists?
       (when-not file-exists?
-        (reset-file! repo-url path default-content)
-        (git-handler/git-add repo-url path)))))
+        (reset-file! repo-url path default-content)))))
+
+(defn edn-file-set-key-value
+  [path k v ok-handler]
+  (when-let [repo (state/get-current-repo)]
+    (when-let [content (db/get-file-no-sub path)]
+      (let [result (try
+                     (rewrite/parse-string content)
+                     (catch js/Error e
+                       (println "Parsing config file failed: ")
+                       (js/console.dir e)
+                       {}))
+            ks (if (vector? k) k [k])
+            new-result (rewrite/assoc-in result ks v)]
+        (when ok-handler (ok-handler repo new-result))
+        (state/set-config! repo new-result)
+        (let [new-content (str new-result)]
+          (set-file-content! repo path new-content))))))

+ 1 - 18
src/main/frontend/handler/git.cljs

@@ -10,9 +10,7 @@
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.common :as common-handler]
             [frontend.handler.common :as common-handler]
             [frontend.config :as config]
             [frontend.config :as config]
-            [cljs-time.local :as tl]
-            [clojure.string :as string]
-            [goog.object :as gobj]))
+            [cljs-time.local :as tl]))
 
 
 (defn- set-git-status!
 (defn- set-git-status!
   [repo-url value]
   [repo-url value]
@@ -63,18 +61,3 @@
      (config/get-repo-dir repo-url)
      (config/get-repo-dir repo-url)
      name
      name
      email)))
      email)))
-
-(defn show-commit-modal!
-  [modal]
-  (fn [e]
-    (when (and
-          (string/starts-with? (state/get-current-repo) "https://")
-          (not (util/input? (gobj/get e "target")))
-          (not (gobj/get e "shiftKey"))
-          (not (gobj/get e "ctrlKey"))
-          (not (gobj/get e "altKey"))
-          (not (gobj/get e "metaKey")))
-     (when-let [repo-url (state/get-current-repo)]
-       (when-not (state/get-edit-input-id)
-         (util/stop e)
-         (state/set-modal! modal))))))

+ 17 - 12
src/main/frontend/handler/graph.cljs

@@ -52,10 +52,11 @@
 
 
 (defn- uuid-or-asset?
 (defn- uuid-or-asset?
   [id]
   [id]
-  (or (util/uuid-string? id)
-      (string/starts-with? id "../assets/")
-      (= id "..")
-      (string/starts-with? id "assets/")))
+  (let [id (str id)]
+    (or (util/uuid-string? id)
+       (string/starts-with? id "../assets/")
+       (= id "..")
+       (string/starts-with? id "assets/"))))
 
 
 (defn- remove-uuids-and-files!
 (defn- remove-uuids-and-files!
   [nodes]
   [nodes]
@@ -70,10 +71,14 @@
                                     (map :source links)
                                     (map :source links)
                                     (map :target links)]))
                                     (map :target links)]))
                        (map string/lower-case))
                        (map string/lower-case))
-        names (db/pull-many '[:page/name :page/original-name] (mapv (fn [page] [:page/name page]) all-pages))
-        names (zipmap (map :page/name names)
-                      (map (fn [x] (get x :page/original-name (:page/name x))) names))
-        nodes (mapv (fn [node] (assoc node :id (get names (:id node)))) nodes)
+        names (db/pull-many '[:block/name :block/original-name] (mapv (fn [page]
+                                                                        (if (util/uuid-string? page)
+                                                                          [:block/uuid (uuid page)]
+                                                                          [:block/name page])) all-pages))
+        names (zipmap (map (fn [x] (get x :block/name)) names)
+                      (map (fn [x]
+                             (get x :block/original-name (:block/name x))) names))
+        nodes (mapv (fn [node] (assoc node :id (get names (:id node) (:id node)))) nodes)
         links (->>
         links (->>
                (mapv (fn [{:keys [source target]}]
                (mapv (fn [{:keys [source target]}]
                        (when (and (not (uuid-or-asset? source))
                        (when (and (not (uuid-or-asset? source))
@@ -89,7 +94,7 @@
 (defn build-global-graph
 (defn build-global-graph
   [theme show-journal?]
   [theme show-journal?]
   (let [dark? (= "dark" theme)
   (let [dark? (= "dark" theme)
-        current-page (:page/name (db/get-current-page))]
+        current-page (:block/name (db/get-current-page))]
     (when-let [repo (state/get-current-repo)]
     (when-let [repo (state/get-current-repo)]
       (let [relation (db/get-pages-relation repo show-journal?)
       (let [relation (db/get-pages-relation repo show-journal?)
             tagged-pages (db/get-all-tagged-pages repo)
             tagged-pages (db/get-all-tagged-pages repo)
@@ -128,9 +133,9 @@
   (let [dark? (= "dark" theme)]
   (let [dark? (= "dark" theme)]
     (when-let [repo (state/get-current-repo)]
     (when-let [repo (state/get-current-repo)]
       (let [page (string/lower-case page)
       (let [page (string/lower-case page)
-            page-entity (db/entity [:page/name page])
-            original-page-name (:page/original-name page-entity)
-            tags (:tags (:page/properties page-entity))
+            page-entity (db/entity [:block/name page])
+            original-page-name (:block/original-name page-entity)
+            tags (:tags (:block/properties page-entity))
             tags (remove #(= page %) tags)
             tags (remove #(= page %) tags)
             ref-pages (db/get-page-referenced-pages repo page)
             ref-pages (db/get-page-referenced-pages repo page)
             mentioned-pages (db/get-pages-that-mentioned-page repo page)
             mentioned-pages (db/get-pages-that-mentioned-page repo page)

+ 24 - 47
src/main/frontend/handler/history.cljs

@@ -1,7 +1,6 @@
 (ns frontend.handler.history
 (ns frontend.handler.history
   (:require [frontend.state :as state]
   (:require [frontend.state :as state]
             [frontend.db :as db]
             [frontend.db :as db]
-            [frontend.history :as history]
             [frontend.handler.file :as file]
             [frontend.handler.file :as file]
             [frontend.handler.editor :as editor]
             [frontend.handler.editor :as editor]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.ui :as ui-handler]
@@ -11,7 +10,8 @@
             [goog.object :as gobj]
             [goog.object :as gobj]
             [dommy.core :as d]
             [dommy.core :as d]
             [frontend.util :as util]
             [frontend.util :as util]
-            [medley.core :as medley]))
+            [medley.core :as medley]
+            [frontend.modules.editor.undo-redo :as undo-redo]))
 
 
 (defn- default-undo
 (defn- default-undo
   []
   []
@@ -22,53 +22,30 @@
   (js/document.execCommand "redo" false nil))
   (js/document.execCommand "redo" false nil))
 
 
 (defn restore-cursor!
 (defn restore-cursor!
-  [{:keys [block-container block-idx pos] :as state}]
+  [{:keys [last-edit-block container pos] :as state}]
   (ui-handler/re-render-root!)
   (ui-handler/re-render-root!)
-  ;; get the element
-  (when (and block-container block-idx pos)
-    (when-let [container (gdom/getElement block-container)]
-      (let [blocks (d/by-class container "ls-block")
-            block-node (util/nth-safe (seq blocks) block-idx)
-            id (and block-node (gobj/get block-node "id"))]
-        (when id
-          (let [block-id (->> (take-last 36 id)
-                              (apply str))
-                block-uuid (when (util/uuid-string? block-id)
-                             (uuid block-id))]
-            (when block-uuid
-              (when-let [block (db/pull [:block/uuid block-uuid])]
-                (editor/edit-block! block pos
-                                    (:block/format block)
-                                    (:block/uuid block))))))))))
+  (when (and container last-edit-block)
+    (when-let [container (gdom/getElement container)]
+      (when-let [block-uuid (:block/uuid last-edit-block)]
+        (when-let [block (db/pull [:block/uuid block-uuid])]
+          (editor/edit-block! block pos
+                              (:block/format block)
+                              (:block/uuid block)
+                              {:custom-content (:block/content block)}))))))
 
 
 (defn undo!
 (defn undo!
-  []
-  (when-not (state/get-editor-op)
-    (let [route (get-in (:route-match @state/state) [:data :name])]
-      (if (and (contains? #{:home :page :file} route)
-               (state/get-current-repo))
-        (let [repo (state/get-current-repo)
-              chan (async/promise-chan)
-              save-commited? (atom nil)
-              undo-fn (fn []
-                        (history/undo! repo file/alter-file restore-cursor!))]
-          (editor/save-current-block-when-idle! {:check-idle? false
-                                                 :chan chan
-                                                 :chan-callback (fn []
-                                                                  (reset! save-commited? true))})
-          (if @save-commited?
-            (async/go
-              (let [_ (async/<! chan)]
-                (undo-fn)))
-            (undo-fn)))
-        (default-undo)))))
+  [e]
+  (util/stop e)
+  (state/set-editor-op! :undo)
+  (editor/save-current-block!)
+  (let [{:keys [editor-cursor]} (undo-redo/undo)]
+    (restore-cursor! editor-cursor))
+  (state/set-editor-op! nil))
 
 
 (defn redo!
 (defn redo!
-  []
-  (when-not (state/get-editor-op)
-    (let [route (get-in (:route-match @state/state) [:data :name])]
-     (if (and (contains? #{:home :page :file} route)
-              (state/get-current-repo))
-       (let [repo (state/get-current-repo)]
-         (history/redo! repo file/alter-file restore-cursor!))
-       (default-redo)))))
+  [e]
+  (util/stop e)
+  (state/set-editor-op! :redo)
+  (let [{:keys [editor-cursor]} (undo-redo/redo)]
+    (restore-cursor! editor-cursor))
+  (state/set-editor-op! nil))

+ 31 - 2
src/main/frontend/handler/metadata.cljs

@@ -4,6 +4,7 @@
             [cljs.reader :as reader]
             [cljs.reader :as reader]
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.db :as db]
             [frontend.db :as db]
+            [datascript.db :as ddb]
             [clojure.string :as string]
             [clojure.string :as string]
             [promesa.core :as p]))
             [promesa.core :as p]))
 
 
@@ -23,11 +24,16 @@
                            (println "Parsing metadata.edn failed: ")
                            (println "Parsing metadata.edn failed: ")
                            (js/console.dir e)
                            (js/console.dir e)
                            {}))
                            {}))
-              ks (if (vector? k) k [k])
-              new-metadata (assoc-in metadata ks v)
+              new-metadata (cond
+                             (= k :block/properties)
+                             (update metadata :block/properties v) ; v should be a function
+                             :else
+                             (let [ks (if (vector? k) k [k])]
+                               (assoc-in metadata ks v)))
               new-metadata (if encrypted?
               new-metadata (if encrypted?
                              (assoc new-metadata :db/encrypted? true)
                              (assoc new-metadata :db/encrypted? true)
                              new-metadata)
                              new-metadata)
+              _ (prn "New metadata:\n" new-metadata)
               new-content (pr-str new-metadata)]
               new-content (pr-str new-metadata)]
           (file-handler/set-file-content! repo path new-content))))))
           (file-handler/set-file-content! repo path new-content))))))
 
 
@@ -35,3 +41,26 @@
   [encrypted-secret]
   [encrypted-secret]
   (when-not (string/blank? encrypted-secret)
   (when-not (string/blank? encrypted-secret)
     (set-metadata! :db/encrypted-secret encrypted-secret)))
     (set-metadata! :db/encrypted-secret encrypted-secret)))
+
+(defn- handler-properties!
+  [all-properties properties-tx]
+  (reduce
+   (fn [acc datom]
+     (let [v (:v datom)
+           id (or (get v :id)
+                  (get v :title))]
+       (if id
+         (let [added? (ddb/datom-added datom)
+               remove-all-properties? (and (not added?)
+                                           ;; only id
+                                           (= 1 (count v)))]
+           (if remove-all-properties?
+             (dissoc acc id)
+             (assoc acc id v)))
+         acc)))
+   all-properties
+   properties-tx))
+
+(defn update-properties!
+  [properties-tx]
+  (set-metadata! :block/properties #(handler-properties! % properties-tx)))

+ 185 - 330
src/main/frontend/handler/page.cljs

@@ -9,30 +9,31 @@
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.file :as file-handler]
             [frontend.handler.file :as file-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.repo :as repo-handler]
-            [frontend.handler.git :as git-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor :as editor-handler]
-            [frontend.handler.project :as project-handler]
             [frontend.handler.web.nfs :as web-nfs]
             [frontend.handler.web.nfs :as web-nfs]
             [frontend.handler.notification :as notification]
             [frontend.handler.notification :as notification]
             [frontend.handler.config :as config-handler]
             [frontend.handler.config :as config-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.ui :as ui-handler]
+            [frontend.modules.outliner.file :as outliner-file]
+            [frontend.modules.outliner.core :as outliner-core]
+            [frontend.modules.outliner.tree :as outliner-tree]
             [frontend.commands :as commands]
             [frontend.commands :as commands]
             [frontend.date :as date]
             [frontend.date :as date]
             [clojure.walk :as walk]
             [clojure.walk :as walk]
             [frontend.git :as git]
             [frontend.git :as git]
             [frontend.fs :as fs]
             [frontend.fs :as fs]
+            [frontend.util.property :as property]
             [promesa.core :as p]
             [promesa.core :as p]
             [lambdaisland.glogi :as log]
             [lambdaisland.glogi :as log]
-            [frontend.format.mldoc :as mldoc]
-            [cljs-time.core :as t]
-            [cljs-time.coerce :as tc]
+            [frontend.format.block :as block]
             [cljs.reader :as reader]
             [cljs.reader :as reader]
-            [goog.object :as gobj]))
+            [goog.object :as gobj]
+            [clojure.data :as data]))
 
 
 (defn- get-directory
 (defn- get-directory
   [journal?]
   [journal?]
   (if journal?
   (if journal?
-    config/default-journals-directory
+    (config/get-journals-directory)
     (config/get-pages-directory)))
     (config/get-pages-directory)))
 
 
 (defn- get-file-name
 (defn- get-file-name
@@ -43,122 +44,102 @@
     ;; Win10 file path has a length limit of 260 chars
     ;; Win10 file path has a length limit of 260 chars
     (util/safe-subs s 0 200)))
     (util/safe-subs s 0 200)))
 
 
+(defn get-page-file-path
+  ([] (get-page-file-path (state/get-current-page)))
+  ([page-name] (when-let [page (db/entity [:block/name page-name])]
+                 (:file/path (:block/file page)))))
+
+(defn default-properties-block
+  [title format page]
+  (let [properties (common-handler/get-page-default-properties title)
+        content (property/build-properties-str format properties)]
+    {:block/pre-block? true
+     :block/uuid (db/new-block-id)
+     :block/properties properties
+     :block/left page
+     :block/format format
+     :block/content content
+     :block/parent page
+     :block/unordered true
+     :block/page page}))
+
 (defn create!
 (defn create!
   ([title]
   ([title]
    (create! title {}))
    (create! title {}))
   ([title {:keys [redirect?]
   ([title {:keys [redirect?]
            :or {redirect? true}}]
            :or {redirect? true}}]
-   (let [title (and title (string/trim title))
-         repo (state/get-current-repo)
-         dir (config/get-repo-dir repo)
-         journal-page? (date/valid-journal-title? title)
-         directory (get-directory journal-page?)]
-     (when dir
-       (p/let [_ (-> (fs/mkdir! (str dir "/" directory))
-                     (p/catch (fn [_e])))]
-         (let [format (name (state/get-preferred-format))
-               page (string/lower-case title)
-               path (str (get-file-name journal-page? title)
-                         "."
-                         (if (= format "markdown") "md" format))
-               path (str directory "/" path)
-               file-path (str "/" path)]
-           (p/let [exists? (fs/file-exists? dir file-path)]
-             (if exists?
-               (notification/show!
-                [:p.content
-                 (util/format "File %s already exists!" file-path)]
-                :error)
-               ;; Create the file
-               (let [content (util/default-content-with-title format title)]
-                 ;; Write to the db first, then write to the filesystem,
-                 ;; otherwise, the main electron ipc will notify that there's
-                 ;; a new file created.
-                 ;; Question: what if the fs write failed?
-                 (p/let [_ (file-handler/reset-file! repo path content)
-                         _ (fs/create-if-not-exists repo dir file-path content)
-                         _ (git-handler/git-add repo path)]
-                   (when redirect?
-                     (route-handler/redirect! {:to :page
-                                               :path-params {:name page}})
-
-                     ;; Edit the first block
-                     (let [blocks (db/get-page-blocks page)
-                           last-block (last blocks)]
-                       (when last-block
-                         (js/setTimeout
-                          #(editor-handler/edit-last-block-for-new-page! last-block 0)
-                          100))))))))))))))
-
-(defn page-add-properties!
-  [page-name properties]
-  (let [page (db/entity [:page/name page-name])
-        page-title (:or (:page/original-name page) (:page/name page))
-        file (:page/file page)]
-    (if file
-      (let [page-format (db/get-page-format page-name)
-            properties-content (db/get-page-properties-content page-name)
-            file (db/entity (:db/id (:page/file page)))
-            file-path (:file/path file)
-            file-content (db/get-file file-path)
-            after-content (if (string/blank? properties-content)
-                            file-content
-                            (subs file-content (inc (count properties-content))))
-            properties-content (if properties-content
-                                 (string/trim properties-content)
-                                 (config/properties-wrapper page-format))
-            new-properties-content (db/add-properties! page-format properties-content properties)
-            full-content (str new-properties-content "\n\n" (string/trim after-content))]
-        (file-handler/alter-file (state/get-current-repo)
-                                 file-path
-                                 full-content
-                                 {:reset? true
-                                  :re-render-root? true}))
-      (p/let [_ (create! page-name)]
-        (page-add-properties! page-name properties)))))
-
-(defn page-remove-property!
-  [page-name k]
-  (when-let [properties-content (string/trim (db/get-page-properties-content page-name))]
-    (let [page (db/entity [:page/name page-name])
-          file (db/entity (:db/id (:page/file page)))
-          file-path (:file/path file)
-          file-content (db/get-file file-path)
-          after-content (subs file-content (count properties-content))
-          page-format (db/get-page-format page-name)
-          new-properties-content (let [lines (string/split-lines properties-content)
-                                       prefix (case page-format
-                                                :org (str "#+" (string/upper-case k) ": ")
-                                                :markdown (str (string/lower-case k) ": ")
-                                                "")
-                                       exists? (atom false)
-                                       lines (remove #(util/starts-with? % prefix) lines)]
-                                   (string/join "\n" lines))
-          full-content (str new-properties-content "\n\n" (string/trim after-content))]
-      (file-handler/alter-file (state/get-current-repo)
-                               file-path
-                               full-content
-                               {:reset? true
-                                :re-render-root? true}))))
-
-(defn published-success-handler
-  [page-name]
-  (fn [result]
-    (let [permalink (:permalink result)]
-      (page-add-properties! page-name {"permalink" permalink})
-      (let [win (js/window.open (str
-                                 config/website
-                                 "/"
-                                 (state/get-current-project)
-                                 "/"
-                                 permalink))]
-        (.focus win)))))
-
-(defn published-failed-handler
-  [error]
-  (notification/show!
-   "Publish failed, please give it another try."
-   :error))
+   (let [title (string/trim title)
+         page (string/lower-case title)
+         tx (block/page-name->map title true)
+         format (state/get-preferred-format)
+         page-entity [:block/uuid (:block/uuid tx)]
+         create-title-property? (util/include-windows-reserved-chars? title)
+         default-properties (default-properties-block title format page-entity)
+         empty-block {:block/uuid (db/new-block-id)
+                      :block/left [:block/uuid (:block/uuid default-properties)]
+                      :block/format format
+                      :block/content ""
+                      :block/parent page-entity
+                      :block/unordered true
+                      :block/page page-entity}
+         txs (if create-title-property?
+               [tx default-properties empty-block]
+               [tx])]
+     (db/transact! txs)
+     (when redirect?
+      (route-handler/redirect! {:to :page
+                                :path-params {:name page}})
+      (when create-title-property?
+        (js/setTimeout (fn []
+                        (editor-handler/edit-block! empty-block 0 format (:block/uuid empty-block))) 50))))))
+
+(defn page-add-property!
+  [page-name key value]
+  (when-let [page (db/pull [:block/name (string/lower-case page-name)])]
+    (let [repo (state/get-current-repo)
+          key (keyword key)
+          pre-block (db/get-pre-block repo (:db/id page))
+          format (state/get-preferred-format)
+          page-id {:db/id (:db/id page)}
+          org? (= format :org)
+          value (if (contains? #{:filters} key) (pr-str value) value)]
+      (if pre-block
+        (let [properties (:block/properties pre-block)
+              new-properties (assoc properties key value)
+              content (:block/content pre-block)
+              front-matter? (property/front-matter? content)
+              new-content (property/insert-property format content key value front-matter?)
+              block {:db/id (:db/id pre-block)
+                     :block/properties new-properties
+                     :block/content new-content
+                     :block/page page-id}
+              tx [(assoc page-id :block/properties new-properties)
+                  block]]
+          ;; (util/pprint tx)
+          (db/transact! tx)
+          (db/refresh! repo {:key :block/change
+                             :data [block]}))
+        (let [block {:block/uuid (db/new-block-id)
+                     :block/left page-id
+                     :block/parent page-id
+                     :block/page page-id
+                     :block/title []
+                     :block/content (if org?
+                                      (str "#+" (string/upper-case (name key)) ": " value)
+                                      (str (name key) ":: " value))
+                     :block/format format
+                     :block/properties {key value}
+                     :block/file (:block/file page)
+                     :block/pre-block? true}]
+          (outliner-core/insert-node (outliner-core/block block)
+                                     (outliner-core/block page)
+                                     false)
+          (db/transact! [(assoc page-id :block/properties {key value})])
+          (db/refresh! repo {:key :block/change
+                             :data [block]})
+          ;; (ui-handler/re-render-root!)
+          ))
+      (outliner-file/sync-to-file page-id))))
 
 
 (defn get-plugins
 (defn get-plugins
   [blocks]
   [blocks]
@@ -183,91 +164,6 @@
      (map :block/body blocks))
      (map :block/body blocks))
     @plugins))
     @plugins))
 
 
-(defn publish-page-as-slide!
-  ([page-name project-add-modal export-page-html]
-   (publish-page-as-slide! page-name (db/get-page-blocks page-name) project-add-modal export-page-html))
-  ([page-name blocks project-add-modal export-page-html]
-   (project-handler/exists-or-create!
-    (fn [project]
-      (config-handler/set-config! [:project :name] project)
-      (page-add-properties! page-name {"published" true
-                                       "slide" true})
-      (let [properties (db/get-page-properties page-name)
-            plugins (get-plugins blocks)
-            data {:project project
-                  :title page-name
-                  :permalink (:permalink properties)
-                  :html (export-page-html page-name blocks notification/show!)
-                  :tags (:tags properties)
-                  :settings (merge
-                             (assoc properties
-                                    :slide true
-                                    :published true)
-                             plugins)
-                  :repo (state/get-current-repo)}]
-        (util/post (str config/api "pages")
-                   data
-                   (published-success-handler page-name)
-                   published-failed-handler)))
-    project-add-modal)))
-
-(defn publish-page!
-  [page-name project-add-modal export-page-html]
-  (project-handler/exists-or-create!
-   (fn [project]
-     (let [properties (db/get-page-properties page-name)
-           slide? (let [slide (:slide properties)]
-                    (or (true? slide)
-                        (= "true" slide)))
-           blocks (db/get-page-blocks page-name)
-           plugins (get-plugins blocks)]
-       (p/let [_ (config-handler/set-config! [:project :name] project)]
-         (if slide?
-           (publish-page-as-slide! page-name blocks project-add-modal)
-           (do
-             (page-add-properties! page-name {"published" true})
-             (let [data {:project project
-                         :title page-name
-                         :permalink (:permalink properties)
-                         :html (export-page-html page-name blocks notification/show!)
-                         :tags (:tags properties)
-                         :settings (merge properties plugins)
-                         :repo (state/get-current-repo)}]
-               (util/post (str config/api "pages")
-                          data
-                          (published-success-handler page-name)
-                          published-failed-handler))))
-         (state/close-modal!))))
-   project-add-modal))
-
-(defn unpublished-success-handler
-  [page-name]
-  (fn [result]
-    (state/close-modal!)
-    (notification/show!
-     "Un-publish successfully!"
-     :success)))
-
-(defn unpublished-failed-handler
-  [error]
-  (notification/show!
-   "Un-publish failed, please give it another try."
-   :error))
-
-(defn unpublish-page!
-  [page-name]
-  (page-add-properties! page-name {"published" false})
-  (let [properties (db/get-page-properties page-name)
-        permalink (:permalink properties)
-        project (state/get-current-project)]
-    (if (and project permalink)
-      (util/delete (str config/api project "/" permalink)
-                   (unpublished-success-handler page-name)
-                   unpublished-failed-handler)
-      (notification/show!
-       "Can't find the permalink of this page!"
-       :error))))
-
 (defn delete!
 (defn delete!
   [page-name ok-handler]
   [page-name ok-handler]
   (when page-name
   (when page-name
@@ -278,9 +174,6 @@
           ;; delete file
           ;; delete file
           (when-not (string/blank? file-path)
           (when-not (string/blank? file-path)
             (db/transact! [[:db.fn/retractEntity [:file/path file-path]]])
             (db/transact! [[:db.fn/retractEntity [:file/path file-path]]])
-            (when-let [files-conn (db/get-files-conn repo)]
-              (d/transact! files-conn [[:db.fn/retractEntity [:file/path file-path]]]))
-
             (let [blocks (db/get-page-blocks page-name)
             (let [blocks (db/get-page-blocks page-name)
                   tx-data (mapv
                   tx-data (mapv
                            (fn [block]
                            (fn [block]
@@ -296,7 +189,7 @@
                (p/catch (fn [err]
                (p/catch (fn [err]
                           (js/console.error "error: " err))))))
                           (js/console.error "error: " err))))))
 
 
-          (db/transact! [[:db.fn/retractEntity [:page/name page-name]]])
+          (db/transact! [[:db.fn/retractEntity [:block/name page-name]]])
 
 
           (ok-handler))))))
           (ok-handler))))))
 
 
@@ -312,18 +205,12 @@
 (defn rename-file!
 (defn rename-file!
   [file new-name ok-handler]
   [file new-name ok-handler]
   (let [repo (state/get-current-repo)
   (let [repo (state/get-current-repo)
+        file (db/pull (:db/id file))
         old-path (:file/path file)
         old-path (:file/path file)
         new-path (compute-new-file-path old-path new-name)]
         new-path (compute-new-file-path old-path new-name)]
     ;; update db
     ;; update db
     (db/transact! repo [{:db/id (:db/id file)
     (db/transact! repo [{:db/id (:db/id file)
                          :file/path new-path}])
                          :file/path new-path}])
-
-    ;; update files db
-    (let [conn (db/get-files-conn repo)]
-      (when-let [file (d/entity (d/db conn) [:file/path old-path])]
-        (d/transact! conn [{:db/id (:db/id file)
-                            :file/path new-path}])))
-
     (->
     (->
      (p/let [_ (fs/rename! repo
      (p/let [_ (fs/rename! repo
                            (if (util/electron?)
                            (if (util/electron?)
@@ -339,45 +226,80 @@
      (p/catch (fn [error]
      (p/catch (fn [error]
                 (println "file rename failed: " error))))))
                 (println "file rename failed: " error))))))
 
 
+;; FIXME: not safe
+(defn- replace-old-page!
+  [s old-name new-name]
+  (-> s
+      (string/replace (util/format "[[%s]]" old-name) (util/format "[[%s]]" new-name))
+      (string/replace (str "#" old-name) (str "#" new-name))))
+
+(defn- walk-replace-old-page!
+  [form old-name new-name]
+  (walk/postwalk (fn [f] (if (string? f)
+                          (if (= f old-name)
+                            new-name
+                            (replace-old-page! f old-name new-name))
+                           f)) form))
+
 (defn rename!
 (defn rename!
   [old-name new-name]
   [old-name new-name]
   (let [new-name (string/trim new-name)]
   (let [new-name (string/trim new-name)]
     (when-not (string/blank? new-name)
     (when-not (string/blank? new-name)
       (when (and old-name new-name)
       (when (and old-name new-name)
-        (let [case-changed? (and (= (string/lower-case old-name)
-                                    (string/lower-case new-name))
-                                 (not= (string/trim old-name)
-                                       (string/trim new-name)))
-              name-changed? (not= (string/lower-case (string/trim old-name))
+        (let [name-changed? (not= (string/lower-case (string/trim old-name))
                                   (string/lower-case (string/trim new-name)))]
                                   (string/lower-case (string/trim new-name)))]
           (when-let [repo (state/get-current-repo)]
           (when-let [repo (state/get-current-repo)]
-            (when-let [page (db/entity [:page/name (string/lower-case old-name)])]
-              (let [old-original-name (:page/original-name page)
-                    file (:page/file page)
-                    journal? (:page/journal? page)]
-                (d/transact! (db/get-conn repo false)
-                  [{:db/id (:db/id page)
-                    :page/name (string/lower-case new-name)
-                    :page/original-name new-name}])
+            (when-let [page (db/pull [:block/name (string/lower-case old-name)])]
+              (let [old-original-name (:block/original-name page)
+                    file (:block/file page)
+                    journal? (:block/journal? page)
+                    properties-block (:data (outliner-tree/-get-down (outliner-core/block page)))
+                    properties-block-tx (when (and properties-block
+                                                   (string/includes? (string/lower-case (:block/content properties-block))
+                                                                     (string/lower-case old-name)))
+                                          {:db/id (:db/id properties-block)
+                                           :block/content (property/insert-property (:block/format properties-block)
+                                                                                 (:block/content properties-block)
+                                                                                 :title
+                                                                                 new-name)})
+                    page-txs [{:db/id (:db/id page)
+                               :block/uuid (:block/uuid page)
+                               :block/name (string/lower-case new-name)
+                               :block/original-name new-name}]
+                    page-txs (if properties-block-tx (conj page-txs properties-block-tx) page-txs)]
+
+                (d/transact! (db/get-conn repo false) page-txs)
 
 
                 (when (and file (not journal?) name-changed?)
                 (when (and file (not journal?) name-changed?)
-                  (rename-file! file new-name
-                                (fn []
-                                  (page-add-properties! (string/lower-case new-name) {:title new-name}))))
+                  (rename-file! file new-name (fn [] nil)))
 
 
                 ;; update all files which have references to this page
                 ;; update all files which have references to this page
-                (let [files (db/get-files-that-referenced-page (:db/id page))]
-                  (doseq [file-path files]
-                    (let [file-content (db/get-file file-path)
-                          ;; FIXME: not safe
-                          new-content (string/replace file-content
-                                                      (util/format "[[%s]]" old-original-name)
-                                                      (util/format "[[%s]]" new-name))]
-                      (file-handler/alter-file repo
-                                               file-path
-                                               new-content
-                                               {:reset? true
-                                                :re-render-root? false})))))
+                (let [blocks (db/get-page-referenced-blocks-no-cache (:db/id page))
+                      page-ids (->> (map :block/page blocks)
+                                    (remove nil?)
+                                    (set))
+                      tx (->> (map (fn [{:block/keys [uuid title content properties] :as block}]
+                                     (let [title (let [title' (walk-replace-old-page! title old-original-name new-name)]
+                                                   (when-not (= title' title)
+                                                     title'))
+                                           content (let [content' (replace-old-page! content old-original-name new-name)]
+                                                     (when-not (= content' content)
+                                                       content'))
+                                           properties (let [properties' (walk-replace-old-page! properties old-original-name new-name)]
+                                                        (when-not (= properties' properties)
+                                                          properties'))]
+                                       (when (or title content properties)
+                                         (util/remove-nils-non-nested
+                                          {:block/uuid uuid
+                                           :block/title title
+                                           :block/content content
+                                           :block/properties properties})))) blocks)
+                              (remove nil?))]
+                  (db/transact! repo tx)
+                  (doseq [page-id page-ids]
+                    (outliner-file/sync-to-file page-id)))
+
+                (outliner-file/sync-to-file page))
 
 
               ;; TODO: update browser history, remove the current one
               ;; TODO: update browser history, remove the current one
 
 
@@ -391,43 +313,15 @@
 
 
               (ui-handler/re-render-root!))))))))
               (ui-handler/re-render-root!))))))))
 
 
-(defn rename-when-alter-title-property!
-  [page path format original-content content]
-  (when (and page (contains? config/mldoc-support-formats format))
-    (let [old-name page
-          new-name (let [ast (mldoc/->edn content (mldoc/default-config format))]
-                     (db/get-page-name path ast))
-          journal? (date/valid-journal-title? old-name)]
-      (if (not= old-name new-name)
-        (if journal?
-          [true old-name]
-          (do
-            (rename! old-name new-name)
-            [false new-name]))
-        [journal? old-name]))))
-
 (defn handle-add-page-to-contents!
 (defn handle-add-page-to-contents!
   [page-name]
   [page-name]
-  (let [last-block (last (db/get-page-blocks (state/get-current-repo) "contents"))
-        last-empty? (>= 3 (count (:block/content last-block)))
-        heading-pattern (config/get-block-pattern (state/get-preferred-format))
-        pre-str (str heading-pattern heading-pattern)
-        new-content (if last-empty?
-                      (str pre-str " [[" page-name "]]")
-                      (str (string/trimr (:block/content last-block))
-                           "\n"
-                           pre-str " [[" page-name "]]"))]
-    (editor-handler/insert-new-block-aux!
-     last-block
-     new-content
-     {:create-new-block? false
-      :ok-handler
-      (fn [_]
-        (notification/show! "Added to contents!" :success)
-        (editor-handler/clear-when-saved!))
-      :with-level? true
-      :new-level 2
-      :current-page "Contents"})))
+  (let [content (str "[[" page-name "]]")]
+    (editor-handler/api-insert-new-block!
+     content
+     {:page "Contents"
+      :sibling? true})
+    (notification/show! "Added to contents!" :success)
+    (editor-handler/clear-when-saved!)))
 
 
 (defn has-more-journals?
 (defn has-more-journals?
   []
   []
@@ -441,7 +335,7 @@
 
 
 (defn update-public-attribute!
 (defn update-public-attribute!
   [page-name value]
   [page-name value]
-  (page-add-properties! page-name {:public value}))
+  (page-add-property! page-name :public value))
 
 
 (defn get-page-ref-text
 (defn get-page-ref-text
   [page]
   [page]
@@ -452,7 +346,7 @@
         page-name (string/lower-case page)]
         page-name (string/lower-case page)]
     (if (and edit-block-file-path
     (if (and edit-block-file-path
              (state/org-mode-file-link? (state/get-current-repo)))
              (state/org-mode-file-link? (state/get-current-repo)))
-      (if-let [ref-file-path (:file/path (:page/file (db/entity [:page/name page-name])))]
+      (if-let [ref-file-path (:file/path (:file/file (db/entity [:file/name page-name])))]
         (util/format "[[file:%s][%s]]"
         (util/format "[[file:%s][%s]]"
                      (util/get-relative-path edit-block-file-path ref-file-path)
                      (util/get-relative-path edit-block-file-path ref-file-path)
                      page)
                      page)
@@ -471,42 +365,6 @@
   []
   []
   (commands/init-commands! get-page-ref-text))
   (commands/init-commands! get-page-ref-text))
 
 
-(defn delete-page-from-logseq
-  [project permalink]
-  (let [url (util/format "%s%s/%s" config/api project permalink)]
-    (js/Promise.
-     (fn [resolve reject]
-       (util/delete url
-                    (fn [result]
-                      (resolve result))
-                    (fn [error]
-                      (log/error :page/http-delete-failed error)
-                      (reject error)))))))
-
-(defn get-page-list-by-project-name
-  [project]
-  (js/Promise.
-   (fn [resolve _]
-     (if-not (string? project)
-       (resolve :project-name-is-invalid)
-       (let [url (util/format "%sprojects/%s/pages" config/api project)]
-         (util/fetch url
-                     (fn [result]
-                       (log/debug :page/get-page-list result)
-                       (let [data (:result result)]
-                         (if (sequential? data)
-                           (do (state/set-published-pages data)
-                               (resolve data))
-                           (log/error :page/http-get-list-result-malformed result))))
-                     (fn [error]
-                       (log/error :page/http-get-list-failed error)
-                       (resolve error))))))))
-
-(defn update-state-and-notify
-  [page-name]
-  (page-add-properties! page-name {:published false})
-  (notification/show! (util/format "Remove Page \"%s\" from Logseq server success" page-name) :success))
-
 (defn add-page-to-recent!
 (defn add-page-to-recent!
   [repo page]
   [repo page]
   (let [pages (or (db/get-key-value repo :recent/pages)
   (let [pages (or (db/get-key-value repo :recent/pages)
@@ -528,28 +386,25 @@
    (fn []
    (fn []
      (init-commands!))))
      (init-commands!))))
 
 
-
 ;; TODO: add use :file/last-modified-at
 ;; TODO: add use :file/last-modified-at
 (defn get-pages-with-modified-at
 (defn get-pages-with-modified-at
   [repo]
   [repo]
   (->> (db/get-modified-pages repo)
   (->> (db/get-modified-pages repo)
        (remove util/file-page?)))
        (remove util/file-page?)))
 
 
-(defn save-filter!
-  [page-name filter-state]
-  (if (empty? filter-state)
-    (page-remove-property! page-name "filters")
-    (page-add-properties! page-name {"filters" filter-state})))
-
-(defn get-filter
+(defn get-filters
   [page-name]
   [page-name]
   (let [properties (db/get-page-properties page-name)]
   (let [properties (db/get-page-properties page-name)]
-    (atom (reader/read-string (get-in properties [:filters] "{}")))))
+    (reader/read-string (get properties :filters "{}"))))
+
+(defn save-filter!
+  [page-name filter-state]
+  (page-add-property! page-name :filters filter-state))
 
 
 (defn page-exists?
 (defn page-exists?
   [page-name]
   [page-name]
   (when page-name
   (when page-name
-    (db/entity [:page/name page-name])))
+    (db/entity [:block/name page-name])))
 
 
 ;; Editor
 ;; Editor
 (defn page-not-exists-handler
 (defn page-not-exists-handler
@@ -584,16 +439,16 @@
     (if (state/sub :editor/show-page-search-hashtag?)
     (if (state/sub :editor/show-page-search-hashtag?)
       (fn [chosen _click?]
       (fn [chosen _click?]
         (state/set-editor-show-page-search! false)
         (state/set-editor-show-page-search! false)
-        (let [chosen (if (re-find #"\s+" chosen)
+        (let [wrapped? (= "[[" (util/safe-subs edit-content (- pos 2) pos))
+              chosen (if (and (re-find #"\s+" chosen) (not wrapped?))
                        (util/format "[[%s]]" chosen)
                        (util/format "[[%s]]" chosen)
                        chosen)]
                        chosen)]
           (editor-handler/insert-command! id
           (editor-handler/insert-command! id
-                                          (str "#" chosen)
+                                          (str "#" (when wrapped? "[[") chosen)
                                           format
                                           format
                                           {:last-pattern (let [q (if @editor-handler/*selected-text "" q)]
                                           {:last-pattern (let [q (if @editor-handler/*selected-text "" q)]
-                                                           (if (and q (string/starts-with? q "#"))
-                                                             q
-                                                             (str "#" q)))})))
+                                                           (str "#" (when wrapped? "[[") q))
+                                           :forward-pos (if wrapped? 3 2)})))
       (fn [chosen _click?]
       (fn [chosen _click?]
         (state/set-editor-show-page-search! false)
         (state/set-editor-show-page-search! false)
         (let [page-ref-text (get-page-ref-text chosen)]
         (let [page-ref-text (get-page-ref-text chosen)]

+ 0 - 112
src/main/frontend/handler/project.cljs

@@ -1,112 +0,0 @@
-(ns frontend.handler.project
-  (:require [frontend.state :as state]
-            [frontend.util :as util :refer-macros [profile]]
-            [clojure.string :as string]
-            [frontend.config :as config]
-            [frontend.handler.notification :as notification]
-            [lambdaisland.glogi :as log]))
-
-(defn get-current-project
-  [current-repo projects]
-  (let [project (some (fn [p]
-                        (when (= (:repo p) current-repo)
-                          p))
-                  projects)
-        project-name (:name project)]
-    (when-not (string/blank? project-name) project-name)))
-
-;; project exists and current user owns it
-;; if project not exists, the server will create it
-(defn project-exists?
-  [project]
-  (let [projects (set (map :name (:projects (state/get-me))))]
-    (and (seq projects) (contains? projects project))))
-
-(defn create-project!
-  ([ok-handler]
-   (create-project! (state/get-current-project) ok-handler))
-  ([project ok-handler]
-   (when (state/logged?)
-     (let [config (state/get-config)
-           data {:name project
-                 :repo (state/get-current-repo)
-                 :settings (or (get config :project)
-                               {:name project})}]
-       (util/post (str config/api "projects")
-                  data
-                  (fn [result]
-                    (when-not (:message result) ; exists
-                      (swap! state/state
-                             update-in [:me :projects]
-                             (fn [projects]
-                               (util/distinct-by :name (conj projects (:result result)))))
-                      ;; update config
-                      (ok-handler project)))
-                  (fn [error]
-                    (js/console.dir error)
-                    (notification/show! (util/format "Project \"%s\" already taken, please change to another name." project) :error)))))))
-
-(defn exists-or-create!
-  [ok-handler modal-content]
-  (when (state/logged?)
-    (if-let [project (state/get-current-project)]
-      (if (project-exists? project)
-        (ok-handler project)
-        (create-project! ok-handler))
-      (state/set-modal! modal-content))))
-
-(defn add-project!
-  [project ok-handler]
-  (when (state/logged?)
-    (create-project! project
-                     (fn [project]
-                       (when ok-handler (ok-handler project))
-                       (notification/show! (util/format "Project \"%s\" was created successfully." project) :success)
-                       (state/close-modal!)))))
-
-(defn sync-project-settings!
-  ([]
-   (when-let [project-name (state/get-current-project)]
-     (let [settings (:project (state/get-config))]
-       (sync-project-settings! project-name settings))))
-  ([project-name settings]
-   (when (state/logged?)
-     (when-let [repo (state/get-current-repo)]
-       (if (project-exists? project-name)
-         (util/post (str config/api "projects/" project-name)
-                    {:name project-name
-                     :settings settings
-                     :repo repo}
-                    (fn [response]
-                      (notification/show! "Project settings changed successfully!" :success))
-                    (fn [error]
-                      (println "Project settings updated failed, reason: ")
-                      (js/console.dir error)))
-         (when (and settings
-                    (not (string/blank? (:name settings)))
-                    (>= (count (string/trim (:name settings))) 2))
-           (add-project! (:name settings) nil)))))))
-
-(defn update-project
-  [project-name data]
-  (let [url (util/format "%sprojects/%s" config/api project-name)]
-    (js/Promise.
-      (fn [resolve reject]
-        (util/post url data
-          (fn [result]
-            (resolve result))
-          (fn [error]
-            (log/error :project/http-update-failed error)
-            (reject error)))))))
-
-(defn delete-project
-  [project-name]
-  (let [url (util/format "%sprojects/%s" config/api project-name)]
-    (js/Promise.
-      (fn [resolve reject]
-        (util/delete url
-          (fn [result]
-            (resolve result))
-          (fn [error]
-            (log/error :project/http-delete-failed error)
-            (reject error)))))))

+ 63 - 162
src/main/frontend/handler/repo.cljs

@@ -1,57 +1,36 @@
 (ns frontend.handler.repo
 (ns frontend.handler.repo
   (:refer-clojure :exclude [clone])
   (:refer-clojure :exclude [clone])
-  (:require [frontend.util :as util :refer-macros [profile]]
+  (:require [cljs-bean.core :as bean]
+            [clojure.string :as string]
+            [frontend.config :as config]
+            [frontend.date :as date]
+            [frontend.db :as db]
+            [frontend.dicts :as dicts]
+            [frontend.encrypt :as encrypt]
+            [frontend.format :as format]
             [frontend.fs :as fs]
             [frontend.fs :as fs]
             [frontend.fs.nfs :as nfs]
             [frontend.fs.nfs :as nfs]
-            [promesa.core :as p]
-            [lambdaisland.glogi :as log]
-            [frontend.state :as state]
-            [frontend.db :as db]
-            [frontend.idb :as idb]
             [frontend.git :as git]
             [frontend.git :as git]
-            [cljs-bean.core :as bean]
-            [frontend.date :as date]
-            [frontend.config :as config]
-            [frontend.format :as format]
-            [frontend.search :as search]
-            [frontend.handler.ui :as ui-handler]
-            [frontend.handler.git :as git-handler]
+            [frontend.handler.common :as common-handler]
+            [frontend.handler.extract :as extract-handler]
             [frontend.handler.file :as file-handler]
             [frontend.handler.file :as file-handler]
+            [frontend.handler.git :as git-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.notification :as notification]
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
-            [frontend.handler.common :as common-handler]
-            [frontend.handler.extract :as extract-handler]
-            [frontend.ui :as ui]
-            [clojure.string :as string]
-            [frontend.dicts :as dicts]
+            [frontend.handler.ui :as ui-handler]
+            [frontend.idb :as idb]
+            [frontend.search :as search]
             [frontend.spec :as spec]
             [frontend.spec :as spec]
-            [frontend.encrypt :as encrypt]
-            [goog.dom :as gdom]
-            [goog.object :as gobj]
-            ;; TODO: remove component dependency from handlers, we can use a core.async channel
-            [frontend.components.encryption :as encryption]))
+            [frontend.state :as state]
+            [frontend.util :as util]
+            [lambdaisland.glogi :as log]
+            [promesa.core :as p]
+            [shadow.resource :as rc]))
 
 
 ;; Project settings should be checked in two situations:
 ;; Project settings should be checked in two situations:
 ;; 1. User changes the config.edn directly in logseq.com (fn: alter-file)
 ;; 1. User changes the config.edn directly in logseq.com (fn: alter-file)
 ;; 2. Git pulls the new change (fn: load-files)
 ;; 2. Git pulls the new change (fn: load-files)
 
 
-(defn show-install-error!
-  [repo-url title]
-  (spec/validate :repos/url repo-url)
-  (notification/show!
-   [:p.content
-    title
-    " "
-    [:span.mr-2
-     (util/format
-      "Please make sure that you've installed the logseq app for the repo %s on GitHub. "
-      repo-url)
-     (ui/button
-      "Install Logseq on GitHub"
-      :href (str "https://github.com/apps/" config/github-app-name "/installations/new"))]]
-   :error
-   false))
-
 (defn create-config-file-if-not-exists
 (defn create-config-file-if-not-exists
   [repo-url]
   [repo-url]
   (spec/validate :repos/url repo-url)
   (spec/validate :repos/url repo-url)
@@ -64,8 +43,7 @@
         (p/let [file-exists? (fs/create-if-not-exists repo-url repo-dir (str app-dir "/" config/config-file) default-content)]
         (p/let [file-exists? (fs/create-if-not-exists repo-url repo-dir (str app-dir "/" config/config-file) default-content)]
           (when-not file-exists?
           (when-not file-exists?
             (file-handler/reset-file! repo-url path default-content)
             (file-handler/reset-file! repo-url path default-content)
-            (common-handler/reset-config! repo-url default-content)
-            (git-handler/git-add repo-url path)))))))
+            (common-handler/reset-config! repo-url default-content)))))))
 
 
 (defn create-contents-file
 (defn create-contents-file
   [repo-url]
   [repo-url]
@@ -77,16 +55,13 @@
                   (config/get-file-extension format))
                   (config/get-file-extension format))
         file-path (str "/" path)
         file-path (str "/" path)
         default-content (case (name format)
         default-content (case (name format)
-                          "org"
-                          "** What's **Contents**?\n*** It's a normal page called [[Contents]], you can use it for:\n**** 1. table of content/index/MOC\n**** 2. pinning/bookmarking favorites pages/blocks (e.g. [[Logseq]])\n**** 3. You can also put many different things, depending on your personal workflow."
-                          "markdown"
-                          "## What's **Contents**?\n### It's a normal page called [[Contents]], you can use it for:\n#### 1. table of content/index/MOC\n#### 2. pinning/bookmarking favorites pages/blocks (e.g. [[Logseq]])\n#### 3. You can also put many different things, depending on your personal workflow."
+                          "org" (rc/inline "contents.org")
+                          "markdown" (rc/inline "contents.md")
                           "")]
                           "")]
     (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" (state/get-pages-directory)))
     (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" (state/get-pages-directory)))
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
       (when-not file-exists?
       (when-not file-exists?
-        (file-handler/reset-file! repo-url path default-content)
-        (git-handler/git-add repo-url path)))))
+        (file-handler/reset-file! repo-url path default-content)))))
 
 
 (defn create-custom-theme
 (defn create-custom-theme
   [repo-url]
   [repo-url]
@@ -98,8 +73,7 @@
     (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" config/app-name))
     (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" config/app-name))
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
       (when-not file-exists?
       (when-not file-exists?
-        (file-handler/reset-file! repo-url path default-content)
-        (git-handler/git-add repo-url path)))))
+        (file-handler/reset-file! repo-url path default-content)))))
 
 
 (defn create-dummy-notes-page
 (defn create-dummy-notes-page
   [repo-url content]
   [repo-url content]
@@ -122,7 +96,7 @@
            format (state/get-preferred-format repo-url)
            format (state/get-preferred-format repo-url)
            title (date/today)
            title (date/today)
            file-name (date/journal-title->default title)
            file-name (date/journal-title->default title)
-           default-content (util/default-content-with-title format title false)
+           default-content (util/default-content-with-title format)
            template (state/get-journal-template)
            template (state/get-journal-template)
            template (if (and template
            template (if (and template
                              (not (string/blank? template)))
                              (not (string/blank? template)))
@@ -135,16 +109,16 @@
                      (str default-content template)
                      (str default-content template)
 
 
                      :else
                      :else
-                     (util/default-content-with-title format title true))
-           path (str config/default-journals-directory "/" file-name "."
+                     default-content)
+           path (str (config/get-journals-directory) "/" file-name "."
                      (config/get-file-extension format))
                      (config/get-file-extension format))
            file-path (str "/" path)
            file-path (str "/" path)
-           page-exists? (db/entity repo-url [:page/name (string/lower-case title)])
+           page-exists? (db/entity repo-url [:block/name (string/lower-case title)])
            empty-blocks? (empty? (db/get-page-blocks-no-cache repo-url (string/lower-case title)))]
            empty-blocks? (empty? (db/get-page-blocks-no-cache repo-url (string/lower-case title)))]
        (when (or empty-blocks?
        (when (or empty-blocks?
                  (not page-exists?))
                  (not page-exists?))
          (p/let [_ (nfs/check-directory-permission! repo-url)
          (p/let [_ (nfs/check-directory-permission! repo-url)
-                 _ (fs/mkdir-if-not-exists (str repo-dir "/" config/default-journals-directory))
+                 _ (fs/mkdir-if-not-exists (str repo-dir "/" (config/get-journals-directory)))
                  file-exists? (fs/file-exists? repo-dir file-path)]
                  file-exists? (fs/file-exists? repo-dir file-path)]
            (when-not file-exists?
            (when-not file-exists?
              (file-handler/reset-file! repo-url path content)
              (file-handler/reset-file! repo-url path content)
@@ -184,23 +158,41 @@
    (create-contents-file repo-url)
    (create-contents-file repo-url)
    (create-custom-theme repo-url)))
    (create-custom-theme repo-url)))
 
 
+(defn- remove-non-exists-refs!
+  [data]
+  (let [block-ids (->> (map :block/uuid data)
+                       (remove nil?)
+                       (set))
+        keep-block-ref-f (fn [refs]
+                           (filter (fn [ref]
+                                     (if (and (vector? ref)
+                                              (= :block/uuid (first ref)))
+                                       (contains? block-ids (second ref))
+                                       ref)) refs))]
+    (map (fn [item]
+           (if (and (map? item)
+                    (:block/uuid item))
+             (update item :block/refs keep-block-ref-f)
+             item)) data)))
+
 (defn- reset-contents-and-blocks!
 (defn- reset-contents-and-blocks!
   [repo-url files blocks-pages delete-files delete-blocks]
   [repo-url files blocks-pages delete-files delete-blocks]
   (db/transact-files-db! repo-url files)
   (db/transact-files-db! repo-url files)
   (let [files (map #(select-keys % [:file/path :file/last-modified-at]) files)
   (let [files (map #(select-keys % [:file/path :file/last-modified-at]) files)
         all-data (-> (concat delete-files delete-blocks files blocks-pages)
         all-data (-> (concat delete-files delete-blocks files blocks-pages)
-                     (util/remove-nils))]
+                     (util/remove-nils)
+                     (remove-non-exists-refs!))]
     (db/transact! repo-url all-data)))
     (db/transact! repo-url all-data)))
 
 
 (defn- parse-files-and-create-default-files-inner!
 (defn- parse-files-and-create-default-files-inner!
-  [repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts]
+  [repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts metadata]
   (let [parsed-files (filter
   (let [parsed-files (filter
                       (fn [file]
                       (fn [file]
                         (let [format (format/get-format (:file/path file))]
                         (let [format (format/get-format (:file/path file))]
                           (contains? config/mldoc-support-formats format)))
                           (contains? config/mldoc-support-formats format)))
                       files)
                       files)
         blocks-pages (if (seq parsed-files)
         blocks-pages (if (seq parsed-files)
-                       (extract-handler/extract-all-blocks-pages repo-url parsed-files)
+                       (extract-handler/extract-all-blocks-pages repo-url parsed-files metadata)
                        [])]
                        [])]
     (let [config-file (config/get-config-path)]
     (let [config-file (config/get-config-path)]
       (when (contains? (set file-paths) config-file)
       (when (contains? (set file-paths) config-file)
@@ -210,25 +202,23 @@
     (reset-contents-and-blocks! repo-url files blocks-pages delete-files delete-blocks)
     (reset-contents-and-blocks! repo-url files blocks-pages delete-files delete-blocks)
     (when first-clone?
     (when first-clone?
       (if (and (not db-encrypted?) (state/enable-encryption? repo-url))
       (if (and (not db-encrypted?) (state/enable-encryption? repo-url))
-        (state/set-modal!
-         (encryption/encryption-setup-dialog
-          repo-url
-          #(create-default-files! repo-url %)))
+        (state/pub-event! [:modal/encryption-setup-dialog repo-url
+                           #(create-default-files! repo-url %)])
         (create-default-files! repo-url db-encrypted?)))
         (create-default-files! repo-url db-encrypted?)))
     (when re-render?
     (when re-render?
       (ui-handler/re-render-root! re-render-opts))
       (ui-handler/re-render-root! re-render-opts))
     (state/set-importing-to-db! false)))
     (state/set-importing-to-db! false)))
 
 
 (defn- parse-files-and-create-default-files!
 (defn- parse-files-and-create-default-files!
-  [repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts]
+  [repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts metadata]
   (if db-encrypted?
   (if db-encrypted?
     (p/let [files (p/all
     (p/let [files (p/all
                    (map (fn [file]
                    (map (fn [file]
                           (p/let [content (encrypt/decrypt (:file/content file))]
                           (p/let [content (encrypt/decrypt (:file/content file))]
                             (assoc file :file/content content)))
                             (assoc file :file/content content)))
                         files))]
                         files))]
-      (parse-files-and-create-default-files-inner! repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts))
-    (parse-files-and-create-default-files-inner! repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts)))
+      (parse-files-and-create-default-files-inner! repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts metadata))
+    (parse-files-and-create-default-files-inner! repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts metadata)))
 
 
 (defn parse-files-and-load-to-db!
 (defn parse-files-and-load-to-db!
   [repo-url files {:keys [first-clone? delete-files delete-blocks re-render? re-render-opts] :as opts
   [repo-url files {:keys [first-clone? delete-files delete-blocks re-render? re-render-opts] :as opts
@@ -244,12 +234,11 @@
           db-encrypted? (:db/encrypted? metadata)
           db-encrypted? (:db/encrypted? metadata)
           db-encrypted-secret (if db-encrypted? (:db/encrypted-secret metadata) nil)]
           db-encrypted-secret (if db-encrypted? (:db/encrypted-secret metadata) nil)]
       (if db-encrypted?
       (if db-encrypted?
-        (state/set-modal!
-         (encryption/encryption-input-secret-dialog
-          repo-url
-          db-encrypted-secret
-          #(parse-files-and-create-default-files! repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts)))
-        (parse-files-and-create-default-files! repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts)))))
+        (let [close-fn #(parse-files-and-create-default-files! repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts metadata)]
+          (state/pub-event! [:modal/encryption-input-secret-dialog repo-url
+                             db-encrypted-secret
+                             close-fn]))
+        (parse-files-and-create-default-files! repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts metadata)))))
 
 
 (defn load-repo-to-db!
 (defn load-repo-to-db!
   [repo-url {:keys [first-clone? diffs nfs-files]
   [repo-url {:keys [first-clone? diffs nfs-files]
@@ -308,92 +297,6 @@
     (load-repo-to-db! repo-url {:first-clone? first-clone?
     (load-repo-to-db! repo-url {:first-clone? first-clone?
                                 :diffs diffs})))
                                 :diffs diffs})))
 
 
-(defn rebuild-page-blocks-children
-  "For performance reason, we can update the :block/children value after every operation,
-  but it's hard to make sure that it's correct, also it needs more time to implement it.
-  We can improve it if the performance is really an issue."
-  [repo page]
-  (let [blocks (->>
-                (db/get-page-blocks-no-cache repo page {:pull-keys '[:db/id :block/uuid :block/level :block/pre-block? :block/meta]})
-                (remove :block/pre-block?)
-                (map #(select-keys % [:db/id :block/uuid :block/level]))
-                (reverse))
-        original-blocks blocks]
-    (loop [blocks blocks
-           tx []
-           children {}
-           last-level 10000]
-      (if (seq blocks)
-        (let [[{:block/keys [uuid level] :as block} & others] blocks
-              [tx children] (cond
-                              (< level last-level)        ; parent
-                              (let [cur-children (get children last-level)
-                                    tx (if (seq cur-children)
-                                         (vec
-                                          (concat
-                                           tx
-                                           (map
-                                            (fn [child]
-                                              [:db/add (:db/id block) :block/children [:block/uuid child]])
-                                            cur-children)))
-                                         tx)
-                                    children (-> children
-                                                 (dissoc last-level)
-                                                 (update level conj uuid))]
-                                [tx children])
-
-                              (> level last-level)        ; child of sibling
-                              (let [children (update children level conj uuid)]
-                                [tx children])
-
-                              :else                       ; sibling
-                              (let [children (update children last-level conj uuid)]
-                                [tx children]))]
-          (recur others tx children level))
-        ;; TODO: add top-level children to the "Page" block (we might remove the Page from db schema)
-        (when (seq tx)
-          (let [delete-tx (map (fn [block]
-                                 [:db/retract (:db/id block) :block/children])
-                               original-blocks)]
-            (->> (concat delete-tx tx)
-                 (remove nil?))))))))
-
-(defn transact-react-and-alter-file!
-  ([repo tx transact-option files]
-   (transact-react-and-alter-file! repo tx transact-option files {}))
-  ([repo tx transact-option files opts]
-   (spec/validate :repos/url repo)
-   (let [files (remove nil? files)
-         pages (->> (map db/get-file-page (map first files))
-                    (remove nil?))]
-     (let [edit-block (state/get-edit-block)
-           edit-input-id (state/get-edit-input-id)
-           block-element (when edit-input-id (gdom/getElement (string/replace edit-input-id "edit-block" "ls-block")))
-           {:keys [idx container]} (when block-element
-                                     (util/get-block-idx-inside-container block-element))]
-       (when (and idx container)
-         (state/set-state! :editor/last-edit-block {:block edit-block
-                                                    :idx idx
-                                                    :container (gobj/get container "id")})))
-
-     ;; try catch so that if db transaction failed, it'll not write to the files
-     (try
-       (do
-         (db/transact-react!
-          repo
-          tx
-          transact-option)
-
-         (when (seq pages)
-           (let [children-tx (mapcat #(rebuild-page-blocks-children repo %) pages)]
-             (when (seq children-tx)
-               (db/transact! repo children-tx))))
-
-         (when (seq files)
-           (file-handler/alter-files repo files opts)))
-       (catch js/Error e
-         (log/error :transact-react/failed e))))))
-
 (declare push)
 (declare push)
 
 
 (defn get-diff-result
 (defn get-diff-result
@@ -481,7 +384,7 @@
                     (cond
                     (cond
                       (string/includes? (str error) "404")
                       (string/includes? (str error) "404")
                       (do (log/error :git/pull-error error)
                       (do (log/error :git/pull-error error)
-                          (show-install-error! repo-url (util/format "Failed to fetch %s." repo-url)))
+                          (state/pub-event! [:repo/install-error repo-url (util/format "Failed to fetch %s." repo-url)]))
 
 
                       (string/includes? (str error) "401")
                       (string/includes? (str error) "401")
                       (let [remain-times (dec try-times)]
                       (let [remain-times (dec try-times)]
@@ -585,7 +488,7 @@
          (state/set-cloning! false)
          (state/set-cloning! false)
          (git-handler/set-git-status! repo-url :clone-failed)
          (git-handler/set-git-status! repo-url :clone-failed)
          (git-handler/set-git-error! repo-url e)
          (git-handler/set-git-error! repo-url e)
-         (show-install-error! repo-url (util/format "Failed to clone %s." repo-url)))))))
+         (state/pub-event! [:repo/install-error repo-url (util/format "Failed to clone %s." repo-url)]))))))
 
 
 (defn remove-repo!
 (defn remove-repo!
   [{:keys [id url] :as repo}]
   [{:keys [id url] :as repo}]
@@ -593,11 +496,10 @@
   (let [delete-db-f (fn []
   (let [delete-db-f (fn []
                       (db/remove-conn! url)
                       (db/remove-conn! url)
                       (db/remove-db! url)
                       (db/remove-db! url)
-                      (db/remove-files-db! url)
                       (search/remove-db! url)
                       (search/remove-db! url)
                       (fs/rmdir! (config/get-repo-dir url))
                       (fs/rmdir! (config/get-repo-dir url))
                       (state/delete-repo! repo))]
                       (state/delete-repo! repo))]
-    (if (config/local-db? url)
+    (if (or (config/local-db? url) (= url "local"))
       (p/let [_ (idb/clear-local-db! url)] ; clear file handles
       (p/let [_ (idb/clear-local-db! url)] ; clear file handles
         (delete-db-f))
         (delete-db-f))
       (util/delete (str config/api "repos/" id)
       (util/delete (str config/api "repos/" id)
@@ -696,7 +598,6 @@
     (db/remove-conn! url)
     (db/remove-conn! url)
     (db/clear-query-state!)
     (db/clear-query-state!)
     (-> (p/do! (db/remove-db! url)
     (-> (p/do! (db/remove-db! url)
-               (db/remove-files-db! url)
                (fs/rmdir! (config/get-repo-dir url))
                (fs/rmdir! (config/get-repo-dir url))
                (clone-and-load-db url))
                (clone-and-load-db url))
         (p/catch (fn [error]
         (p/catch (fn [error]

+ 3 - 3
src/main/frontend/handler/route.cljs

@@ -55,9 +55,9 @@
               (str (subs content 0 48) "...")
               (str (subs content 0 48) "...")
               content))
               content))
           "Page no longer exists!!")
           "Page no longer exists!!")
-        (let [page (db/pull [:page/name (string/lower-case name)])]
-          (or (:page/original-name page)
-              (:page/name page)
+        (let [page (db/pull [:block/name (string/lower-case name)])]
+          (or (:block/original-name page)
+              (:block/name page)
               "Logseq"))))
               "Logseq"))))
     :tag
     :tag
     (str "#"  (:name path-params))
     (str "#"  (:name path-params))

+ 14 - 13
src/main/frontend/handler/search.cljs

@@ -17,13 +17,11 @@
                  limit 20}
                  limit 20}
             :as opts}]
             :as opts}]
    (let [page-db-id (if (string? page-db-id)
    (let [page-db-id (if (string? page-db-id)
-                      (:db/id (db/entity repo [:page/name (string/lower-case page-db-id)]))
-                      page-db-id)]
+                      (:db/id (db/entity repo [:block/name (string/lower-case page-db-id)]))
+                      page-db-id)
+         opts (if page-db-id (assoc opts :page (str page-db-id)) opts)]
      (p/let [blocks (search/block-search repo q opts)]
      (p/let [blocks (search/block-search repo q opts)]
-      (let [blocks (if page-db-id
-                     (filter (fn [block] (= (get-in block [:block/page :db/id]) page-db-id)) blocks)
-                     blocks)
-            result (merge
+      (let [result (merge
                     {:blocks blocks
                     {:blocks blocks
                      :has-more? (= limit (count blocks))}
                      :has-more? (= limit (count blocks))}
                     (when-not page-db-id
                     (when-not page-db-id
@@ -33,13 +31,16 @@
         (swap! state/state assoc search-key result))))))
         (swap! state/state assoc search-key result))))))
 
 
 (defn clear-search!
 (defn clear-search!
-  []
-  (swap! state/state assoc
-         :search/result nil
-         :search/q ""
-         :search/mode :global)
-  (when-let [input (gdom/getElement "search-field")]
-    (gobj/set input "value" "")))
+  ([]
+   (clear-search! true))
+  ([clear-search-mode?]
+   (let [m (cond-> {:search/result nil
+                    :search/q ""}
+             clear-search-mode?
+             (assoc :search/mode :global))]
+     (swap! state/state merge m))
+   (when-let [input (gdom/getElement "search-field")]
+     (gobj/set input "value" ""))))
 
 
 (defn rebuild-indices!
 (defn rebuild-indices!
   []
   []

部分文件因为文件数量过多而无法显示