Ver Fonte

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

Peng Xiao há 3 anos atrás
pai
commit
6b5b1940d3
100 ficheiros alterados com 1258 adições e 817 exclusões
  1. 2 14
      .github/workflows/build.yml
  2. 93 0
      .github/workflows/db.yml
  3. 27 18
      .github/workflows/graph-parser.yml
  4. 2 2
      android/app/build.gradle
  5. 17 8
      bb.edn
  6. 1 1
      deps.edn
  7. 6 0
      deps/db/.carve/config.edn
  8. 6 0
      deps/db/.carve/ignore
  9. 52 0
      deps/db/README.md
  10. 35 0
      deps/db/bb.edn
  11. 7 0
      deps/db/deps.edn
  12. 8 0
      deps/db/package.json
  13. 3 3
      deps/db/src/logseq/db.cljs
  14. 1 1
      deps/db/src/logseq/db/default.cljs
  15. 4 3
      deps/db/src/logseq/db/rules.cljc
  16. 1 1
      deps/db/src/logseq/db/schema.cljs
  17. 43 0
      deps/db/yarn.lock
  18. 0 2
      deps/graph-parser/.carve/config.edn
  19. 23 0
      deps/graph-parser/bb.edn
  20. 5 4
      deps/graph-parser/deps.edn
  21. 1 1
      deps/graph-parser/package.json
  22. 37 45
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  23. 2 2
      deps/graph-parser/src/logseq/graph_parser/cli.cljs
  24. 4 2
      deps/graph-parser/test/logseq/graph_parser/nbb_test_runner.cljs
  25. 43 0
      deps/graph-parser/test/logseq/graph_parser_test.cljs
  26. 4 4
      deps/graph-parser/yarn.lock
  27. 9 14
      docs/dev-practices.md
  28. 4 4
      ios/App/App.xcodeproj/project.pbxproj
  29. 1 2
      package.json
  30. 0 1
      public/index.html
  31. 8 0
      resources/css/common.css
  32. 1 1
      resources/package.json
  33. 0 43
      scripts/carve.clj
  34. 0 54
      scripts/large_vars.clj
  35. 0 52
      scripts/lint_rules.clj
  36. 4 6
      scripts/src/logseq/tasks/dev.clj
  37. 0 47
      scripts/src/logseq/tasks/nbb.clj
  38. 5 4
      src/electron/electron/core.cljs
  39. 63 63
      src/electron/electron/fs_watcher.cljs
  40. 27 15
      src/electron/electron/handler.cljs
  41. 2 2
      src/electron/electron/state.cljs
  42. 6 0
      src/electron/electron/utils.cljs
  43. 20 15
      src/main/frontend/components/block.cljs
  44. 9 1
      src/main/frontend/components/block.css
  45. 16 13
      src/main/frontend/components/export.cljs
  46. 15 17
      src/main/frontend/components/header.cljs
  47. 14 22
      src/main/frontend/components/header.css
  48. 1 1
      src/main/frontend/components/onboarding/index.css
  49. 1 1
      src/main/frontend/components/page.cljs
  50. 5 2
      src/main/frontend/components/page.css
  51. 0 1
      src/main/frontend/components/plugins.cljs
  52. 0 1
      src/main/frontend/components/plugins.css
  53. 1 1
      src/main/frontend/components/repo.cljs
  54. 11 12
      src/main/frontend/components/right_sidebar.cljs
  55. 20 5
      src/main/frontend/components/settings.cljs
  56. 9 8
      src/main/frontend/components/sidebar.cljs
  57. 8 14
      src/main/frontend/components/sidebar.css
  58. 7 4
      src/main/frontend/date.cljs
  59. 3 3
      src/main/frontend/db.cljs
  60. 2 2
      src/main/frontend/db/conn.cljs
  61. 10 15
      src/main/frontend/db/model.cljs
  62. 1 1
      src/main/frontend/db/query_custom.cljs
  63. 1 1
      src/main/frontend/db/query_dsl.cljs
  64. 6 1
      src/main/frontend/db/query_react.cljs
  65. 33 12
      src/main/frontend/dicts.cljc
  66. 36 20
      src/main/frontend/extensions/code.css
  67. 11 1
      src/main/frontend/extensions/excalidraw.cljs
  68. 7 6
      src/main/frontend/extensions/html_parser.cljs
  69. 15 7
      src/main/frontend/format/block.cljs
  70. 21 2
      src/main/frontend/fs.cljs
  71. 2 0
      src/main/frontend/fs/bfs.cljs
  72. 3 1
      src/main/frontend/fs/capacitor_fs.cljs
  73. 4 2
      src/main/frontend/fs/node.cljs
  74. 2 1
      src/main/frontend/fs/protocol.cljs
  75. 10 12
      src/main/frontend/fs/watcher_handler.cljs
  76. 1 1
      src/main/frontend/handler.cljs
  77. 2 1
      src/main/frontend/handler/block.cljs
  78. 34 31
      src/main/frontend/handler/editor.cljs
  79. 24 1
      src/main/frontend/handler/events.cljs
  80. 40 16
      src/main/frontend/handler/export.cljs
  81. 8 5
      src/main/frontend/handler/file.cljs
  82. 1 1
      src/main/frontend/handler/graph.cljs
  83. 15 14
      src/main/frontend/handler/metadata.cljs
  84. 4 0
      src/main/frontend/handler/notification.cljs
  85. 12 10
      src/main/frontend/handler/page.cljs
  86. 22 19
      src/main/frontend/handler/plugin.cljs
  87. 20 17
      src/main/frontend/handler/repo.cljs
  88. 5 2
      src/main/frontend/handler/web/nfs.cljs
  89. 10 0
      src/main/frontend/mobile/mobile_bar.cljs
  90. 6 4
      src/main/frontend/modules/file/core.cljs
  91. 1 1
      src/main/frontend/modules/instrumentation/sentry.cljs
  92. 66 46
      src/main/frontend/modules/outliner/core.cljs
  93. 2 1
      src/main/frontend/modules/outliner/pipeline.cljs
  94. 5 7
      src/main/frontend/modules/shortcut/config.cljs
  95. 62 9
      src/main/frontend/modules/shortcut/dicts.cljc
  96. 1 1
      src/main/frontend/publishing.cljs
  97. 12 2
      src/main/frontend/state.cljs
  98. 6 2
      src/main/frontend/ui.cljs
  99. 1 1
      src/main/frontend/version.cljs
  100. 37 1
      src/test/frontend/modules/outliner/core_test.cljs

+ 2 - 14
.github/workflows/build.yml

@@ -52,11 +52,6 @@ jobs:
         with:
           cli: ${{ env.CLOJURE_VERSION }}
 
-      - name: Setup Babashka
-        uses: turtlequeue/[email protected]
-        with:
-          babashka-version: ${{ env.BABASHKA_VERSION }}
-
       - name: Clojure cache
         uses: actions/cache@v2
         id: clojure-deps
@@ -79,10 +74,6 @@ jobs:
           yarn cljs:test
           node static/tests.js
 
-      # In this job because it depends on an npm package
-      - name: Load nbb compatible namespaces
-        run: bb test:load-namespaces-with-nbb
-
   lint:
     runs-on: ubuntu-latest
 
@@ -110,17 +101,14 @@ jobs:
         run: clojure -M:clj-kondo --parallel --lint src
 
       - name: Carve lint for unused vars
-        run: scripts/carve.clj
+        run: bb lint:carve
 
       - name: Lint for vars that are too large
-        run: scripts/large_vars.clj
+        run: bb lint:large-vars
 
       - name: Lint invalid translation entries
         run: bb lang:invalid-translations
 
-      - name: Lint datalog rules
-        run: scripts/lint_rules.clj
-
   e2e-test:
     runs-on: ubuntu-latest
 

+ 93 - 0
.github/workflows/db.yml

@@ -0,0 +1,93 @@
+name: logseq/db CI
+
+on:
+  # Path filters ensure jobs only kick off if a change is made to db
+  push:
+    branches: [master]
+    paths:
+      - 'deps/db/**'
+      - '.github/workflows/db.yml'
+      - '!deps/db/**.md'
+  pull_request:
+    branches: [master]
+    paths:
+      - 'deps/db/**'
+      - '.github/workflows/db.yml'
+      - '!deps/db/**.md'
+
+defaults:
+  run:
+    working-directory: deps/db
+
+env:
+  CLOJURE_VERSION: '1.10.1.727'
+  # This is the same as 1.8.
+  JAVA_VERSION: '8'
+  # This is the latest node version we can run.
+  NODE_VERSION: '16'
+  BABASHKA_VERSION: '0.8.156'
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Set up Node
+        uses: actions/setup-node@v3
+        with:
+          node-version: ${{ env.NODE_VERSION }}
+          cache: 'yarn'
+          cache-dependency-path: deps/db/yarn.lock
+
+      - name: Set up Java
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: ${{ env.JAVA_VERSION }}
+
+      - name: Set up Clojure
+        uses: DeLaGuardo/setup-clojure@master
+        with:
+          cli: ${{ env.CLOJURE_VERSION }}
+          bb: ${{ env.BABASHKA_VERSION }}
+
+      - name: Fetch yarn deps
+        run: yarn install --frozen-lockfile
+
+      # In this job because it depends on an npm package
+      - name: Load namespaces into nbb-logseq
+        run: bb test:load-all-namespaces-with-nbb .
+
+  lint:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Set up Java
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: ${{ env.JAVA_VERSION }}
+
+      - name: Set up Clojure
+        uses: DeLaGuardo/setup-clojure@master
+        with:
+          cli: ${{ env.CLOJURE_VERSION }}
+          bb: ${{ env.BABASHKA_VERSION }}
+
+      - name: Run clj-kondo lint
+        run: clojure -M:clj-kondo --parallel --lint src
+
+      - name: Carve lint for unused vars
+        run: bb lint:carve
+
+      - name: Lint for vars that are too large
+        run: bb lint:large-vars
+
+      - name: Lint datalog rules
+        run: bb lint:rules

+ 27 - 18
.github/workflows/graph-parser.yml

@@ -1,26 +1,35 @@
-name: logseq graph-parser CI
+name: logseq/graph-parser CI
 
 on:
-  # Path filters ensure jobs only kick off if a change is made to graph-parser
+  # Path filters ensure jobs only kick off if a change is made to graph-parser or
+  # its local dependencies
   push:
     branches: [master]
     paths:
       - 'deps/graph-parser/**'
+      # db is a local dep that could break functionality in this lib and should trigger this
+      - 'deps/db/**'
+      - '.github/workflows/graph-parser.yml'
       - '!deps/graph-parser/**.md'
   pull_request:
     branches: [master]
     paths:
       - 'deps/graph-parser/**'
+      - 'deps/db/**'
+      - '.github/workflows/graph-parser.yml'
       - '!deps/graph-parser/**.md'
 
+defaults:
+  run:
+    working-directory: deps/graph-parser
+
 env:
   CLOJURE_VERSION: '1.10.1.727'
-  # setup-java@v2 dropped support for legacy Java version syntax.
   # This is the same as 1.8.
   JAVA_VERSION: '8'
   # This is the latest node version we can run.
   NODE_VERSION: '16'
-  BABASHKA_VERSION: '0.8.2'
+  BABASHKA_VERSION: '0.8.156'
 
 jobs:
   test:
@@ -28,17 +37,17 @@ jobs:
 
     steps:
       - name: Checkout
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
       - name: Set up Node
-        uses: actions/setup-node@v2
+        uses: actions/setup-node@v3
         with:
           node-version: ${{ env.NODE_VERSION }}
           cache: 'yarn'
           cache-dependency-path: deps/graph-parser/yarn.lock
 
       - name: Set up Java
-        uses: actions/setup-java@v2
+        uses: actions/setup-java@v3
         with:
           distribution: 'zulu'
           java-version: ${{ env.JAVA_VERSION }}
@@ -54,7 +63,7 @@ jobs:
           babashka-version: ${{ env.BABASHKA_VERSION }}
 
       - name: Clojure cache
-        uses: actions/cache@v2
+        uses: actions/cache@v3
         id: clojure-deps
         with:
           path: |
@@ -65,30 +74,30 @@ jobs:
 
       - name: Fetch Clojure deps
         if: steps.clojure-deps.outputs.cache-hit != 'true'
-        run: cd deps/graph-parser && clojure -A:test -P
+        run: clojure -A:test -P
 
       - name: Fetch yarn deps
-        run: cd deps/graph-parser && yarn install --frozen-lockfile
+        run: yarn install --frozen-lockfile
 
       - name: Run ClojureScript tests
-        run: cd deps/graph-parser && clojure -M:test
+        run: clojure -M:test
 
       - name: Run nbb-logseq tests
-        run: cd deps/graph-parser && yarn nbb-logseq -cp src:test -m logseq.graph-parser.nbb-test-runner/run-tests
+        run: yarn nbb-logseq -cp src:test:../db/src -m logseq.graph-parser.nbb-test-runner/run-tests
 
       # In this job because it depends on an npm package
       - name: Load namespaces into nbb-logseq
-        run: bb test:load-all-namespaces-with-nbb deps/graph-parser src
+        run: bb test:load-all-namespaces-with-nbb .
 
   lint:
     runs-on: ubuntu-latest
 
     steps:
       - name: Checkout
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
       - name: Set up Java
-        uses: actions/setup-java@v2
+        uses: actions/setup-java@v3
         with:
           distribution: 'zulu'
           java-version: ${{ env.JAVA_VERSION }}
@@ -104,10 +113,10 @@ jobs:
           babashka-version: ${{ env.BABASHKA_VERSION }}
 
       - name: Run clj-kondo lint
-        run: cd deps/graph-parser && clojure -M:clj-kondo --parallel --lint src test
+        run: clojure -M:clj-kondo --parallel --lint src test
 
       - name: Carve lint for unused vars
-        run: cd deps/graph-parser && ../../scripts/carve.clj
+        run: bb lint:carve
 
       - name: Lint for vars that are too large
-        run: scripts/large_vars.clj deps/graph-parser/src '{:max-lines-count 75}'
+        run: bb lint:large-vars

+ 2 - 2
android/app/build.gradle

@@ -6,8 +6,8 @@ android {
         applicationId "com.logseq.app"
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 25
-        versionName "0.7.1"
+        versionCode 28
+        versionName "0.7.4"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

+ 17 - 8
bb.edn

@@ -6,7 +6,7 @@
   logseq/bb-tasks
   #_{:local/root "../bb-tasks"}
   {:git/url "https://github.com/logseq/bb-tasks"
-   :git/sha "4b3e623fb475cacb992425aa9dac770d6dd63e38"}
+   :git/sha "abb32ccd26405d56fd28a29d56f3cb902b8c4334"}
   logseq/graph-parser
   {:local/root "deps/graph-parser"}}
  :pods
@@ -33,18 +33,18 @@
   dev:lint
   logseq.tasks.dev/lint
 
+  lint:large-vars
+  logseq.bb-tasks.lint.large-vars/-main
+
+  lint:carve
+  logseq.bb-tasks.lint.carve/-main
+
   nbb:watch
   logseq.bb-tasks.nbb.watch/watch
 
   nbb:portal-watch
   logseq.bb-tasks.nbb.watch/portal-watch
 
-  test:load-namespaces-with-nbb
-  logseq.tasks.nbb/load-compatible-namespaces
-
-  test:load-all-namespaces-with-nbb
-  logseq.tasks.nbb/load-all-namespaces
-
   lang:list
   logseq.tasks.lang/list-langs
 
@@ -58,4 +58,13 @@
   logseq.tasks.lang/invalid-translations
 
   file-sync:integration-tests
-  logseq.tasks.file-sync/integration-tests}}
+  logseq.tasks.file-sync/integration-tests}
+
+ :tasks/config
+ {:large-vars
+  ;; TODO: Get to a smaller max-lines-count
+  {:max-lines-count 100
+   ;; TODO: Address vars tagged with cleanup-todo. These
+   ;; are left mostly because they are not high priority
+   ;; or not well understood
+   :metadata-exceptions #{:large-vars/cleanup-todo}}}}

+ 1 - 1
deps.edn

@@ -47,5 +47,5 @@
                   :main-opts   ["-m" "shadow.cljs.devtools.cli"]}
 
            ;; Use :replace-deps for tools. See https://github.com/clj-kondo/clj-kondo/issues/1536#issuecomment-1013006889
-           :clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2022.05.28"}}
+           :clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2022.05.31"}}
                        :main-opts  ["-m" "clj-kondo.main"]}}}

+ 6 - 0
deps/db/.carve/config.edn

@@ -0,0 +1,6 @@
+{:paths ["src"]
+ :api-namespaces [
+                  ;; Some fns are used by frontend but not worth moving over yet
+                  logseq.db.schema
+                  ]
+ :report {:format :ignore}}

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

@@ -0,0 +1,6 @@
+;; API
+logseq.db/start-conn
+;; API
+logseq.db.rules/query-dsl-rules
+;; Internal API
+logseq.db.rules/rules

+ 52 - 0
deps/db/README.md

@@ -0,0 +1,52 @@
+## Description
+
+This library provides a minimal API for using a
+[datascript](https://github.com/tonsky/datascript) database from the Logseq app
+and the CLI. This library is compatible with ClojureScript and with
+[nbb-logseq](https://github.com/logseq/nbb-logseq) to respectively provide
+frontend and commandline functionality.
+
+## API
+
+This library is under the parent namespace `logseq.db`. This library provides
+two main namespaces, `logseq.db` and `logseq.db.rules`.
+
+## Usage
+
+See usage in `deps/graph-parser` and in the Logseq app.
+
+## Dev
+
+This follows the practices that [the Logseq frontend
+follows](/docs/dev-practices.md). Most of the same linters are used, with
+configurations that are specific to this library. See [this library's CI
+file](/.github/workflows/db.yml) for linting examples.
+
+### Setup
+
+To run linters, you'll want to install yarn dependencies once:
+```
+yarn install
+```
+
+This step is not needed if you're just running the application.
+
+## Linting
+
+### Datalog linting
+
+Our rules are linted through a script that also uses the datalog-parser. To run this linter:
+```
+bb lint:rules
+```
+
+
+### Managing dependencies
+
+The package.json dependencies are just for testing and should be updated if there is
+new behavior to test.
+
+The deps.edn dependecies are used by both ClojureScript and nbb-logseq. Their
+versions should be backwards compatible with each other with priority given to
+the frontend. _No new dependency_ should be introduced to this library without
+an understanding of the tradeoffs of adding this to nbb-logseq.

+ 35 - 0
deps/db/bb.edn

@@ -0,0 +1,35 @@
+{:paths ["src"]
+ :min-bb-version "0.8.156"
+ :deps
+ {logseq/bb-tasks
+  #_{:local/root "../../../bb-tasks"}
+  {:git/url "https://github.com/logseq/bb-tasks"
+   :git/sha "abb32ccd26405d56fd28a29d56f3cb902b8c4334"}}
+
+ :pods
+ {clj-kondo/clj-kondo {:version "2022.02.09"}}
+
+ :tasks
+ {test:load-all-namespaces-with-nbb
+  logseq.bb-tasks.nbb.test/load-all-namespaces
+
+  lint:large-vars
+  logseq.bb-tasks.lint.large-vars/-main
+
+  lint:carve
+  logseq.bb-tasks.lint.carve/-main
+
+  lint:rules
+  {:requires ([logseq.bb-tasks.lint.datalog :as datalog]
+              [logseq.db.rules :as rules])
+   :doc "Lint datalog rules for parsability and unbound variables"
+   :task (datalog/lint-rules
+          (into rules/rules
+                (-> rules/query-dsl-rules
+                    ;; TODO: Update linter to handle false positive on ?str-val
+                    (dissoc :property)
+                    vals)))}}
+
+ :tasks/config
+ {:large-vars
+  {:max-lines-count 30}}}

+ 7 - 0
deps/db/deps.edn

@@ -0,0 +1,7 @@
+{:deps
+ ;; External deps should be kept in sync with https://github.com/logseq/nbb-logseq/blob/main/bb.edn
+ {datascript/datascript {:mvn/version "1.3.8"}}
+ :aliases
+ {:clj-kondo
+  {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2022.05.31"}}
+   :main-opts  ["-m" "clj-kondo.main"]}}}

+ 8 - 0
deps/db/package.json

@@ -0,0 +1,8 @@
+{
+  "name": "@logseq/db",
+  "version": "1.0.0",
+  "private": true,
+  "devDependencies": {
+    "@logseq/nbb-logseq": "^0.5.103"
+  }
+}

+ 3 - 3
deps/graph-parser/src/logseq/graph_parser/db.cljs → deps/db/src/logseq/db.cljs

@@ -1,6 +1,6 @@
-(ns logseq.graph-parser.db
-  (:require [logseq.graph-parser.db.default :as default-db]
-            [logseq.graph-parser.db.schema :as db-schema]
+(ns logseq.db
+  (:require [logseq.db.default :as default-db]
+            [logseq.db.schema :as db-schema]
             [datascript.core :as d]))
 
 (defn start-conn

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

@@ -1,4 +1,4 @@
-(ns logseq.graph-parser.db.default
+(ns logseq.db.default
   (:require [clojure.string :as string]))
 
 (defonce built-in-pages-names

+ 4 - 3
src/main/frontend/db/rules.cljc → deps/db/src/logseq/db/rules.cljc

@@ -1,6 +1,7 @@
-(ns ^:bb-compatible ^:nbb-compatible frontend.db.rules)
+(ns ^:bb-compatible logseq.db.rules
+  "Datalog rules for use with logseq.db.schema")
 
-(def rules
+(def ^:large-vars/data-var rules
   ;; rule "parent" is optimized for child node -> parent node nesting queries
   '[[(parent ?p ?c)
      [?c :block/parent ?p]]
@@ -58,7 +59,7 @@
     ;;                      [?e ?a ?v]))]
     ])
 
-(def query-dsl-rules
+(def ^:large-vars/data-var query-dsl-rules
   "Rules used by frontend.db.query-dsl. The symbols ?b and ?p respectively refer
   to block and page. Do not alter them as they are programatically built by the
   query-dsl ns"

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

@@ -1,4 +1,4 @@
-(ns logseq.graph-parser.db.schema)
+(ns logseq.db.schema)
 
 (defonce version 1)
 (defonce ast-version 1)

+ 43 - 0
deps/db/yarn.lock

@@ -0,0 +1,43 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@logseq/nbb-logseq@^0.5.103":
+  version "0.5.103"
+  resolved "https://registry.yarnpkg.com/@logseq/nbb-logseq/-/nbb-logseq-0.5.103.tgz#1084380cd54c92ca8cc94a8934cc777206e45cc0"
+  integrity sha512-V9UW0XrCaaadHUc6/Hp9wfGpQqkzqzoqnDGeSVZkWR6l3QwyqGi9mkhnhVcfTwAvxIfOgrfz93GcaeepV4pYNA==
+  dependencies:
+    import-meta-resolve "^1.1.1"
+
+builtins@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/builtins/-/builtins-4.1.0.tgz#1edd016dd91ce771a1ed6fc3b2b71fb918953250"
+  integrity sha512-1bPRZQtmKaO6h7qV1YHXNtr6nCK28k0Zo95KM4dXfILcZZwoHJBN1m3lfLv9LPkcOZlrSr+J1bzMaZFO98Yq0w==
+  dependencies:
+    semver "^7.0.0"
+
+import-meta-resolve@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-1.1.1.tgz#244fd542fd1fae73550d4f8b3cde3bba1d7b2b18"
+  integrity sha512-JiTuIvVyPaUg11eTrNDx5bgQ/yMKMZffc7YSjvQeSMXy58DO2SQ8BtAf3xteZvmzvjYh14wnqNjL8XVeDy2o9A==
+  dependencies:
+    builtins "^4.0.0"
+
+lru-cache@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+  integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+  dependencies:
+    yallist "^4.0.0"
+
+semver@^7.0.0:
+  version "7.3.7"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
+  integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
+  dependencies:
+    lru-cache "^6.0.0"
+
+yallist@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+  integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==

+ 0 - 2
deps/graph-parser/.carve/config.edn

@@ -2,8 +2,6 @@
  :api-namespaces [
                   ;; carve doesn't detect nbb only usage
                   logseq.graph-parser.log
-                  ;; Used by logseq but not worth splitting up
-                  logseq.graph-parser.db.schema
                   ;; Used in tests
                   logseq.graph-parser.test.docs-graph-helper]
  :report {:format :ignore}}

+ 23 - 0
deps/graph-parser/bb.edn

@@ -0,0 +1,23 @@
+{:min-bb-version "0.8.156"
+ :deps
+ {logseq/bb-tasks
+  #_{:local/root "../../../bb-tasks"}
+  {:git/url "https://github.com/logseq/bb-tasks"
+   :git/sha "abb32ccd26405d56fd28a29d56f3cb902b8c4334"}}
+
+ :pods
+ {clj-kondo/clj-kondo {:version "2022.02.09"}}
+
+ :tasks
+ {test:load-all-namespaces-with-nbb
+  logseq.bb-tasks.nbb.test/load-all-namespaces
+
+  lint:large-vars
+  logseq.bb-tasks.lint.large-vars/-main
+
+  lint:carve
+  logseq.bb-tasks.lint.carve/-main}
+
+ :tasks/config
+ {:large-vars
+  {:max-lines-count 75}}}

+ 5 - 4
deps/graph-parser/deps.edn

@@ -1,10 +1,11 @@
 {:paths ["src"]
  :deps
- ;; Deps should be kept in sync with https://github.com/logseq/nbb-logseq/blob/main/bb.edn
- {datascript/datascript {:mvn/version "1.3.8"}
-  frankiesardo/linked {:mvn/version "1.3.0"}
+ ;; External deps should be kept in sync with https://github.com/logseq/nbb-logseq/blob/main/bb.edn
+ {frankiesardo/linked {:mvn/version "1.3.0"}
   com.andrewmcveigh/cljs-time {:git/url "https://github.com/logseq/cljs-time" ;; fork
                                :sha     "5704fbf48d3478eedcf24d458c8964b3c2fd59a9"}
+  ;; local dep
+  logseq/db {:local/root "../db"}
   ;; stubbed in nbb
   com.lambdaisland/glogi {:mvn/version "1.1.144"}
   ;; built in to nbb
@@ -19,5 +20,5 @@
                       org.clojure/clojurescript {:mvn/version "1.11.54"}}
          :main-opts ["-m" "cljs-test-runner.main"]}
 
-  :clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2022.05.28"}}
+  :clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2022.05.31"}}
               :main-opts  ["-m" "clj-kondo.main"]}}}

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

@@ -6,6 +6,6 @@
     "@logseq/nbb-logseq": "^0.5.103"
   },
   "dependencies": {
-    "mldoc": "^1.3.3"
+    "mldoc": "^1.3.9"
   }
 }

+ 37 - 45
deps/graph-parser/src/logseq/graph_parser/block.cljc → deps/graph-parser/src/logseq/graph_parser/block.cljs

@@ -1,7 +1,4 @@
 (ns logseq.graph-parser.block
-  ;; Disable clj linters since we don't support clj
-  #?(:clj {:clj-kondo/config {:linters {:unresolved-namespace {:level :off}
-                                        :unresolved-symbol {:level :off}}}})
   "Block related code needed for graph-parser"
   (:require [clojure.string :as string]
             [clojure.walk :as walk]
@@ -12,9 +9,7 @@
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.mldoc :as gp-mldoc]
-            [logseq.graph-parser.date-time-util :as date-time-util]
-            #?(:org.babashka/nbb [logseq.graph-parser.log :as log]
-               :default [lambdaisland.glogi :as log])))
+            [logseq.graph-parser.date-time-util :as date-time-util]))
 
 (defn heading-block?
   [block]
@@ -546,45 +541,42 @@
      :date-formatter and :db"
   [blocks content with-id? format {:keys [user-config] :as options}]
   {:pre [(seq blocks) (string? content) (boolean? with-id?) (contains? #{:markdown :org} format)]}
-  (try
-    (let [encoded-content (utf8/encode content)
-          [blocks body pre-block-properties]
-          (loop [headings []
-                 blocks (reverse blocks)
-                 timestamps {}
-                 properties {}
-                 body []]
-            (if (seq blocks)
-              (let [[block pos-meta] (first blocks)
-                    ;; fix start_pos
-                    pos-meta (assoc pos-meta :end_pos
-                                    (if (seq headings)
-                                      (get-in (last headings) [:meta :start_pos])
-                                      nil))]
-                (cond
-                  (paragraph-timestamp-block? block)
-                  (let [timestamps (extract-timestamps block)
-                        timestamps' (merge timestamps timestamps)]
-                    (recur headings (rest blocks) timestamps' properties body))
-
-                  (gp-property/properties-ast? block)
-                  (let [properties (extract-properties format (second block) user-config)]
-                    (recur headings (rest blocks) timestamps properties body))
-
-                  (heading-block? block)
-                  (let [block (construct-block block properties timestamps body encoded-content format pos-meta with-id? options)]
-                    (recur (conj headings block) (rest blocks) {} {} []))
-
-                  :else
-                  (recur headings (rest blocks) timestamps properties (conj body block))))
-              [(-> (reverse headings)
-                   sanity-blocks-data)
-               body
-               properties]))
-          result (with-pre-block-if-exists blocks body pre-block-properties encoded-content options)]
-      (map #(dissoc % :block/meta) result))
-    (catch :default e
-      (log/error :extract-blocks-failure e))))
+  (let [encoded-content (utf8/encode content)
+        [blocks body pre-block-properties]
+        (loop [headings []
+               blocks (reverse blocks)
+               timestamps {}
+               properties {}
+               body []]
+          (if (seq blocks)
+            (let [[block pos-meta] (first blocks)
+                  ;; fix start_pos
+                  pos-meta (assoc pos-meta :end_pos
+                                  (if (seq headings)
+                                    (get-in (last headings) [:meta :start_pos])
+                                    nil))]
+              (cond
+                (paragraph-timestamp-block? block)
+                (let [timestamps (extract-timestamps block)
+                      timestamps' (merge timestamps timestamps)]
+                  (recur headings (rest blocks) timestamps' properties body))
+
+                (gp-property/properties-ast? block)
+                (let [properties (extract-properties format (second block) user-config)]
+                  (recur headings (rest blocks) timestamps properties body))
+
+                (heading-block? block)
+                (let [block (construct-block block properties timestamps body encoded-content format pos-meta with-id? options)]
+                  (recur (conj headings block) (rest blocks) {} {} []))
+
+                :else
+                (recur headings (rest blocks) timestamps properties (conj body block))))
+            [(-> (reverse headings)
+                 sanity-blocks-data)
+             body
+             properties]))
+        result (with-pre-block-if-exists blocks body pre-block-properties encoded-content options)]
+    (map #(dissoc % :block/meta) result)))
 
 (defn with-parent-and-left
   [page-id blocks]

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

@@ -6,7 +6,7 @@
             [clojure.string :as string]
             [logseq.graph-parser :as graph-parser]
             [logseq.graph-parser.config :as gp-config]
-            [logseq.graph-parser.db :as gp-db]))
+            [logseq.db :as ldb]))
 
 (defn slurp
   "Return file contents like clojure.core/slurp"
@@ -60,7 +60,7 @@ TODO: Fail fast when process exits 1"
    (parse-graph dir {}))
   ([dir options]
    (let [files (or (:files options) (build-graph-files dir))
-         conn (gp-db/start-conn)
+         conn (ldb/start-conn)
          config (read-config dir)]
      (when-not (:files options) (println "Parsing" (count files) "files..."))
      (parse-files conn files (merge options {:config config}))

+ 4 - 2
deps/graph-parser/test/logseq/graph_parser/nbb_test_runner.cljs

@@ -6,7 +6,8 @@
             [logseq.graph-parser.block-test]
             [logseq.graph-parser.property-test]
             [logseq.graph-parser.extract-test]
-            [logseq.graph-parser.cli-test]))
+            [logseq.graph-parser.cli-test]
+            [logseq.graph-parser-test]))
 
 (defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m]
   (when-not (cljs.test/successful? m)
@@ -19,4 +20,5 @@
                'logseq.graph-parser.property-test
                'logseq.graph-parser.block-test
                'logseq.graph-parser.extract-test
-               'logseq.graph-parser.cli-test))
+               'logseq.graph-parser.cli-test
+               'logseq.graph-parser-test))

+ 43 - 0
deps/graph-parser/test/logseq/graph_parser_test.cljs

@@ -0,0 +1,43 @@
+(ns logseq.graph-parser-test
+  (:require [cljs.test :refer [deftest testing is]]
+            [logseq.graph-parser :as graph-parser]
+            [logseq.db :as ldb]
+            [logseq.graph-parser.block :as gp-block]
+            [datascript.core :as d]))
+
+(deftest parse-file
+  (testing "id properties"
+    (let [conn (ldb/start-conn)]
+      (graph-parser/parse-file conn "foo.md" "- id:: 628953c1-8d75-49fe-a648-f4c612109098" {})
+      (is (= [{:id "628953c1-8d75-49fe-a648-f4c612109098"}]
+             (->> (d/q '[:find (pull ?b [*])
+                         :in $
+                         :where [?b :block/content] [(missing? $ ?b :block/name)]]
+                       @conn)
+                  (map first)
+                  (map :block/properties)))
+          "id as text has correct :block/properties"))
+
+    (let [conn (ldb/start-conn)]
+      (graph-parser/parse-file conn "foo.md" "- id:: [[628953c1-8d75-49fe-a648-f4c612109098]]" {})
+      (is (= [{:id #{"628953c1-8d75-49fe-a648-f4c612109098"}}]
+             (->> (d/q '[:find (pull ?b [*])
+                         :in $
+                         :where [?b :block/content] [(missing? $ ?b :block/name)]]
+                       @conn)
+                  (map first)
+                  (map :block/properties)))
+          "id as linked ref has correct :block/properties")))
+
+  (testing "unexpected failure during block extraction"
+    (let [conn (ldb/start-conn)
+          deleted-page (atom nil)]
+      (with-redefs [gp-block/with-pre-block-if-exists (fn stub-failure [& _args]
+                                              (throw (js/Error "Testing unexpected failure")))]
+        (try
+          (graph-parser/parse-file conn "foo.md" "- id:: 628953c1-8d75-49fe-a648-f4c612109098"
+                                  {:delete-blocks-fn (fn [page _file]
+                                                       (reset! deleted-page page))})
+          (catch :default _)))
+      (is (= nil @deleted-page)
+          "Page should not be deleted when there is unexpected failure"))))

+ 4 - 4
deps/graph-parser/yarn.lock

@@ -177,10 +177,10 @@ mimic-fn@^2.0.0:
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
   integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
 
-mldoc@^1.3.3:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-1.3.3.tgz#b7f39b48eb0ef3558619d3e3522265977bd78fe3"
-  integrity sha512-TzW06GBltdKxwWAxOvflPmIVedu6bzl9T4YoYqnDUyZ3kELFMllEgiYCh65PPW3xsRMA/5OcRQqqGZGiKEJEug==
+mldoc@^1.3.9:
+  version "1.3.9"
+  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-1.3.9.tgz#9e45a25ba79596f3b0b0eace65f651a4c5a0c30a"
+  integrity sha512-UfqNuBphOj7paSBvozTdin5BLB5+W2tr2SGKTfp5eae6VQPx23oICx6RPQprft7KGFtn8T3rpM1YMUN8FaJLhg==
   dependencies:
     yargs "^12.0.2"
 

+ 9 - 14
docs/dev-practices.md

@@ -27,7 +27,7 @@ We use https://github.com/borkdude/carve to detect unused vars in our codebase.
 
 To run this linter:
 ```
-scripts/carve.clj
+bb lint:carve
 ```
 
 By default, the script runs in CI mode which prints unused vars if they are
@@ -35,7 +35,7 @@ found. The script can be run in an interactive mode which prompts for keeping
 (ignoring) an unused var or removing it. Run this mode with:
 
 ```
-scripts/carve.clj '{:interactive true}'
+bb lint:carve '{:interactive true}'
 ```
 
 When a var is ignored, it is added to `.carve/ignore`. Please add a comment for
@@ -46,24 +46,19 @@ why a var is ignored to help others understand why it's unused.
 Large vars have a lot of complexity and make it hard for the team to maintain
 and understand them. To run this linter:
 ```
-scripts/large_vars.clj
+bb lint:large-vars
 ```
 
 To configure the linter, see its `config` var.
 
 ### Datalog linting
 
-We use [datascript](https://github.com/tonsky/datascript)'s datalog to power our modeling and querying layer. Since datalog is concise, it is easy to write something invalid. To avoid typos and other preventable mistakes, we lint our queries and rules. Our queries are linted through clj-kondo and [datalog-parser](https://github.com/lambdaforge/datalog-parser). clj-kondo will error if it detects an invalid query. Our rules are linted through a script that also uses the datalog-parser. To run this linter:
-```
-scripts/lint_rules.clj
-```
-
-### Nbb compatible
-
-Namespaces have the metadata flag `^:nbb-compatible` indicate they are compatible with https://github.com/logseq/nbb-logseq. This compatibility is necessary in order for namespaces to be reused by the frontend and CLIs. To confirm these compatibilities, run:
-```
-bb test:load-namespaces-with-nbb
-```
+We use [datascript](https://github.com/tonsky/datascript)'s datalog to power our
+modeling and querying layer. Since datalog is concise, it is easy to write
+something invalid. To avoid typos and other preventable mistakes, we lint our
+queries and rules. Our queries are linted through clj-kondo and
+[datalog-parser](https://github.com/lambdaforge/datalog-parser). clj-kondo will
+error if it detects an invalid query.
 
 ## Testing
 

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

@@ -550,7 +550,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.7.1;
+				MARKETING_VERSION = 0.7.4;
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -576,7 +576,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.7.1;
+				MARKETING_VERSION = 0.7.4;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -601,7 +601,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.7.1;
+				MARKETING_VERSION = 0.7.4;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
@@ -628,7 +628,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.7.1;
+				MARKETING_VERSION = 0.7.4;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 1 - 2
package.json

@@ -5,7 +5,6 @@
     "main": "static/electron.js",
     "devDependencies": {
         "@capacitor/cli": "3.2.2",
-        "@logseq/nbb-logseq": "^0.5.103",
         "@playwright/test": "^1.19.2",
         "@tailwindcss/ui": "0.7.2",
         "@types/gulp": "^4.0.7",
@@ -55,7 +54,7 @@
         "cljs:electron-watch": "clojure -M:cljs watch app electron --config-merge '{:asset-path \"./js\"}'",
         "cljs:release": "clojure -M:cljs release app publishing electron",
         "cljs:release-electron": "clojure -M:cljs release app electron --debug && clojure -M:cljs release publishing",
-        "cljs:release-app": "clojure -M:cljs release app",
+        "cljs:release-app": "clojure -M:cljs release app --config-merge '{:compiler-options {:output-feature-set :es6}}'",
         "cljs:release-android-app": "clojure -M:cljs release app --config-merge '{:compiler-options {:output-feature-set :es6}}'",
         "cljs:test": "clojure -M:test compile test",
         "cljs:run-test": "node static/tests.js",

+ 0 - 1
public/index.html

@@ -46,7 +46,6 @@
 </script>
 <script defer src="/static/js/highlight.min.js"></script>
 <script defer src="/static/js/interact.min.js"></script>
-<script defer src="/static/js/lsplugin.core.js"></script>
 <script defer src="/static/js/main.js"></script>
 <script defer src="/static/js/code-editor.js"></script>
 <script defer src="/static/js/age-encryption.js"></script>

+ 8 - 0
resources/css/common.css

@@ -808,6 +808,14 @@ li p:last-child,
   border-right-color: var(--ls-border-color, #ccc);
 }
 
+i.ti {
+  /*
+  compensates the wrong top spacing in the iconfont.
+  See https://github.com/tabler/tabler-icons/issues/118/
+  */
+  transform: translateY(-1px);
+}
+
 .dnd-separator {
   border-bottom: 3px solid #ccc;
 }

+ 1 - 1
resources/package.json

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

+ 0 - 43
scripts/carve.clj

@@ -1,43 +0,0 @@
-#!/usr/bin/env bb
-;; This file is copied from
-;; https://github.com/borkdude/carve/blob/df552797a198b6701fb2d92390fce7c59205ea77/carve.clj
-;; and thus this file is under the same EPL license.
-;; The script is modified to run latest clj-kondo and carve versions and to add
-;; a more friendly commandline interface through -main
-
-(require '[babashka.pods :as pods])
-
-(pods/load-pod 'clj-kondo/clj-kondo "2022.02.09")
-(require '[pod.borkdude.clj-kondo :as clj-kondo])
-;; define clj-kondo.core ns which is used by carve
-(intern (create-ns 'clj-kondo.core) 'run! clj-kondo/run!)
-
-(require '[babashka.deps :as deps])
-(deps/add-deps '{:deps {borkdude/carve ;; {:local/root "."}
-                        {:git/url "https://github.com/borkdude/carve"
-                         :git/sha "df552797a198b6701fb2d92390fce7c59205ea77"}
-                        borkdude/spartan.spec {:git/url "https://github.com/borkdude/spartan.spec"
-                                               :sha "12947185b4f8b8ff8ee3bc0f19c98dbde54d4c90"}}})
-
-(require '[spartan.spec]) ;; defines clojure.spec
-
-(with-out-str ;; silence warnings about spartan.spec + with-gen
-  (binding [*err* *out*]
-    (require '[carve.api :as carve])))
-
-;; again to make clj-kondo happy
-(require '[carve.main])
-(require '[clojure.edn :as edn])
-
-(defn -main
-  "Wrapper around carve.main that defaults to .carve/config.edn and merges
-in an optional string of options"
-  [args]
-  (let [default-opts (slurp ".carve/config.edn")
-         opts (if-let [more-opts (first args)]
-                (pr-str (merge (select-keys (edn/read-string default-opts) [:paths :api-namespaces])
-                               (edn/read-string more-opts)))
-                default-opts)]
-    (apply carve.main/-main ["--opts" opts])))
-
-(-main *command-line-args*)

+ 0 - 54
scripts/large_vars.clj

@@ -1,54 +0,0 @@
-#!/usr/bin/env bb
-
-(ns large-vars
-  "This script detects vars that are too large and that make it difficult for
-  the team to maintain and understand them."
-  (:require [babashka.pods :as pods]
-            [clojure.pprint :as pprint]
-            [clojure.edn :as edn]
-            [clojure.set :as set]))
-
-(pods/load-pod 'clj-kondo/clj-kondo "2022.02.09")
-(require '[pod.borkdude.clj-kondo :as clj-kondo])
-
-(def default-config
-  ;; TODO: Discuss with team and agree on lower number
-  {:max-lines-count 100
-   ;; Vars with these metadata flags are allowed. Name should indicate the reason
-   ;; it is allowed
-   :metadata-exceptions #{::data-var
-                          ;; TODO: Address vars tagged with cleanup-todo. These
-                          ;; are left mostly because they are not high priority
-                          ;; or not well understood
-                          ::cleanup-todo}})
-
-(defn -main
-  [args]
-  (let [paths [(or (first args) "src")]
-        config (or (some->> (second args) edn/read-string (merge default-config))
-                   default-config)
-        {{:keys [var-definitions]} :analysis}
-        (clj-kondo/run!
-         {:lint paths
-          :config {:output {:analysis {:var-definitions {:meta true
-                                                         :lang :cljs}}}}})
-        vars (->> var-definitions
-                  (keep (fn [m]
-                          (let [lines-count (inc (- (:end-row m) (:row m)))]
-                            (when (and (> lines-count (:max-lines-count config))
-                                       (empty? (set/intersection (set (keys (:meta m)))
-                                                                 (:metadata-exceptions config))))
-                              {:var (:name m)
-                               :lines-count lines-count
-                               :filename (:filename m)}))))
-                  (sort-by :lines-count (fn [x y] (compare y x))))]
-    (if (seq vars)
-      (do
-        (println (format "\nThe following vars exceed the line count max of %s:"
-                         (:max-lines-count config)))
-        (pprint/print-table vars)
-        (System/exit 1))
-      (println "All vars are below the max size!"))))
-
-(when (= *file* (System/getProperty "babashka.file"))
-  (-main *command-line-args*))

+ 0 - 52
scripts/lint_rules.clj

@@ -1,52 +0,0 @@
-#!/usr/bin/env bb
-
-(require '[babashka.deps :as deps])
-(deps/add-deps '{:deps {me.tagaholic/dlint {:mvn/version "0.1.0"}
-                        io.lambdaforge/datalog-parser {:mvn/version "0.1.11"}}
-                 :paths ["src/main"]})
-
-(ns lint-rules
-  "Lint datalog rules for parse-ability and unbound variables"
-  (:require [datalog.parser.impl :as parser-impl]
-            [dlint.core :as dlint]
-            [frontend.db.rules :as rules]))
-
-(defn- lint-unbound-rule [rule]
-  (->> (dlint/lint [rule])
-       (keep
-        (fn [[k v]]
-          (when (seq v)
-            {:success false :name k :rule rule :unbound-vars v})))))
-
-(defn- lint-rule [rule]
-  (try (parser-impl/parse-rule rule)
-    {:success true :rule rule}
-    (catch Exception e
-      {:success false :rule rule :error (.getMessage e)})))
-
-(defn- collect-logseq-rules
-  "Collects logseq rules and prepares them for linting"
-  []
-  (into rules/rules
-        (-> rules/query-dsl-rules
-            ;; TODO: Update linter to handle false positive on ?str-val
-            (dissoc :property)
-            vals)))
-
-(defn -main [rules]
-  (let [invalid-unbound-rules (->> rules
-                                   (mapcat lint-unbound-rule)
-                                   (remove :success))
-        invalid-rules (->> rules
-                           (map lint-rule)
-                           (remove :success))
-        lint-results (concat invalid-unbound-rules invalid-rules)]
-    (if (seq lint-results)
-      (do
-        (println (count lint-results) "rules failed to lint:")
-        (println lint-results)
-        (System/exit 1))
-      (println (count rules) "datalog rules linted fine!"))))
-
-(when (= *file* (System/getProperty "babashka.file"))
-  (-main (collect-logseq-rules)))

+ 4 - 6
scripts/src/logseq/tasks/dev.clj

@@ -31,13 +31,11 @@
   - clj-kondo lint
   - carve lint for unused vars
   - lint for vars that are too large
-  - lint invalid translation entries
-  - Lint datalog rules"
+  - lint invalid translation entries"
   []
   (doseq [cmd ["clojure -M:clj-kondo --parallel --lint src --cache false"
-               "scripts/carve.clj"
-               "scripts/large_vars.clj"
-               "bb lang:invalid-translations"
-               "scripts/lint_rules.clj"]]
+               "bb lint:carve"
+               "bb lint:large-vars"
+               "bb lang:invalid-translations"]]
     (println cmd)
     (shell cmd)))

+ 0 - 47
scripts/src/logseq/tasks/nbb.clj

@@ -1,47 +0,0 @@
-(ns logseq.tasks.nbb
-  (:require [pod.borkdude.clj-kondo :as clj-kondo]
-            [clojure.string :as str]
-            [babashka.tasks :refer [shell]]))
-
-(defn- fetch-meta-namespaces
-  "Return namespaces with metadata"
-  [paths]
-  (let [{{:keys [namespace-definitions]} :analysis}
-        (clj-kondo/run!
-         {:lint paths
-          :config {:output {:analysis {:namespace-definitions {:meta true
-                                                               :lang :cljs}}}}})
-        matches (keep (fn [m]
-                        (when (:meta m)
-                          {:ns   (:name m)
-                           :meta (:meta m)}))
-                      namespace-definitions)]
-    matches))
-
-(defn- validate-namespaces
-  [namespaces classpath dir]
-  (assert (seq namespaces) "There must be some namespaces to check")
-  ;; distinct b/c sometimes namespaces are duplicated with .cljc analysis
-  (doseq [n (distinct namespaces)]
-    (println "Requiring" n "...")
-    (shell {:dir dir} "yarn nbb-logseq -cp" classpath "-e" (format "(require '[%s])" n)))
-  (println "Success!"))
-
-(defn load-compatible-namespaces
-  "Check nbb-compatible namespaces can be required by nbb-logseq"
-  []
-  (let [namespaces (map :ns
-                        (filter #(get-in % [:meta :nbb-compatible])
-                                (fetch-meta-namespaces ["src/main"])))]
-    (validate-namespaces namespaces "src/main" ".")))
-
-(defn load-all-namespaces
-  "Check all namespaces in source path(s) can be required by nbb-logseq"
-  [dir & paths]
-  (let [{{:keys [namespace-definitions]} :analysis}
-        (clj-kondo/run!
-         {:lint (map #(str dir "/" %) paths)
-          :config {:output {:analysis {:namespace-definitions {:lang :cljs}}}}})]
-    (validate-namespaces (map :name namespace-definitions)
-                         (str/join ":" paths)
-                         dir)))

+ 5 - 4
src/electron/electron/core.cljs

@@ -2,7 +2,7 @@
   (:require [electron.handler :as handler]
             [electron.search :as search]
             [electron.updater :refer [init-updater] :as updater]
-            [electron.utils :refer [*win mac? linux? dev? logger get-win-from-sender restore-user-fetch-agent]]
+            [electron.utils :refer [*win mac? linux? dev? logger get-win-from-sender restore-user-fetch-agent get-graph-name]]
             [electron.url :refer [logseq-url-handler]]
             [clojure.string :as string]
             [promesa.core :as p]
@@ -201,9 +201,10 @@
                        {:role "fileMenu"
                         :submenu [{:label "New Window"
                                    :click (fn []
-                                            (handler/open-new-window!))
-                                   :accelerator "CommandOrControl+N"
-                                   :acceleratorWorksWhenHidden false}
+                                            (p/let [graph-name (get-graph-name (state/get-graph-path))
+                                                    _ (handler/broadcast-persist-graph! graph-name)]
+                                              (handler/open-new-window!)))
+                                   :accelerator "CommandOrControl+N"}
                                   (if mac?
                                     {:role "close"}
                                     {:role "quit"})]}

+ 63 - 63
src/electron/electron/fs_watcher.cljs

@@ -4,8 +4,7 @@
             ["chokidar" :as watcher]
             [electron.utils :as utils]
             ["electron" :refer [app]]
-            [electron.window :as window]
-            ["path" :as path]))
+            [electron.window :as window]))
 
 ;; TODO: explore different solutions for different platforms
 ;; 1. https://github.com/Axosoft/nsfw
@@ -16,17 +15,22 @@
 
 (defonce file-watcher-chan "file-watcher")
 (defn- send-file-watcher! [dir type payload]
-  ;; Should only send to one window; then dbsync will do his job
-  ;; If no window is on this graph, just ignore
-  (let [sent? (some (fn [^js win]
-                      (when-not (.isDestroyed win)
-                        (.. win -webContents
-                            (send file-watcher-chan
-                                  (bean/->js {:type type :payload payload})))
-                        true)) ;; break some loop on success
-                    (window/get-graph-all-windows dir))]
-    (when-not sent? (prn "unhandled file event will cause uncatched file modifications!.
-                          target:" dir))))
+  (let [send-fn (fn [^js win]
+                  (when-not (.isDestroyed win)
+                    (.. win -webContents
+                        (send file-watcher-chan
+                              (bean/->js {:type type :payload payload})))
+                    true))
+        wins (window/get-graph-all-windows dir)]
+    (if (contains? #{"unlinkDir" "addDir"} type)
+      ;; notify every windows
+      (doseq [win wins] (send-fn win))
+
+      ;; Should only send to one window; then dbsync will do his job
+      ;; If no window is on this graph, just ignore
+      (let [sent? (some send-fn wins)]
+        (when-not sent? (prn "unhandled file event will cause uncatched file modifications!.
+                          target:" dir))))))
 
 (defn- publish-file-event!
   [dir path event]
@@ -45,58 +49,54 @@
 
 (defn watch-dir!
   "Watch a directory if no such file watcher exists"
-  [_win dir]
-  (when (and (fs/existsSync dir)
-             (not (get @*file-watcher dir)))
-    (let [watcher-opts (clj->js
-                        {:ignored (fn [path]
-                                    (utils/ignored-path? dir path))
-                         :ignoreInitial false
-                         :ignorePermissionErrors true
-                         :interval polling-interval
-                         :binaryInterval polling-interval
-                         :persistent true
-                         :disableGlobbing true
-                         :usePolling false
-                         :awaitWriteFinish true})
-          dir-watcher (.watch watcher dir watcher-opts)
-          watcher-del-f #(.close dir-watcher)
-          parent-dir (path/join dir "..")
-          parent-watcher (.watch watcher parent-dir watcher-opts)
-          parent-watcher-del-f #(.close parent-watcher)]
-      (swap! *file-watcher assoc dir
-             [dir-watcher watcher-del-f]
-             [parent-watcher parent-watcher-del-f])
-      ;; TODO: batch sender
-      (.on parent-watcher "unlinkDir"
-           (fn [path]
-             (when (= dir path)
-               (publish-file-event! dir dir "unlinkDir"))))
-      (.on parent-watcher "addDir"
-           (fn [path]
-             (when (= dir path)
-               (publish-file-event! dir dir "addDir"))))
-      (.on dir-watcher "add"
-           (fn [path]
-             (publish-file-event! dir path "add")))
-      (.on dir-watcher "change"
-           (fn [path]
-             (publish-file-event! dir path "change")))
-      (.on dir-watcher "unlink"
-           (fn [path]
-             (publish-file-event! dir path "unlink")))
-      (.on dir-watcher "error"
-           (fn [path]
-             (println "Watch error happened: "
-                      {:path path})))
+  [dir]
+  (when-not (get @*file-watcher dir)
+    (if (fs/existsSync dir)
+      (let [watcher-opts (clj->js
+                          {:ignored (fn [path]
+                                      (utils/ignored-path? dir path))
+                           :ignoreInitial false
+                           :ignorePermissionErrors true
+                           :interval polling-interval
+                           :binaryInterval polling-interval
+                           :persistent true
+                           :disableGlobbing true
+                           :usePolling false
+                           :awaitWriteFinish true})
+            dir-watcher (.watch watcher dir watcher-opts)
+            watcher-del-f #(.close dir-watcher)]
+        (swap! *file-watcher assoc dir [dir-watcher watcher-del-f])
+        ;; TODO: batch sender
+        (.on dir-watcher "unlinkDir"
+             (fn [path]
+               (when (= dir path)
+                 (publish-file-event! dir dir "unlinkDir"))))
+        (.on dir-watcher "addDir"
+             (fn [path]
+               (when (= dir path)
+                 (publish-file-event! dir dir "addDir"))))
+        (.on dir-watcher "add"
+             (fn [path]
+               (publish-file-event! dir path "add")))
+        (.on dir-watcher "change"
+             (fn [path]
+               (publish-file-event! dir path "change")))
+        (.on dir-watcher "unlink"
+             (fn [path]
+               (publish-file-event! dir path "unlink")))
+        (.on dir-watcher "error"
+             (fn [path]
+               (println "Watch error happened: "
+                        {:path path})))
 
-      ;; electron app extends `EventEmitter`
-      ;; TODO check: duplicated with the logic in "window-all-closed" ?
-      (.on app "quit" (fn []
-                        (watcher-del-f)
-                        (parent-watcher-del-f)))
+        ;; electron app extends `EventEmitter`
+        ;; TODO check: duplicated with the logic in "window-all-closed" ?
+        (.on app "quit" watcher-del-f)
 
-      true)))
+        true)
+      ;; retry if the `dir` not exists, which is useful when a graph's folder is
+      ;; back after refreshing the window
+      (js/setTimeout #(watch-dir! dir) 5000))))
 
 (defn close-watcher!
   "If no `dir` provided, close all watchers;

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

@@ -52,13 +52,16 @@
 (defmethod handle :unlink [_window [_ repo path]]
   (if (plugin/dotdir-file? path)
     (fs/unlinkSync path)
-    (let [file-name   (-> (string/replace path (str repo "/") "")
-                          (string/replace "/" "_")
-                          (string/replace "\\" "_"))
-          recycle-dir (str repo "/logseq/.recycle")
-          _           (fs-extra/ensureDirSync recycle-dir)
-          new-path    (str recycle-dir "/" file-name)]
-      (fs/renameSync path new-path))))
+    (try
+      (let [file-name   (-> (string/replace path (str repo "/") "")
+                           (string/replace "/" "_")
+                           (string/replace "\\" "_"))
+           recycle-dir (str repo "/logseq/.recycle")
+           _           (fs-extra/ensureDirSync recycle-dir)
+           new-path    (str recycle-dir "/" file-name)]
+        (fs/renameSync path new-path))
+      (catch :default _e
+        nil))))
 
 (defonce Diff (google-diff.))
 (defn string-some-deleted?
@@ -292,6 +295,11 @@
   (p/let [_ (utils/fetch url)]
     (utils/send-to-renderer win :notification {:type "success" :payload (str "Successfully: " url)})))
 
+(defmethod handle :httpFetchJSON [_win [_ url options]]
+  (p/let [res (utils/fetch url options)
+          json (.json res)]
+         json))
+
 (defmethod handle :getUserDefaultPlugins []
   (utils/get-ls-default-plugins))
 
@@ -329,7 +337,8 @@
 (defn set-current-graph!
   [window graph-path]
   (let [old-path (state/get-window-graph-path window)]
-    (when old-path (close-watcher-when-orphaned! window old-path))
+    (when (and old-path graph-path (not= old-path graph-path))
+      (close-watcher-when-orphaned! window old-path))
     (swap! state/state assoc :graph/current graph-path)
     (swap! state/state assoc-in [:window/graph window] graph-path)
     nil))
@@ -382,19 +391,21 @@
         windows (win/get-graph-all-windows dir)]
     (> (count windows) 1)))
 
-(defmethod handle :addDirWatcher [^js window [_ dir]]
+(defmethod handle :addDirWatcher [^js _window [_ dir]]
   ;; receive dir path (not repo / graph) from frontend
   ;; Windows on same dir share the same watcher
   ;; Only close file watcher when:
   ;;    1. there is no one window on the same dir
   ;;    2. reset file watcher to resend `add` event on window refreshing
   (when dir
-    ;; adding dir watcher when the window has watcher already - must be cmd + r refreshing
-    ;; maintain only one file watcher when multiple windows on the same dir
-    (close-watcher-when-orphaned! window dir)
-    (watcher/watch-dir! window dir)))
+    (watcher/watch-dir! dir)))
+
+(defmethod handle :unwatchDir [^js _window [_ dir]]
+  (when dir
+    (watcher/close-watcher! dir)))
 
 (defn open-new-window!
+  "Persist db first before calling! Or may break db persistency"
   []
   (let [win (win/create-main-window)]
     (win/on-close-actions! win close-watcher-when-orphaned!)
@@ -458,8 +469,9 @@
   (println "Error: no ipc handler for: " (bean/->js args)))
 
 (defn broadcast-persist-graph!
-  "Sends persist graph event to the renderer contains the target graph.
-   Returns a promise."
+  "Receive graph-name (not graph path)
+   Sends persist graph event to the renderer contains the target graph.
+   Returns a promise<void>."
   [graph-name]
   (p/create (fn [resolve _reject]
               (let [graph-path (utils/get-graph-dir graph-name)

+ 2 - 2
src/electron/electron/state.cljs

@@ -15,10 +15,10 @@
 
          ;; window -> current graph
          :window/graph {}
-         
+
          ;; job to do when persistGraph is done on renderer
          :window/once-persist-done nil
-         
+
          ;; job to do when graph is loaded on renderer
          :window/once-graph-ready nil}))
 

+ 6 - 0
src/electron/electron/utils.cljs

@@ -121,9 +121,15 @@
          (send (name kind) (bean/->js payload))))))
 
 (defn get-graph-dir
+  "required by all internal state in the electron section"
   [graph-name]
   (string/replace graph-name "logseq_local_" ""))
 
+(defn get-graph-name
+  "reversing `get-graph-dir`"
+  [graph-dir]
+  (str "logseq_local_" graph-dir))
+
 ;; Keep update with the normalization in main
 (defn normalize
   [s]

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

@@ -1563,7 +1563,9 @@
                      (if collapsed?
                        (editor-handler/expand-block! uuid)
                        (editor-handler/collapse-block! uuid))))}
-      [:span {:class (if control-show? "control-show cursor-pointer" "control-hide")}
+      [:span {:class (if (and control-show?
+                              (or collapsed?
+                                  (editor-handler/collapsable? uuid {:semantic? true}))) "control-show cursor-pointer" "control-hide")}
        (ui/rotating-arrow collapsed?)]]
      (let [bullet [:a {:on-click (fn [event]
                                    (bullet-on-click event block uuid))}
@@ -1925,8 +1927,14 @@
         (state/conj-selection-block! (gdom/getElement block-id) :down))
       (when (contains? #{1 0} button)
         (when-not (target-forbidden-edit? target)
-          (if (and shift? (state/get-selection-start-block))
+          (cond
+            (and shift? (state/get-selection-start-block))
             (editor-handler/highlight-selection-area! block-id)
+
+            shift?
+            (util/clear-selection!)
+
+            :else
             (do
               (editor-handler/clear-selection!)
               (editor-handler/unhighlight-blocks!)
@@ -2032,7 +2040,7 @@
        (merge attrs))
 
      [:<>
-      [:div.flex.flex-row.justify-between
+      [:div.flex.flex-row.justify-between.block-content-inner
        [:div.flex-1
         (cond
           (seq title)
@@ -2309,13 +2317,10 @@
   (editor-handler/unhighlight-blocks!))
 
 (defn- block-mouse-over
-  [uuid e *control-show? block-id doc-mode?]
+  [e *control-show? block-id doc-mode?]
   (when-not @*dragging?
     (util/stop e)
-    (when (or
-           (model/block-collapsed? uuid)
-           (editor-handler/collapsable? uuid {:semantic? true}))
-      (reset! *control-show? true))
+    (reset! *control-show? true)
     (when-let [parent (gdom/getElement block-id)]
       (let [node (.querySelector parent ".bullet-container")]
         (when doc-mode?
@@ -2477,7 +2482,7 @@
                        (block-handler/on-touch-end event block uuid *show-left-menu? *show-right-menu?))
        :on-touch-cancel block-handler/on-touch-cancel
        :on-mouse-over (fn [e]
-                        (block-mouse-over uuid e *control-show? block-id doc-mode?))
+                        (block-mouse-over e *control-show? block-id doc-mode?))
        :on-mouse-leave (fn [e]
                          (block-mouse-leave e *control-show? block-id doc-mode?))}
       (when (not slide?)
@@ -2794,12 +2799,12 @@
            [:span.opacity-60.text-sm.ml-2.results-count
             (str (count transformed-query-result) " results")]]
            ;;insert an "edit" button in the query view
-           [:a.opacity-70.hover:opacity-100.svg-small.inline 
-            {:on-mouse-down (fn [e]
-                              (util/stop e)
-                              (editor-handler/edit-block! current-block :max (:block/uuid current-block)))}
-            svg/edit]]
-          
+           (when-not built-in?
+            [:a.opacity-70.hover:opacity-100.svg-small.inline
+                      {:on-mouse-down (fn [e]
+                                        (util/stop e)
+                                        (editor-handler/edit-block! current-block :max (:block/uuid current-block)))}
+                      svg/edit])]
           (fn []
             [:div
              (when (and current-block (not view-f) (nil? table-view?))

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

@@ -227,7 +227,7 @@
   border-bottom-color: var(--ls-block-ref-link-text-color);
   cursor: alias;
   padding: 2px 0;
-  display: inline-block;
+  display: inherit;
 
   &:hover {
     color: var(--ls-link-text-hover-color);
@@ -235,6 +235,14 @@
 
   .block-content {
     cursor: inherit;
+
+    &-inner {
+      display: inherit;
+
+      > * {
+        display: inherit;
+      }
+    }
   }
 }
 

+ 16 - 13
src/main/frontend/components/export.cljs

@@ -1,10 +1,11 @@
 (ns frontend.components.export
-  (:require [rum.core :as rum]
-            [frontend.ui :as ui]
-            [frontend.util :as util]
+  (:require [frontend.context.i18n :refer [t]]
             [frontend.handler.export :as export]
+            [frontend.mobile.util :as mobile-util]
             [frontend.state :as state]
-            [frontend.context.i18n :refer [t]]))
+            [frontend.ui :as ui]
+            [frontend.util :as util]
+            [rum.core :as rum]))
 
 (rum/defc export
   []
@@ -17,21 +18,23 @@
         [:li.mb-4
          [:a.font-medium {:on-click #(export/export-repo-as-html! current-repo)}
           (t :export-public-pages)]])
-      [:li.mb-4
-       [:a.font-medium {:on-click #(export/export-repo-as-markdown! current-repo)}
-        (t :export-markdown)]]
-      [:li.mb-4
-       [:a.font-medium {:on-click #(export/export-repo-as-opml! current-repo)}
-        (t :export-opml)]]
+      (when-not (mobile-util/native-platform?)
+        [:li.mb-4
+         [:a.font-medium {:on-click #(export/export-repo-as-markdown! current-repo)}
+          (t :export-markdown)]]
+        [:li.mb-4
+         [:a.font-medium {:on-click #(export/export-repo-as-opml! current-repo)}
+          (t :export-opml)]])
       [:li.mb-4
        [:a.font-medium {:on-click #(export/export-repo-as-edn-v2! current-repo)}
         (t :export-edn)]]
       [:li.mb-4
        [:a.font-medium {:on-click #(export/export-repo-as-json-v2! current-repo)}
         (t :export-json)]]
-      [:li.mb-4
-       [:a.font-medium {:on-click #(export/export-repo-as-roam-json! current-repo)}
-        (t :export-roam-json)]]]
+      (when-not (mobile-util/native-platform?)
+       [:li.mb-4
+        [:a.font-medium {:on-click #(export/export-repo-as-roam-json! current-repo)}
+         (t :export-roam-json)]])]
      [:a#download-as-edn-v2.hidden]
      [:a#download-as-json-v2.hidden]
      [:a#download-as-roam-json.hidden]

+ 15 - 17
src/main/frontend/components/header.cljs

@@ -25,7 +25,7 @@
 
 (rum/defc home-button []
   (ui/with-shortcut :go/home "left"
-    [:a.button
+    [:button.button.icon.inline
      {:href     (rfe/href :home)
       :on-click #(do
                    (when (mobile-util/native-iphone?)
@@ -40,13 +40,13 @@
       (if (user-handler/logged-in?)
         (ui/dropdown-with-links
          (fn [{:keys [toggle-fn]}]
-           [:a.button
+           [:button.button
             {:on-click toggle-fn}
             [:span.text-sm.font-medium (user-handler/email)]])
          [{:title (t :logout)
            :options {:on-click user-handler/logout}}]
          {})
-        [:a.button.text-sm.font-medium.block {:on-click #(js/window.open config/LOGIN-URL)}
+        [:button.button.text-sm.font-medium.block {:on-click #(js/window.open config/LOGIN-URL)}
          [:span (t :login)]]))))
 
 (rum/defcs file-sync-remote-graphs <
@@ -90,10 +90,10 @@
         (ui/dropdown-with-links
          (fn [{:keys [toggle-fn]}]
            (if not-syncing?
-             [:a.button
+             [:button.button.icon.inline
               {:on-click toggle-fn}
               (ui/icon "cloud-off" {:style {:fontSize ui/icon-size}})]
-             [:a.button
+             [:button.button.icon.inline
               {:on-click toggle-fn}
               (ui/icon "cloud" {:style {:fontSize ui/icon-size}})]))
          (cond-> []
@@ -131,11 +131,9 @@
 (rum/defc left-menu-button < rum/reactive
   [{:keys [on-click]}]
   (ui/with-shortcut :ui/toggle-left-sidebar "bottom"
-    [:a#left-menu.cp__header-left-menu.button
-     {:on-click on-click
-      :style    {:margin-left 12}}
-     [:span.inner
-      (ui/icon "menu-2" {:style {:fontSize ui/icon-size}})]]))
+    [:button.#left-menu.cp__header-left-menu.button.icon
+     {:on-click on-click}
+      (ui/icon "menu-2" {:style {:fontSize ui/icon-size}})]))
 
 (rum/defc dropdown-menu < rum/reactive
   [{:keys [current-repo t]}]
@@ -144,7 +142,7 @@
                            (concat page-menu [{:hr true}]))]
     (ui/dropdown-with-links
      (fn [{:keys [toggle-fn]}]
-       [:a.button
+       [:button.button.icon
         {:on-click toggle-fn}
         (ui/icon "dots" {:style {:fontSize ui/icon-size}})])
      (->>
@@ -188,12 +186,12 @@
   [:div.flex.flex-row
 
    (ui/with-shortcut :go/backward "bottom"
-     [:a.it.navigation.nav-left.button
+     [:button.it.navigation.nav-left.button.icon
       {:title "Go back" :on-click #(js/window.history.back)}
       (ui/icon "arrow-left" {:style {:fontSize ui/icon-size}})])
 
    (ui/with-shortcut :go/forward "bottom"
-     [:a.it.navigation.nav-right.button
+     [:button.it.navigation.nav-right.button.icon
       {:title "Go forward" :on-click #(js/window.history.forward)}
       (ui/icon "arrow-right" {:style {:fontSize ui/icon-size}})])])
 
@@ -249,7 +247,7 @@
         [left-menu
          (when current-repo ;; this is for the Search button
            (ui/with-shortcut :go/search "right"
-             [:a.button#search-button
+             [:button.button.icon#search-button
               {:on-click #(do (when (or (mobile-util/native-android?)
                                         (mobile-util/native-iphone?))
                                 (state/set-left-sidebar-open! false))
@@ -259,7 +257,7 @@
         (if (state/home?)
           left-menu
           (ui/with-shortcut :go/backward "bottom"
-            [:a.it.navigation.nav-left.button
+            [:button.it.navigation.nav-left.button.icon
              {:title "Go back" :on-click #(js/window.history.back)}
              (ui/icon "chevron-left" {:style {:fontSize 25}})])))]
 
@@ -281,7 +279,7 @@
         (new-block-mode))
 
       (when show-open-folder?
-        [:a.text-sm.font-medium.button.add-graph-btn.flex.items-center
+        [:a.text-sm.font-medium.button.icon.add-graph-btn.flex.items-center
          {:on-click #(route-handler/redirect! {:to :repo-add})}
          (ui/icon "folder-plus")
          (when-not config/mobile?
@@ -289,7 +287,7 @@
             (t :on-boarding/add-graph)])])
 
       (when config/publishing?
-        [:a.text-sm.font-medium.button {:href (rfe/href :graph)}
+        [:button.text-sm.font-medium.button {:href (rfe/href :graph)}
          (t :graph)])
 
       (dropdown-menu {:t            t

+ 14 - 22
src/main/frontend/components/header.css

@@ -17,6 +17,7 @@
   white-space: nowrap;
 
   > .l {
+    @apply pl-4;
     width: var(--ls-left-sidebar-width);
     height: 100%;
     align-items: center;
@@ -24,8 +25,8 @@
   }
 
   > .r {
+    @apply pr-4;
     align-items: center;
-    padding-right: 0.5rem;
     flex: 1;
     justify-content: flex-end;
   }
@@ -41,10 +42,7 @@
     transform: scale(0.8);
   }
 
-  a.button {
-    margin: 0 4px;
-    height: 30px;
-    min-width: 30px;
+  .button {
     display: flex;
     align-items: center;
     justify-content: center;
@@ -55,6 +53,10 @@
     }
   }
 
+  .ui-items-container .button {
+    width: 2rem;
+  }
+
   svg.warning {
     transform: scale(0.6);
     color: red;
@@ -119,18 +121,6 @@
       top: 1px;
     }
   }
-
-  &-left-menu {
-    &.button {
-      margin: 0;
-      padding: 0;
-    }
-
-    > .inner {
-      line-height: 0;
-      padding: 3px;
-    }
-  }
 }
 
 .is-electron.is-mac .cp__header {
@@ -191,11 +181,9 @@
   height: 14px;
 }
 
-a.button {
-  padding: 0.25rem;
-  opacity: 0.6;
+.button {
+  @apply h-8 px-2.5 py-1 rounded-md opacity-60;
   display: block;
-  border-radius: 4px;
   user-select: none;
 
   &:hover, &.active {
@@ -212,6 +200,10 @@ a.button {
   }
 }
 
+.button.icon {
+  @apply w-8 h-8 text-lg p-1;
+}
+
 .is-mac.is-electron :is(.cp__header, .cp__right-sidebar-topbar) :is(button, .button, a) {
   cursor: default !important;
 }
@@ -257,7 +249,7 @@ html.is-native-ipad {
       display: flex;
     }
 
-    a.button {
+    .button {
       opacity: 1;
     }
   }

+ 1 - 1
src/main/frontend/components/onboarding/index.css

@@ -487,7 +487,7 @@ body[data-page=import] {
 
     .bd {
       ul a {
-        padding: 2px 4px !important;
+        padding: 2px 8px !important;
       }
     }
   }

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

@@ -892,7 +892,7 @@
              [:a.button.journal
               {:class    (util/classnames [{:active (boolean @*journal?)}])
                :on-click #(reset! *journal? (not @*journal?))}
-              (ui/icon "calendar")])]
+              (ui/icon "calendar" {:style {:fontSize ui/icon-size}})])]
 
            [:div.paginates
             [:span.flex.items-center

+ 5 - 2
src/main/frontend/components/page.css

@@ -119,6 +119,7 @@
       font-size: 16px;
       display: inline-block;
       position: relative;
+      transform: none;
     }
 
     .l {
@@ -133,9 +134,11 @@
     .r {
       font-size: 14px;
 
-      a.journal {
+      a.button {
         color: var(--ls-primary-text-color);
         margin-top: 1px;
+        height: unset;
+        padding: 4px;
 
         &.active {
           opacity: 1;
@@ -194,7 +197,7 @@
     display: flex;
     align-items: center;
     justify-content: space-between;
-    padding: 0 5px 0 0;
+    padding: 0 4px;
 
     > span {
       color: var(--ls-primary-text-color);

+ 0 - 1
src/main/frontend/components/plugins.cljs

@@ -372,7 +372,6 @@
       [:p [:label [:strong (t :type)]
            (ui/select [{:label "Disabled" :value "" :selected disabled?}
                        {:label "http" :value "http" :selected (= protocol "http")}
-                       {:label "https" :value "https" :selected (= protocol "https")}
                        {:label "socks5" :value "socks5" :selected (= protocol "socks5")}]
                       #(set-opts!
                          (assoc opts :protocol (if (= "disabled" (util/safe-lower-case %)) nil %))) nil)]]

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

@@ -70,7 +70,6 @@
       .ti {
         margin-right: 4px;
       }
-
       .ui__dropdown-trigger {
         .ti-circle {
           visibility: hidden;

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

@@ -148,7 +148,7 @@
             render-content (fn [{:keys [toggle-fn]}]
                              (let [repo-path (db/get-repo-name current-repo)
                                    short-repo-name (db/get-short-repo-name repo-path)]
-                               [:a.item.group.flex.items-center.px-1.py-2.text-sm.font-medium.rounded-md
+                               [:a.item.group.flex.items-center.p-2.text-sm.font-medium.rounded-md
                                 {:on-click (fn []
                                              (check-multiple-windows? state)
                                              (toggle-fn))

+ 11 - 12
src/main/frontend/components/right_sidebar.cljs

@@ -24,7 +24,7 @@
   []
   (when-not (util/mobile?)
     (ui/with-shortcut :ui/toggle-right-sidebar "left"
-      [:a.button.fade-link.toggle-right-sidebar
+      [:button.button.icon.fade-link.toggle-right-sidebar
        {:on-click ui-handler/toggle-right-sidebar!}
        (ui/icon "layout-sidebar-right" {:style {:fontSize "20px"}})])))
 
@@ -197,15 +197,15 @@
 
      (sidebar-resizer)
      [:div.cp__right-sidebar-scrollable
-      [:div.cp__right-sidebar-topbar.flex.flex-row.justify-between.items-center.pl-4.pr-2.h-12
-       [:div.cp__right-sidebar-settings.hide-scrollbar {:key "right-sidebar-settings"}
-        [:div.ml-4.text-sm
-         [:a.cp__right-sidebar-settings-btn {:on-click (fn [_e]
+      [:div.cp__right-sidebar-topbar.flex.flex-row.justify-between.items-center.px-2.h-12
+       [:div.cp__right-sidebar-settings.hide-scrollbar.gap-1 {:key "right-sidebar-settings"}
+        [:div.text-sm
+         [:button.button.cp__right-sidebar-settings-btn {:on-click (fn [_e]
                                                          (state/sidebar-add-block! repo "contents" :contents))}
           (t :right-side-bar/contents)]]
 
-        [:div.ml-4.text-sm
-         [:a.cp__right-sidebar-settings-btn {:on-click (fn []
+        [:div.text-sm
+         [:button.button.cp__right-sidebar-settings-btn {:on-click (fn []
                                                          (when-let [page (get-current-page)]
                                                            (state/sidebar-add-block!
                                                             repo
@@ -213,16 +213,15 @@
                                                             :page-graph)))}
           (t :right-side-bar/page)]]
 
-        [:div.ml-4.text-sm
-         [:a.cp__right-sidebar-settings-btn {:on-click (fn [_e]
+        [:div.text-sm
+         [:button.button.cp__right-sidebar-settings-btn {:on-click (fn [_e]
                                                          (state/sidebar-add-block! repo "help" :help))}
           (t :right-side-bar/help)]]]
 
-       [:div.flex.align-items {:style {:z-index 999
-                                       :margin-right 2}}
+       [:div
         (toggle)]]
 
-      [:.sidebar-item-list.flex-1.scrollbar-spacing
+      [:.sidebar-item-list.flex-1.scrollbar-spacing.flex.flex-col.gap-2
        (if @*anim-finished?
          (for [[idx [repo db-id block-type]] (medley/indexed blocks)]
            (rum/with-key

+ 20 - 5
src/main/frontend/components/settings.cljs

@@ -491,7 +491,15 @@
          {:style {:top -18 :left 10}}
          (ui/button (t :plugin/restart)
                     :on-click #(js/logseq.api.relaunch)
-                    :small? true :intent "logseq")]])]))
+           :small? true :intent "logseq")]])]))
+
+(rum/defc flashcards-enabled-switcher
+  [enable-flashcards?]
+  (ui/toggle enable-flashcards?
+             (fn []
+               (let [value (not enable-flashcards?)]
+                 (config-handler/set-config! :feature/enable-flashcards? value)))
+             true))
 
 (rum/defc user-proxy-settings
   [{:keys [protocol host port] :as agent-opts}]
@@ -508,6 +516,11 @@
    {:left-label (t :settings-page/plugin-system)
     :action (plugin-enabled-switcher t)}))
 
+(defn flashcards-switcher-row [enable-flashcards?]
+  (row-with-button-action
+   {:left-label (t :settings-page/enable-flashcards)
+    :action (flashcards-enabled-switcher enable-flashcards?)}))
+
 (defn https-user-agent-row [agent-opts]
   (row-with-button-action
    {:left-label (t :settings-page/network-proxy)
@@ -596,16 +609,18 @@
      :warning
      [:p (t :settings-page/git-confirm)])])
 
-(rum/defcs settings-advanced < rum/reactive
-  [_state]
+(rum/defc settings-advanced < rum/reactive
+  [current-repo]
   (let [instrument-disabled? (state/sub :instrument/disabled?)
         developer-mode? (state/sub [:ui/developer-mode?])
-        https-agent-opts (state/sub [:electron/user-cfgs :settings/agent])]
+        https-agent-opts (state/sub [:electron/user-cfgs :settings/agent])
+        enable-flashcards? (state/enable-flashcards? current-repo)]
     [:div.panel-wrap.is-advanced
      (when (and util/mac? (util/electron?)) (app-auto-update-row t))
      (usage-diagnostics-row t instrument-disabled?)
      (when-not (mobile-util/native-platform?) (developer-mode-row t developer-mode?))
      (when (util/electron?) (plugin-system-switcher-row))
+     (flashcards-switcher-row enable-flashcards?)
      (when (util/electron?) (https-user-agent-row https-agent-opts))
      (clear-cache-row t)
 
@@ -678,6 +693,6 @@
          (settings-git)
 
          :advanced
-         (settings-advanced)
+         (settings-advanced current-repo)
 
          nil)]]]))

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

@@ -223,11 +223,11 @@
                    (when (some (fn [sel] (boolean (.closest target sel)))
                                [".favorites .bd" ".recent .bd" ".dropdown-wrapper" ".nav-header"])
                      (close-modal-fn)))}
-     [:div.flex.flex-col.pb-4.wrap
-      [:nav.px-4.pt-1.space-y-1 {:aria-label "Sidebar"}
+     [:div.flex.flex-col.pb-4.wrap.gap-4
+      [:nav.px-4.flex.flex-col.gap-1 {:aria-label "Sidebar"}
        (repo/repos-dropdown)
 
-       [:div.nav-header
+       [:div.nav-header.flex.gap-1.flex-col
         (if-let [page (:page default-home)]
           (sidebar-item
             {:class            "home-nav"
@@ -245,8 +245,9 @@
              :on-click-handler route-handler/go-to-journals!
              :icon             "calendar"}))
 
-        [:div.flashcards-nav
-         (flashcards srs-open?)]
+        (when (state/enable-flashcards? (state/get-current-repo))
+          [:div.flashcards-nav
+           (flashcards srs-open?)])
 
         (sidebar-item
           {:class  "graph-view-nav"
@@ -340,7 +341,7 @@
 
       (when show-action-bar?
         (action-bar/action-bar))
-      
+
       [:div.cp__sidebar-main-content
        {:data-is-margin-less-pages margin-less-pages?
         :data-is-full-width        (or margin-less-pages?
@@ -351,11 +352,11 @@
 
        (mobile-bar)
        (footer/footer)
-       
+
        (when (and (not (mobile-util/native-platform?))
                   (contains? #{:page :home} route-name))
          (widgets/demo-graph-alert))
-       
+
        (cond
          (not indexeddb-support?)
          nil

+ 8 - 14
src/main/frontend/components/sidebar.css

@@ -87,24 +87,24 @@
   }
 
   .page-icon {
+    @apply mr-1 align-baseline;
+    width: 16px;
+    height: 16px;
     text-align: center;
     display: inline-block;
     line-height: 1em;
-    color: #aaa;
-    padding: 0 4px 0 8px;
+    color: var(--ls-icon-color);
   }
 
   a.item {
     user-select: none;
     transition: background-color .3s;
-    margin-bottom: 2px;
 
     > .ti {
       font-size: 16px;
       margin-right: 8px;
       opacity: .6;
       position: relative;
-      top: -1px;
     }
 
     &.active, &:active {
@@ -121,19 +121,17 @@
   }
 
   .nav-content-item {
-    margin-top: 14px;
-
     &-inner {
       border-radius: 8px;
     }
 
     .header {
+      @apply px-6 py-1;
       display: flex;
       justify-content: space-between;
       align-items: center;
       user-select: none;
       cursor: pointer;
-      padding: 4px 25px;
 
       > span {
         > a {
@@ -181,7 +179,6 @@
         > span {
           font-size: 11px;
           font-weight: 600;
-          padding-top: 2px;
         }
       }
     }
@@ -196,7 +193,7 @@
 
         a {
           width: 100%;
-          padding: 2px 18px;
+          padding: 2px 24px;
           display: block;
           text-overflow: ellipsis;
           overflow: hidden;
@@ -247,7 +244,7 @@
     background-color: var(--ls-secondary-background-color);
 
     > .wrap {
-      margin-top: 50px;
+      margin-top: 52px;
     }
 
     .new-page {
@@ -433,7 +430,6 @@ html[data-theme='dark'] {
 
   &-settings {
     @apply flex flex-row;
-    margin: -15px;
     margin-bottom: 0;
     margin-top: 0;
     overflow: auto;
@@ -478,9 +474,7 @@ html[data-theme='dark'] {
   }
 
   .sidebar-item {
-    padding-top: 24px;
-    padding-bottom: 24px;
-    margin-bottom: 8px;
+    @apply p-4;
 
     .close {
       transform: scale(0.8);

+ 7 - 4
src/main/frontend/date.cljs

@@ -21,8 +21,11 @@
 
 (defn journal-title-formatters
   []
-  (conj
-   #{"do MMM yyyy"
+  (->
+   (cons
+    (state/get-date-formatter)
+    (list
+     "do MMM yyyy"
      "do MMMM yyyy"
      "MMM do, yyyy"
      "MMMM do, yyyy"
@@ -48,8 +51,8 @@
      "yyyy-MM-dd EEEE"
      "yyyy_MM_dd"
      "yyyyMMdd"
-     "yyyy年MM月dd日"}
-   (state/get-date-formatter)))
+     "yyyy年MM月dd日"))
+   (distinct)))
 
 (defn get-date-time-string
   ([]

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

@@ -1,9 +1,9 @@
 (ns frontend.db
   (:require [clojure.core.async :as async]
             [datascript.core :as d]
-            [logseq.graph-parser.db.schema :as db-schema]
+            [logseq.db.schema :as db-schema]
             [frontend.db.conn :as conn]
-            [logseq.graph-parser.db.default :as default-db]
+            [logseq.db.default :as default-db]
             [frontend.db.model]
             [frontend.db.query-custom]
             [frontend.db.query-react]
@@ -66,7 +66,7 @@
  [frontend.db.query-react
   react-query custom-query-result-transform]
 
- [logseq.graph-parser.db.default built-in-pages-names built-in-pages])
+ [logseq.db.default built-in-pages-names built-in-pages])
 
 (defn get-schema-version [db]
   (d/q

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

@@ -7,7 +7,7 @@
             [frontend.config :as config]
             [frontend.util.text :as text-util]
             [logseq.graph-parser.text :as text]
-            [logseq.graph-parser.db :as gp-db]))
+            [logseq.db :as ldb]))
 
 (defonce conns (atom {}))
 
@@ -71,7 +71,7 @@
    (start! repo {}))
   ([repo {:keys [listen-handler]}]
    (let [db-name (datascript-db repo)
-         db-conn (gp-db/start-conn)]
+         db-conn (ldb/start-conn)]
      (swap! conns assoc db-name db-conn)
      (when listen-handler
        (listen-handler repo)))))

+ 10 - 15
src/main/frontend/db/model.cljs

@@ -8,15 +8,15 @@
             [datascript.core :as d]
             [frontend.config :as config]
             [frontend.date :as date]
-            [logseq.graph-parser.db.schema :as db-schema]
+            [logseq.db.schema :as db-schema]
             [frontend.db.conn :as conn]
             [frontend.db.react :as react]
             [frontend.db.utils :as db-utils]
             [frontend.state :as state]
             [frontend.util :as util :refer [react]]
             [logseq.graph-parser.util :as gp-util]
-            [frontend.db.rules :refer [rules]]
-            [logseq.graph-parser.db.default :as default-db]
+            [logseq.db.rules :refer [rules]]
+            [logseq.db.default :as default-db]
             [frontend.util.drawer :as drawer]))
 
 ;; lazy loading
@@ -802,13 +802,6 @@
     (->> (tree-seq map? (fn [x] [(:block/parent x)]) block)
          (some util/collapsed?))))
 
-(defn block-collapsed?
-  ([block-id]
-   (block-collapsed? (state/get-current-repo) block-id))
-  ([repo block-id]
-   (when-let [block (db-utils/entity repo [:block/uuid block-id])]
-     (util/collapsed? block))))
-
 (defn get-block-page
   [repo block-id]
   (when-let [block (db-utils/entity repo [:block/uuid block-id])]
@@ -1457,11 +1450,13 @@
       (mapv (fn [page] [:db.fn/retractEntity [:block/name page]]) (map util/page-name-sanity-lc pages)))))
 
 (defn set-file-content!
-  [repo path content]
-  (when (and repo path)
-    (let [tx-data {:file/path path
-                   :file/content content}]
-      (db-utils/transact! repo [tx-data] {:skip-refresh? true}))))
+  ([repo path content]
+   (set-file-content! repo path content {}))
+  ([repo path content opts]
+   (when (and repo path)
+     (let [tx-data {:file/path path
+                    :file/content content}]
+       (db-utils/transact! repo [tx-data] (merge opts {:skip-refresh? true}))))))
 
 (defn get-pre-block
   [repo page-id]

+ 1 - 1
src/main/frontend/db/query_custom.cljs

@@ -4,7 +4,7 @@
             [frontend.db.query-react :as query-react]
             [frontend.db.query-dsl :as query-dsl]
             [frontend.db.model :as model]
-            [frontend.db.rules :as rules]
+            [logseq.db.rules :as rules]
             [frontend.util.datalog :as datalog-util]
             [clojure.walk :as walk]))
 

+ 1 - 1
src/main/frontend/db/query_dsl.cljs

@@ -11,7 +11,7 @@
             [frontend.db.model :as model]
             [frontend.db.query-react :as query-react]
             [frontend.db.utils :as db-utils]
-            [frontend.db.rules :as rules]
+            [logseq.db.rules :as rules]
             [frontend.template :as template]
             [logseq.graph-parser.text :as text]
             [frontend.util.text :as text-util]

+ 6 - 1
src/main/frontend/db/query_react.cljs

@@ -114,6 +114,11 @@
                page-ref (string/lower-case page-ref)]
            (list 'contains? sym (text/page-ref-un-brackets! page-ref)))
 
+         (and (vector? f)
+              (= (first f) 'page-property)
+              (keyword? (util/nth-safe f 2)))
+         (update f 2 (fn [k] (keyword (string/replace (name k) "_" "-"))))
+
          :else
          f)) query)))
 
@@ -132,4 +137,4 @@
           k [:custom query']]
       (pprint "inputs (post-resolution):" resolved-inputs)
       (pprint "query-opts:" query-opts)
-      (apply react/q repo k query-opts query inputs))))
+      (apply react/q repo k query-opts query inputs))))

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

@@ -183,6 +183,7 @@
         :settings-page/tab-version-control "Version control"
         :settings-page/tab-advanced "Advanced"
         :settings-page/plugin-system "Plug-in system"
+        :settings-page/enable-flashcards "Flashcards"
         :settings-page/network-proxy "Network proxy"
         :logseq "Logseq"
         :on "ON"
@@ -1566,9 +1567,9 @@
         :file-sync/graph-deleted "El gráfico remoto actual se ha eliminado"}
 
    :nb-NO {:tutorial/text #?(:cljs (rc/inline "tutorial-no.md")
-                                :default "tutorial-no.md")
+                             :default "tutorial-no.md")
            :tutorial/dummy-notes #?(:cljs (rc/inline "dummy-notes-no.md")
-                                       :default "dummy-notes-no.md")
+                                    :default "dummy-notes-no.md")
            :on-boarding/demo-graph "Dette er en demo graf, endringer vil ikke bli lagret før du åpner en lokal mappe."
            :on-boarding/add-graph "Legg til en graf"
            :on-boarding/open-local-dir "Åpne en lokal mappe"
@@ -1625,11 +1626,11 @@
            :right-side-bar/favorites "Favoritter"
            :right-side-bar/page-graph "Sidegraf"
            :right-side-bar/block-ref "Blokkreferanse"
-           :right-side-bar/graph-view "Graph view"
-           :right-side-bar/all-pages "All pages"
+           :right-side-bar/graph-view "Grafvisning"
+           :right-side-bar/all-pages "Alle sider"
            :right-side-bar/flashcards "Flashcards"
-           :right-side-bar/new-page "New page"
-           :left-side-bar/journals "Journals"
+           :right-side-bar/new-page "Ny side"
+           :left-side-bar/journals "Dagbøker"
            :left-side-bar/new-page "Ny side"
            :left-side-bar/nav-favorites "Favoritter"
            :left-side-bar/nav-shortcuts "Snarveier"
@@ -1866,8 +1867,28 @@
            :select.graph/empty-placeholder-description "Ingen grafer matcher. Vil du legge til en ny?"
            :select.graph/add-graph "Ja, legg til en ny graf"
 
-           :file-sync/other-user-graph "Nåværende lokal graf er bundet til annen brukers fjernkontroll. Så kan ikke begynne å synkronisere."
-           :file-sync/graph-deleted "Nåværende fjernkontrollen er slettet"}
+           :file-sync/other-user-graph "Nåværende lokal graf er bundet til annen brukers fjerngraf. Kan ikke begynne å synkronisere."
+           :file-sync/graph-deleted "Nåværende fjerngraf er slettet"
+           :host "Vert"
+           :port "Port"
+           :re-index-discard-unsaved-changes-warning "Reindeksering vil forkaste nåværende graf, og deretter prosessere alle filene på nytt slik de er på disk akkurat nå. Du vil miste ulagrede endringer, og det kan ta litt tid. Forsette?"
+           :re-index-multiple-windows-warning "Du må lukke de andre vinduene før du kan reindeksere denne grafen"
+           :save "Lagrer..."
+           :settings-of-plugins "Innstillinger for utvidelser"
+           :sync-from-local-changes-detected "Oppfrisk oppdager og prosesserer filer på disk som er modifiserte og avviker fra sideinnholdet som vises i Logseq. Fortsett?"
+           :type "Type"
+           :graph/persist "Logeq synkroniserer intern status, vennligst vent i flere sekunder."
+           :graph/persist-error "Intern status synk feilet"
+           :graph/save "Lagrer..."
+           :graph/save-error "Lagring feilet"
+           :graph/save-success "Lagring vellykket"
+           :page/copy-page-url "Kopier side URL"
+           :page/file-sync-versions "Versjoner av siden"
+           :page/open-backup-directory "Åpne mappe med sidens sikkerhetskopier"
+           :plugin/not-installed "Ikke installert"
+           :settings-page/edit-export-css "Rediger export.css"
+           :settings-page/network-proxy "Nettverksproxy"
+           :settings-page/plugin-system "System for utvidelser"}
 
    :pt-BR {:on-boarding/demo-graph "Esse é um gráfico de demonstração, mudanças não serão salvas enquanto uma pasta local não for aberta."
            :on-boarding/add-graph "Adicionar gráfico"
@@ -2180,7 +2201,7 @@
 
            :file-sync/other-user-graph "O gráfico local atual é obrigado ao gráfico remoto de outro usuário. Portanto, não consigo iniciar a sincronização."
            :file-sync/graph-deleted "O gráfico remoto atual foi excluído"
-           
+
            :page/copy-page-url "Copiar URL da página"
            :page/file-sync-versions "Versões da página"
            :plugin/not-installed "Não instalado"
@@ -3732,7 +3753,7 @@
 
         :file-sync/other-user-graph "Geçerli yerel grafik, diğer kullanıcının uzak grafiğine bağlıdır. Bu yüzden senkronizasyon başlatılamıyor."
         :file-sync/graph-deleted "Geçerli uzak grafik silindi"}
-   
+
    :ko {:tutorial/text #?(:cljs (rc/inline "tutorial-ko.md")
                           :default "tutorial-ko.md")
         :tutorial/dummy-notes #?(:cljs (rc/inline "dummy-notes-ko.md")
@@ -3829,7 +3850,7 @@
         :page/show-journals "일지 보기"
         :page/show-name "페이지 이름 보기"
         :page/hide-name "페이지 이름 숨기기"
-        :block/name "페이지 일므"
+        :block/name "페이지 이름"
         :page/last-modified "마지막 편집 시간:"
         :page/new-title "새 문서의 제목이 무엇입니까?"
         :page/earlier "이전"
@@ -4059,7 +4080,7 @@
         :file-sync/other-user-graph "현재 로컬 그래프가 다른 유저의 리모트 그래프와 충돌합니다. 동기화를 시작할 수 없습니다."
         :file-sync/graph-deleted "현재 리모트 그래프를 삭제했습니다."
         }
-     
+
      :tongue/fallback :en})
 
 (def languages [{:label "English" :value :en}

+ 36 - 20
src/main/frontend/extensions/code.css

@@ -13,16 +13,21 @@
     position: absolute;
     width: fit-content;
     /* height:2rem; */
-    order:3;
-    display: flex;
+    order: 3;
     left: auto;
     right: 0;
     /* margin-top: 4px; */
     z-index: 9999;
-  }
+    display: none;
+    padding: 4px 6px;
+    line-height: 1em;
+    opacity: .8;
 
-  .code-editor {
-      margin-top: 28px;
+    &:hover {
+      cursor: pointer;
+      opacity: 1;
+      user-select: none;
+    }
   }
 
   &-calc {
@@ -37,25 +42,36 @@
       font-family: Fira Code, Monaco, Menlo, Consolas, 'COURIER NEW', monospace;
     }
   }
+
+  @screen md {
+    &-lang {
+      display: flex;
+    }
+  }
 }
 
 .CodeMirror {
-    width: 100%;
-    font-family: Fira Code, Monaco, Menlo, Consolas, 'COURIER NEW', monospace;
-    border-radius: 2px;
-    line-height: 1.45em;
-
-    &:not(.CodeMirror-focused) {
-        .CodeMirror-activeline-background {
-            background: unset !important;
-        }
-    }
+  width: 100%;
+  font-family: Fira Code, Monaco, Menlo, Consolas, 'COURIER NEW', monospace;
+  border-radius: 2px;
+  line-height: 1.45em;
 
-    pre.CodeMirror-line {
-        box-shadow: none !important;
-    }
+  &-scroll {
+    padding-top: 18px;
+    padding-bottom: 62px;
+  }
 
-    .CodeMirror-hscrollbar {
-        cursor: pointer;
+  &:not(.CodeMirror-focused) {
+    .CodeMirror-activeline-background {
+      background: unset !important;
     }
+  }
+
+  pre.CodeMirror-line {
+    box-shadow: none !important;
+  }
+
+  .CodeMirror-hscrollbar {
+    cursor: pointer;
+  }
 }

+ 11 - 1
src/main/frontend/extensions/excalidraw.cljs

@@ -33,6 +33,15 @@
         (recur (.-parentNode el))))
     state))
 
+(defn excalidraw-theme [ui-theme]
+  ;; One of these constants are meant to be used as a 'theme' argument for escalidraw:
+  ;; https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L75
+  ;; But they are missing from the prod build of excalidraw we're using.
+  ;; They map to "light" and "dark", happens that :ui/theme uses same values, so we are safe to pass it directly, for now.
+  ;; Escalidraw may migrate to different values for these constants in future versions,
+  ;; so, in order to not watch out for it every time we bump a new version we better migrate to constants as soon as they appear in a prod build.
+  ui-theme)
+
 (rum/defcs draw-inner < rum/reactive
   (rum/local 800 ::draw-width)
   (rum/local true ::zen-mode?)
@@ -89,7 +98,8 @@
            :zen-mode-enabled @*zen-mode?
            :view-mode-enabled @*view-mode?
            :grid-mode-enabled @*grid-mode?
-           :initial-data data}))]])))
+           :initial-data data
+           :theme (excalidraw-theme (state/sub :ui/theme))}))]])))
 
 (rum/defc draw
   [option]

+ 7 - 6
src/main/frontend/extensions/html_parser.cljs

@@ -129,12 +129,13 @@
                            :a (let [href (:href attrs)
                                     label (or (map-join children) "")
                                     has-img-tag? (util/safe-re-find #"\[:img" (str x))]
-                                (if has-img-tag?
-                                  (export-hiccup x)
-                                  (case format
-                                    :markdown (util/format "[%s](%s)" label href)
-                                    :org (util/format "[[%s][%s]]" href label)
-                                    nil)))
+                                (when-not (string/blank? href)
+                                  (if has-img-tag?
+                                    (export-hiccup x)
+                                    (case format
+                                      :markdown (util/format "[%s](%s)" label href)
+                                      :org (util/format "[[%s][%s]]" href label)
+                                      nil))))
                            :img (let [src (:src attrs)
                                       alt (or (:alt attrs) "")]
                                   (case format

+ 15 - 7
src/main/frontend/format/block.cljs

@@ -6,19 +6,27 @@
             [frontend.db :as db]
             [frontend.format :as format]
             [frontend.state :as state]
+            [frontend.handler.notification :as notification]
+            ["@sentry/react" :as Sentry]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.property :as gp-property]
             [logseq.graph-parser.mldoc :as gp-mldoc]))
 
 (defn extract-blocks
-  "Wrapper around logseq.graph-parser.block/extract-blocks that adds in system state"
+  "Wrapper around logseq.graph-parser.block/extract-blocks that adds in system state
+and handles unexpected failure."
   [blocks content with-id? format]
-  (gp-block/extract-blocks blocks content with-id? format
-                           {:user-config (state/get-config)
-                            :block-pattern (config/get-block-pattern format)
-                            :supported-formats (gp-config/supported-formats)
-                            :db (db/get-db (state/get-current-repo))
-                            :date-formatter (state/get-date-formatter)}))
+  (try
+    (gp-block/extract-blocks blocks content with-id? format
+                             {:user-config (state/get-config)
+                              :block-pattern (config/get-block-pattern format)
+                              :supported-formats (gp-config/supported-formats)
+                              :db (db/get-db (state/get-current-repo))
+                              :date-formatter (state/get-date-formatter)})
+    (catch :default e
+      (Sentry/captureException e)
+      (notification/show! "An unexpected error occurred during block extraction." :error)
+      [])))
 
 (defn page-name->map
   "Wrapper around logseq.graph-parser.block/page-name->map that adds in db"

+ 21 - 2
src/main/frontend/fs.cljs

@@ -12,7 +12,8 @@
             [promesa.core :as p]
             [frontend.db :as db]
             [clojure.string :as string]
-            [frontend.encrypt :as encrypt]))
+            [frontend.encrypt :as encrypt]
+            [frontend.state :as state]))
 
 (defonce nfs-record (nfs/->Nfs))
 (defonce bfs-record (bfs/->Bfs))
@@ -79,7 +80,17 @@
       (p/let [md-or-org? (contains? #{"md" "markdown" "org"} (util/get-file-ext path))
               content (if-not md-or-org? content (encrypt/encrypt content))]
         (->
-         (p/let [_ (protocol/write-file! (get-fs dir) repo dir path content opts)]
+         (p/let [opts (assoc opts
+                             :error-handler
+                             (fn [error]
+                               (state/pub-event! [:instrument {:type :write-file/failed
+                                                               :payload {:fs (type fs-record)
+                                                                         :user-agent (when js/navigator js/navigator.userAgent)
+                                                                         :path path
+                                                                         :content-length (count content)
+                                                                         :error-str (str error)
+                                                                         :error error}}])))
+                 _ (protocol/write-file! (get-fs dir) repo dir path content opts)]
            (when (= bfs-record fs-record)
              (db/set-file-last-modified-at! repo (config/get-file-path repo path) (js/Date.))))
          (p/catch (fn [error]
@@ -156,6 +167,10 @@
   [dir]
   (protocol/watch-dir! (get-record) dir))
 
+(defn unwatch-dir!
+  [dir]
+  (protocol/unwatch-dir! (get-record) dir))
+
 (defn mkdir-if-not-exists
   [dir]
   (->
@@ -189,3 +204,7 @@
    (stat dir path)
    (fn [_stat] true)
    (fn [_e] false)))
+
+(defn dir-exists?
+  [dir]
+  (file-exists? dir ""))

+ 2 - 0
src/main/frontend/fs/bfs.cljs

@@ -35,4 +35,6 @@
   (get-files [_this _path-or-handle _ok-handler]
     nil)
   (watch-dir! [_this _dir]
+    nil)
+  (unwatch-dir! [_this _dir]
     nil))

+ 3 - 1
src/main/frontend/fs/capacitor_fs.cljs

@@ -298,4 +298,6 @@
   (watch-dir! [_this dir]
     (p/do!
      (.unwatch mobile-util/fs-watcher)
-     (.watch mobile-util/fs-watcher #js {:path dir}))))
+     (.watch mobile-util/fs-watcher #js {:path dir})))
+  (unwatch-dir! [_this _dir]
+    (.unwatch mobile-util/fs-watcher)))

+ 4 - 2
src/main/frontend/fs/node.cljs

@@ -70,7 +70,7 @@
          (p/let [result (ipc/ipc "writeFile" repo path content)
                  mtime (gobj/get result "mtime")]
            (when-not contents-matched?
-             (ipc/ipc "backupDbFile" (config/get-local-dir repo) path db-content content))
+             (ipc/ipc "backupDbFile" (config/get-local-dir repo) path disk-content content))
            (db/set-file-last-modified-at! repo path mtime)
            (p/let [content (if (encrypt/encrypted-db? (state/get-current-repo))
                              (encrypt/decrypt content)
@@ -127,4 +127,6 @@
   (get-files [_this path-or-handle _ok-handler]
     (ipc/ipc "getFiles" path-or-handle))
   (watch-dir! [_this dir]
-    (ipc/ipc "addDirWatcher" dir)))
+    (ipc/ipc "addDirWatcher" dir))
+  (unwatch-dir! [_this dir]
+    (ipc/ipc "unwatchDir" dir)))

+ 2 - 1
src/main/frontend/fs/protocol.cljs

@@ -15,7 +15,8 @@
   (stat [this dir path])
   (open-dir [this ok-handler])
   (get-files [this path-or-handle ok-handler])
-  (watch-dir! [this dir]) 
+  (watch-dir! [this dir])
+  (unwatch-dir! [this dir])
   ;; Ensure the dir is watched, window agnostic.
   ;; Implementation should handle the actual watcher's construction / destruction.
   ;; So shouldn't consider `unwatch-dir!`.

+ 10 - 12
src/main/frontend/fs/watcher_handler.cljs

@@ -14,7 +14,8 @@
             [electron.ipc :as ipc]
             [promesa.core :as p]
             [frontend.state :as state]
-            [frontend.encrypt :as encrypt]))
+            [frontend.encrypt :as encrypt]
+            [frontend.fs :as fs]))
 
 ;; all IPC paths must be normalized! (via gp-util/path-normalize)
 
@@ -53,15 +54,10 @@
                  (not (:encryption/graph-parsing? @state/state)))
         (cond
           (and (= "unlinkDir" type) dir)
-          (do
-            (state/pub-event! [:notification/show
-                               {:content (str "The directory " dir " has been renamed or deleted, the editor will be disabled for this graph, you can unlink the graph.")
-                                :status :error
-                                :clear? false}])
-            (state/update-state! :file/unlinked-dirs (fn [dirs] (conj dirs dir))))
+          (state/pub-event! [:graph/dir-gone dir])
 
-          (= "addDir" type)
-          (state/update-state! :file/unlinked-dirs (fn [dirs] (disj dirs dir)))
+          (and (= "addDir" type) dir)
+          (state/pub-event! [:graph/dir-back repo dir])
 
           (contains? (:file/unlinked-dirs @state/state) dir)
           nil
@@ -90,9 +86,11 @@
 
           (and (= "unlink" type)
                (db/file-exists? repo path))
-          (when-let [page-name (db/get-file-page path)]
-            (println "Delete page: " page-name ", file path: " path ".")
-            (page-handler/delete! page-name #() :delete-file? false))
+          (p/let [dir-exists? (fs/file-exists? dir "")]
+            (when dir-exists?
+              (when-let [page-name (db/get-file-page path)]
+                (println "Delete page: " page-name ", file path: " path ".")
+                (page-handler/delete! page-name #() :unlink-file? true))))
 
           (and (contains? #{"add" "change" "unlink"} type)
                (string/ends-with? path "logseq/custom.css"))

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

@@ -6,7 +6,7 @@
             [frontend.config :as config]
             [frontend.context.i18n :as i18n]
             [frontend.db :as db]
-            [logseq.graph-parser.db.schema :as db-schema]
+            [logseq.db.schema :as db-schema]
             [frontend.db.conn :as conn]
             [frontend.db.react :as react]
             [frontend.error :as error]

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

@@ -172,7 +172,8 @@
   (when-let [touches (.-targetTouches event)]
     (let [selection-type (.-type (.getSelection js/document))]
       (when-not (= selection-type "Range")
-        (when (< (- (js/Date.now) @*touch-start) 600)
+        (when (or (not (state/sub :editor/editing?))
+                  (< (- (js/Date.now) @*touch-start) 600))
           (when (and (= (.-length touches) 1) @*swipe)
             (let [{:keys [x0 xi direction]} @*swipe
                   touch (aget touches 0)

+ 34 - 31
src/main/frontend/handler/editor.cljs

@@ -13,7 +13,7 @@
             [frontend.config :as config]
             [frontend.date :as date]
             [frontend.db :as db]
-            [logseq.graph-parser.db.schema :as db-schema]
+            [logseq.db.schema :as db-schema]
             [frontend.db.model :as db-model]
             [frontend.db.utils :as db-utils]
             [frontend.diff :as diff]
@@ -1060,8 +1060,8 @@
   (when-let [blocks (seq (get-selected-blocks))]
     ;; remove embeds, references and queries
     (let [dom-blocks (remove (fn [block]
-                           (or (= "true" (dom/attr block "data-transclude"))
-                               (= "true" (dom/attr block "data-query")))) blocks)]
+                              (or (= "true" (dom/attr block "data-transclude"))
+                                  (= "true" (dom/attr block "data-query")))) blocks)]
       (when (seq dom-blocks)
         (let [repo (state/get-current-repo)
               block-uuids (distinct (map #(uuid (dom/attr % "blockid")) dom-blocks))
@@ -1940,7 +1940,8 @@
               blocks' (map (fn [block]
                              (paste-block-cleanup block page exclude-properties format content-update-fn))
                         blocks)
-              result (outliner-core/insert-blocks! blocks' target-block {:sibling? sibling?})]
+              result (outliner-core/insert-blocks! blocks' target-block {:sibling? sibling?
+                                                                         :outliner-op :paste})]
           (edit-last-block-after-inserted! result))))))
 
 (defn- block-tree->blocks
@@ -2914,11 +2915,11 @@
       (do
         (util/stop e)
         (let [blocks (or
-                     (:copy/full-blocks copied-blocks)
-                     (get-all-blocks-by-ids (state/get-current-repo) copied-block-ids))]
-         (when (seq blocks)
-           (state/set-copied-full-blocks! blocks)
-           (paste-blocks blocks {}))))
+                      (:copy/full-blocks copied-blocks)
+                      (get-all-blocks-by-ids (state/get-current-repo) copied-block-ids))]
+          (when (seq blocks)
+            (state/set-copied-full-blocks! blocks)
+            (paste-blocks blocks {}))))
 
       (and (gp-util/url? text)
            (not (string/blank? (util/get-selected-text))))
@@ -2935,35 +2936,28 @@
       :else
       ;; from external
       (let [format (or (db/get-page-format (state/get-current-page)) :markdown)]
+        (util/stop e)
         (match [format
                 (nil? (util/safe-re-find #"(?m)^\s*(?:[-+*]|#+)\s+" text))
                 (nil? (util/safe-re-find #"(?m)^\s*\*+\s+" text))
                 (nil? (util/safe-re-find #"(?:\r?\n){2,}" text))]
           [:markdown false _ _]
-          (do
-            (util/stop e)
-            (paste-text-parseable format text))
+          (paste-text-parseable format text)
 
           [:org _ false _]
-          (do
-            (util/stop e)
-            (paste-text-parseable format text))
+          (paste-text-parseable format text)
 
           [:markdown true _ false]
-          (do
-            (util/stop e)
-            (paste-segmented-text format text))
+          (paste-segmented-text format text)
 
           [:markdown true _ true]
-          nil
+          (commands/simple-insert! (state/get-edit-input-id) text nil)
 
           [:org _ true false]
-          (do
-            (util/stop e)
-            (paste-segmented-text format text))
+          (paste-segmented-text format text)
 
           [:org _ true true]
-          nil)))))
+          (commands/simple-insert! (state/get-edit-input-id) text nil))))))
 
 (defn paste-text-in-one-block-at-point
   []
@@ -2986,7 +2980,7 @@
           edit-block (state/get-edit-block)
           format (or (:block/format edit-block) :markdown)
           initial-text (.getData clipboard-data "text")
-          text (or (when (string/blank? html)
+          text (or (when-not (string/blank? html)
                      (html-parser/convert format html))
                    initial-text)
           input (state/get-input)]
@@ -3026,18 +3020,25 @@
 
 ;; credits to @pengx17
 (defn- copy-current-block-ref
-  []
+  [format]
   (when-let [current-block (state/get-edit-block)]
     (when-let [block-id (:block/uuid current-block)]
-      (copy-block-ref! block-id #(str "((" % "))"))
+      (if (= format "embed")
+       (copy-block-ref! block-id #(str "{{embed ((" % "))}}"))
+       (copy-block-ref! block-id #(str "((" % "))")))
       (notification/show!
        [:div
-        [:span.mb-1.5 "Block ref copied!"]
-        [:div [:code.whitespace-nowrap (str "((" block-id "))")]]]
+        [:span.mb-1.5 (str "Block " format " copied!")]
+        [:div [:code.whitespace.break-all (if (= format "embed")
+                                         (str "{{embed ((" block-id "))}}")
+                                         (str "((" block-id "))"))]]]
        :success true
        ;; use uuid to make sure there is only one toast a time
        (str "copied-block-ref:" block-id)))))
 
+(defn copy-current-block-embed []
+  (copy-current-block-ref "embed"))
+
 (defn shortcut-copy
   "shortcut copy action:
   * when in selection mode, copy selected blocks
@@ -3054,7 +3055,7 @@
             selected-start (util/get-selection-start input)
             selected-end (util/get-selection-end input)]
         (if (= selected-start selected-end)
-          (copy-current-block-ref)
+          (copy-current-block-ref "ref")
           (js/document.execCommand "copy")))
 
       :else
@@ -3131,9 +3132,9 @@
           ;; if the move is to cross block boundary, select the whole block
          (or (and (= direction :up) (cursor/textarea-cursor-rect-first-row? cursor-rect))
              (and (= direction :down) (cursor/textarea-cursor-rect-last-row? cursor-rect)))
-          (select-block-up-down direction)
+         (select-block-up-down direction)
           ;; simulate text selection
-          (cursor/select-up-down input direction anchor cursor-rect)))
+         (cursor/select-up-down input direction anchor cursor-rect)))
       (select-block-up-down direction))))
 
 (defn open-selected-block!
@@ -3206,6 +3207,8 @@
     (util/forward-kill-word input)
     (state/set-edit-content! (state/get-edit-input-id) (.-value input))))
 
+
+
 (defn block-with-title?
   [format content semantic?]
   (and (string/includes? content "\n")

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

@@ -13,7 +13,7 @@
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
-            [logseq.graph-parser.db.schema :as db-schema]
+            [logseq.db.schema :as db-schema]
             [frontend.encrypt :as encrypt]
             [frontend.extensions.srs :as srs]
             [frontend.fs :as fs]
@@ -240,6 +240,11 @@
     (state/set-modal! #(git-component/file-specific-version path hash content))))
 
 (defmethod handle :graph/ready [[_ repo]]
+  (when (config/local-db? repo)
+    (p/let [dir (config/get-repo-dir repo)
+            dir-exists? (fs/dir-exists? dir)]
+      (when-not dir-exists?
+        (state/pub-event! [:graph/dir-gone dir]))))
   (search-handler/rebuild-indices-when-stale! repo)
   (repo-handler/graph-ready! repo))
 
@@ -436,6 +441,24 @@
   (route-handler/redirect! {:to :whiteboard
                             :path-params {:name link}}))
 
+(defmethod handle :graph/dir-gone [[_ dir]]
+  (state/pub-event! [:notification/show
+                     {:content (str "The directory " dir " has been renamed or deleted, the editor will be disabled for this graph, you can unlink the graph.")
+                      :status :error
+                      :clear? false}])
+  (state/update-state! :file/unlinked-dirs (fn [dirs] (conj dirs dir))))
+
+(defmethod handle :graph/dir-back [[_ repo dir]]
+  (when (contains? (:file/unlinked-dirs @state/state) dir)
+    (notification/clear-all!)
+    (state/pub-event! [:notification/show
+                       {:content (str "The directory " dir " has been back, you can edit your graph now.")
+                        :status :success
+                        :clear? true}])
+    (state/update-state! :file/unlinked-dirs (fn [dirs] (disj dirs dir))))
+  (when (= dir (config/get-repo-dir repo))
+    (fs/watch-dir! dir)))
+
 (defn run!
   []
   (let [chan (state/get-events-chan)]

+ 40 - 16
src/main/frontend/handler/export.cljs

@@ -1,5 +1,6 @@
 (ns frontend.handler.export
-  (:require [cljs.pprint :as pprint]
+  (:require ["@capacitor/filesystem" :refer [Encoding Filesystem]]
+            [cljs.pprint :as pprint]
             [clojure.set :as s]
             [clojure.string :as string]
             [clojure.walk :as walk]
@@ -9,19 +10,23 @@
             [frontend.extensions.zip :as zip]
             [frontend.external.roam-export :as roam-export]
             [frontend.format :as f]
+            [frontend.format.mldoc :as mldoc]
             [frontend.format.protocol :as fp]
+            [frontend.mobile.util :as mobile-util]
             [frontend.modules.file.core :as outliner-file]
             [frontend.modules.outliner.tree :as outliner-tree]
             [frontend.publishing.html :as html]
             [frontend.state :as state]
             [frontend.util :as util]
-            [frontend.format.mldoc :as mldoc]
+            [frontend.util.property :as property]
+            [goog.dom :as gdom]
+            [lambdaisland.glogi :as log]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.util :as gp-util]
-            [goog.dom :as gdom]
             [promesa.core :as p]
-            [frontend.util.property :as property])
-  (:import [goog.string StringBuffer]))
+            [frontend.handler.notification :as notification])
+  (:import
+   [goog.string StringBuffer]))
 
 (defn- get-page-content
   [repo page]
@@ -385,6 +390,18 @@
                                 :format (gp-util/get-format path)})))))
 
 
+(defn- export-file-on-mobile [data path]
+  (p/catch
+      (.writeFile Filesystem (clj->js {:path path
+                                       :data data
+                                       :encoding (.-UTF8 Encoding)
+                                       :recursive true}))
+      (notification/show! "Export succeeded! You can find you exported file in the root directory of your graph." :success)
+    (fn [error]
+        (notification/show! "Export failed!" :error)
+        (log/error :export-file-failed error))))
+
+
 (defn export-repo-as-markdown!
   [repo]
   (when-let [files (get-file-contents-with-suffix repo)]
@@ -479,13 +496,17 @@
 
 (defn export-repo-as-edn-v2!
   [repo]
-  (when-let [data-str (some->> (export-repo-as-edn-str repo)
-                               js/encodeURIComponent
-                               (str "data:text/edn;charset=utf-8,"))]
-    (when-let [anchor (gdom/getElement "download-as-edn-v2")]
-      (.setAttribute anchor "href" data-str)
-      (.setAttribute anchor "download" (file-name repo :edn))
-      (.click anchor))))
+  (when-let [edn-str (export-repo-as-edn-str repo)]
+    (let [data-str (some->> edn-str
+                            js/encodeURIComponent
+                            (str "data:text/edn;charset=utf-8,"))
+          filename (file-name repo :edn)]
+     (if (mobile-util/native-platform?)
+       (export-file-on-mobile edn-str filename)
+       (when-let [anchor (gdom/getElement "download-as-edn-v2")]
+         (.setAttribute anchor "href" data-str)
+         (.setAttribute anchor "download" filename)
+         (.click anchor))))))
 
 (defn- nested-update-id
   [vec-tree]
@@ -504,12 +525,15 @@
               nested-update-id
               clj->js
               js/JSON.stringify)
+          filename (file-name repo :json)
           data-str (str "data:text/json;charset=utf-8,"
                         (js/encodeURIComponent json-str))]
-      (when-let [anchor (gdom/getElement "download-as-json-v2")]
-        (.setAttribute anchor "href" data-str)
-        (.setAttribute anchor "download" (file-name repo :json))
-        (.click anchor)))))
+      (if (mobile-util/native-platform?)
+        (export-file-on-mobile json-str filename)
+        (when-let [anchor (gdom/getElement "download-as-json-v2")]
+          (.setAttribute anchor "href" data-str)
+          (.setAttribute anchor "download" filename)
+          (.click anchor))))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Export to roam json ;;

+ 8 - 5
src/main/frontend/handler/file.cljs

@@ -155,16 +155,18 @@
                       #(p/resolved nil)
                       #(fs/write-file! repo (config/get-repo-dir repo) path content
                                        (assoc (when original-content {:old-content original-content})
-                                              :skip-compare? skip-compare?)))]
+                                              :skip-compare? skip-compare?)))
+        opts {:new-graph? new-graph?
+              :from-disk? from-disk?}]
     (if reset?
       (do
         (when-let [page-id (db/get-file-page-id path)]
           (db/transact! repo
             [[:db/retract page-id :block/alias]
-             [:db/retract page-id :block/tags]]))
-        (reset-file! repo path content {:new-graph? new-graph?
-                                        :from-disk? from-disk?}))
-      (db/set-file-content! repo path content))
+             [:db/retract page-id :block/tags]]
+            opts))
+        (reset-file! repo path content opts))
+      (db/set-file-content! repo path content opts))
     (util/p-handle (write-file!)
                    (fn [_]
                      (when (= path (config/get-config-path repo))
@@ -258,6 +260,7 @@
   []
   (when-let [repo (state/get-current-repo)]
     (when-let [dir (config/get-repo-dir repo)]
+      (fs/unwatch-dir! dir)
       (fs/watch-dir! dir))))
 
 (defn create-metadata-file

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

@@ -2,7 +2,7 @@
   (:require [clojure.set :as set]
             [clojure.string :as string]
             [frontend.db :as db]
-            [logseq.graph-parser.db.default :as default-db]
+            [logseq.db.default :as default-db]
             [frontend.state :as state]
             [frontend.util :as util]))
 

+ 15 - 14
src/main/frontend/handler/metadata.cljs

@@ -41,20 +41,21 @@
 
 (defn set-pages-metadata!
   [repo]
-  (let [path (config/get-pages-metadata-path repo)
-        all-pages (->> (db/get-all-pages repo)
-                       (common-handler/fix-pages-timestamps)
-                       (map #(select-keys % [:block/name :block/created-at :block/updated-at]))
-                       (sort-by :block/name)
-                       (vec))]
-    (p/let [_ (-> (file-handler/create-pages-metadata-file repo)
-                  (p/catch (fn [] nil)))]
-      (let [new-content (with-out-str (cljs.pprint/pprint all-pages))]
-        (fs/write-file! repo
-                        (config/get-repo-dir repo)
-                        path
-                        new-content
-                        {})))))
+  (when-not (state/unlinked-dir? (config/get-repo-dir repo))
+    (let [path (config/get-pages-metadata-path repo)
+          all-pages (->> (db/get-all-pages repo)
+                         (common-handler/fix-pages-timestamps)
+                         (map #(select-keys % [:block/name :block/created-at :block/updated-at]))
+                         (sort-by :block/name)
+                         (vec))]
+      (p/let [_ (-> (file-handler/create-pages-metadata-file repo)
+                    (p/catch (fn [] nil)))]
+        (let [new-content (with-out-str (cljs.pprint/pprint all-pages))]
+          (fs/write-file! repo
+                          (config/get-repo-dir repo)
+                          path
+                          new-content
+                          {}))))))
 
 (defn set-db-encrypted-secret!
   [encrypted-secret]

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

@@ -7,6 +7,10 @@
   (let [contents (state/get-notification-contents)]
     (state/set-state! :notification/contents (dissoc contents uid))))
 
+(defn clear-all!
+  []
+  (state/set-state! :notification/contents nil))
+
 (defn show!
   ([content status]
    (show! content status true nil 1500))

+ 12 - 10
src/main/frontend/handler/page.cljs

@@ -7,7 +7,7 @@
             [frontend.config :as config]
             [frontend.date :as date]
             [frontend.db :as db]
-            [logseq.graph-parser.db.schema :as db-schema]
+            [logseq.db.schema :as db-schema]
             [frontend.db.model :as model]
             [frontend.db.utils :as db-utils]
             [frontend.db.conn :as conn]
@@ -160,19 +160,21 @@
      page-name)))
 
 (defn delete-file!
-  [repo page-name]
+  [repo page-name unlink-file?]
   (let [file (db/get-page-file page-name)
         file-path (:file/path file)]
     ;; delete file
     (when-not (string/blank? file-path)
       (db/transact! [[:db.fn/retractEntity [:file/path file-path]]])
-      (->
-       (p/let [_ (and (config/local-db? repo)
-                      (mobile-util/native-platform?)
-                      (fs/delete-file! repo file-path file-path {}))
-               _ (fs/unlink! repo (config/get-repo-path repo file-path) nil)])
-       (p/catch (fn [err]
-                  (js/console.error "error: " err)))))))
+      (when unlink-file?
+        (->
+         (p/let [_ (and (config/local-db? repo)
+                        (mobile-util/native-platform?)
+                        ;; TODO: @leizhe remove fs/delete-file! and use fs/unlink!
+                        (fs/delete-file! repo file-path file-path {}))
+                 _ (fs/unlink! repo (config/get-repo-path repo file-path) nil)])
+         (p/catch (fn [err]
+                    (js/console.error "error: " err))))))))
 
 (defn- compute-new-file-path
   [old-path new-name]
@@ -316,7 +318,7 @@
             page (db/entity [:block/name page-name])]
         (db/transact! tx-data)
 
-        (when delete-file? (delete-file! repo page-name))
+        (delete-file! repo page-name delete-file?)
 
         ;; if other page alias this pagename,
         ;; then just remove some attrs of this entity instead of retractEntity

+ 22 - 19
src/main/frontend/handler/plugin.cljs

@@ -55,12 +55,13 @@
   (if (or refresh? (nil? (:plugin/marketplace-pkgs @state/state)))
     (p/create
       (fn [resolve reject]
-        (-> (util/fetch plugins-url
-                        (fn [res]
-                          (let [pkgs (:packages res)]
-                            (state/set-state! :plugin/marketplace-pkgs pkgs)
-                            (resolve pkgs)))
-                        reject)
+        (-> (ipc/ipc :httpFetchJSON plugins-url)
+            (p/then (fn [res]
+                      (if-let [res (and res (bean/->clj res))]
+                        (let [pkgs (:packages res)]
+                          (state/set-state! :plugin/marketplace-pkgs pkgs)
+                          (resolve pkgs))
+                        (reject nil))))
             (p/catch reject))))
     (p/resolved (:plugin/marketplace-pkgs @state/state))))
 
@@ -69,18 +70,20 @@
   (if (or refresh? (nil? (:plugin/marketplace-stats @state/state)))
     (p/create
       (fn [resolve reject]
-        (util/fetch stats-url
-                    (fn [res]
-                      (when res
-                        (state/set-state!
-                          :plugin/marketplace-stats
-                          (into {} (map (fn [[k stat]]
-                                          [k (assoc stat
-                                               :total_downloads
-                                               (reduce (fn [a b] (+ a (get b 2))) 0 (:releases stat)))])
-                                        res)))
-                        (resolve nil)))
-                    reject)))
+        (-> (ipc/ipc :httpFetchJSON stats-url)
+            (p/then (fn [^js res]
+                      (if-let [res (and res (bean/->clj res))]
+                        (do
+                          (state/set-state!
+                           :plugin/marketplace-stats
+                           (into {} (map (fn [[k stat]]
+                                           [k (assoc stat
+                                                     :total_downloads
+                                                     (reduce (fn [a b] (+ a (get b 2))) 0 (:releases stat)))])
+                                         res)))
+                          (resolve nil))
+                        (reject nil))))
+            (p/catch reject))))
     (p/resolved nil)))
 
 (defn installed?
@@ -605,7 +608,7 @@
                                             (state/set-custom-theme! mode theme)
                                             (state/set-theme-mode! mode))
                                           (state/set-state! :plugin/selected-theme url))))
-                                        
+
                 (.on "reset-custom-theme" (fn [^js themes]
                                             (let [themes (bean/->clj themes)
                                                   custom-theme (dissoc themes :mode)

+ 20 - 17
src/main/frontend/handler/repo.cljs

@@ -169,7 +169,7 @@
                                          (not= updated-at (:block/created-at page)))))) metadata)
                            (remove nil?))]
              (when (seq metadata)
-               (db/transact! repo metadata))))))
+               (db/transact! repo metadata {:new-graph? true}))))))
      (catch js/Error e
        (log/error :exception e)))))
 
@@ -180,7 +180,7 @@
         files [{:file/path path
                 :file/content content}]
         file-paths [path]]
-    (util/profile "update-pages-metadata!" (load-pages-metadata! repo file-paths files force?))))
+    (load-pages-metadata! repo file-paths files force?)))
 
 (defn- parse-and-load-file!
   [repo-url file new-graph?]
@@ -381,26 +381,29 @@
 
 (defn rebuild-index!
   [url]
-  (when url
-    (search/reset-indice! url)
-    (db/remove-conn! url)
-    (db/clear-query-state!)
-    (-> (p/do! (db-persist/delete-graph! url))
-        (p/catch (fn [error]
-                   (prn "Delete repo failed, error: " error))))))
+  (when-not (state/unlinked-dir? (config/get-repo-dir url))
+    (when url
+      (search/reset-indice! url)
+      (db/remove-conn! url)
+      (db/clear-query-state!)
+      (-> (p/do! (db-persist/delete-graph! url))
+          (p/catch (fn [error]
+                     (prn "Delete repo failed, error: " error)))))))
 
 (defn re-index!
   [nfs-rebuild-index! ok-handler]
-  (route-handler/redirect-to-home!)
   (when-let [repo (state/get-current-repo)]
-    (let [local? (config/local-db? repo)]
-      (if local?
-        (p/let [_ (metadata-handler/set-pages-metadata! repo)]
-          (nfs-rebuild-index! repo ok-handler))
-        (rebuild-index! repo))
-      (js/setTimeout
+    (let [dir (config/get-repo-dir repo)]
+      (when-not (state/unlinked-dir? dir)
        (route-handler/redirect-to-home!)
-       500))))
+       (let [local? (config/local-db? repo)]
+         (if local?
+           (p/let [_ (metadata-handler/set-pages-metadata! repo)]
+             (nfs-rebuild-index! repo ok-handler))
+           (rebuild-index! repo))
+         (js/setTimeout
+          (route-handler/redirect-to-home!)
+          500))))))
 
 (defn persist-db!
   ([]

+ 5 - 2
src/main/frontend/handler/web/nfs.cljs

@@ -268,7 +268,8 @@
                       (repo-handler/load-repo-to-db! repo
                                                      {:diffs     diffs
                                                       :nfs-files modified-files
-                                                      :refresh? (not re-index?)}))
+                                                      :refresh? (not re-index?)
+                                                      :new-graph? re-index?}))
                     (when (and (util/electron?) (not re-index?))
                       (db/transact! repo new-files))))))))
 
@@ -326,9 +327,11 @@
             _ (ok-handler)]
       (state/set-nfs-refreshing! false))))
 
+;; TODO: move to frontend.handler.repo
 (defn refresh!
   [repo ok-handler]
-  (when repo
+  (when (and repo
+             (not (state/unlinked-dir? (config/get-repo-dir repo))))
     (state/set-nfs-refreshing! true)
     (p/let [_ (reload-dir! repo)
             _ (ok-handler)]

+ 10 - 0
src/main/frontend/mobile/mobile_bar.cljs

@@ -31,6 +31,14 @@
     (config-handler/set-config!
      :mobile/toolbar-stats @commands-stats)))
 
+(rum/defc indent-outdent [indent? icon]
+  [:div
+   [:button.bottom-action
+    {:on-mouse-down (fn [e]
+                      (util/stop e)
+                      (editor-handler/indent-outdent indent?))}
+    (ui/icon icon {:style {:fontSize ui/icon-size}})]])
+
 (rum/defc command
   [command-handler icon & [count? event?]]
   [:div
@@ -119,6 +127,8 @@
           sorted-commands (sort-by (comp :counts second) > @commands-stats)]
       [:div#mobile-editor-toolbar.bg-base-2
        [:div.toolbar-commands
+        (indent-outdent false "indent-decrease")
+        (indent-outdent true "indent-increase")
         (command (editor-handler/move-up-down true) "arrow-bar-to-up")
         (command (editor-handler/move-up-down false) "arrow-bar-to-down")
         (command #(if (state/sub :document/mode?)

+ 6 - 4
src/main/frontend/modules/file/core.cljs

@@ -119,15 +119,17 @@
                               (state/get-preferred-format)))
             title (string/capitalize (:block/name page))
             journal-page? (date/valid-journal-title? title)
+            filename (if journal-page?
+                       (date/date->file-name journal-page?)
+                       (-> (or (:block/original-name page) (:block/name page))
+                           (util/file-name-sanity)))
             path (str
                   (if journal-page?
                     (config/get-journals-directory)
                     (config/get-pages-directory))
                   "/"
-                  (if journal-page?
-                    (date/date->file-name journal-page?)
-                    (-> (or (:block/original-name page) (:block/name page))
-                        (util/file-name-sanity))) "."
+                  filename
+                  "."
                   (if (= format "markdown") "md" format))
             file-path (config/get-file-path repo path)
             file {:file/path file-path}

+ 1 - 1
src/main/frontend/modules/instrumentation/sentry.cljs

@@ -45,4 +45,4 @@
 (defn init []
   (when-not config/dev?
     (let [config (clj->js config)]
-     (Sentry/init config))))
+      (Sentry/init config))))

+ 66 - 46
src/main/frontend/modules/outliner/core.cljs

@@ -4,7 +4,7 @@
             [datascript.impl.entity :as de]
             [frontend.db :as db]
             [frontend.db.model :as db-model]
-            [logseq.graph-parser.db.schema :as db-schema]
+            [logseq.db.schema :as db-schema]
             [frontend.db.conn :as conn]
             [frontend.db.outliner :as db-outliner]
             [frontend.modules.outliner.datascript :as ds]
@@ -370,6 +370,21 @@
 
 ;;; ### insert-blocks, delete-blocks, move-blocks
 
+(defn- fix-top-level-blocks
+  "Blocks with :block/level"
+  [blocks]
+  (loop [blocks blocks
+         last-top-level-block nil
+         result []]
+    (if-let [block (first blocks)]
+      (if (= 1 (:block/level block))
+        (let [block' (assoc block
+                            :block/left {:db/id (:db/id last-top-level-block)}
+                            :block/parent (:block/parent last-top-level-block))]
+          (recur (rest blocks) block (conj result block')))
+        (recur (rest blocks) last-top-level-block (conj result block)))
+      result)))
+
 (defn- insert-blocks-aux
   [blocks target-block {:keys [sibling? replace-empty-target? keep-uuid? move? outliner-op]}]
   (let [block-uuids (map :block/uuid blocks)
@@ -454,6 +469,9 @@
                                      (> (count blocks) 1)
                                      (not move?)))
         blocks' (blocks-with-level blocks)
+        blocks' (if (= outliner-op ::paste)
+                  (fix-top-level-blocks blocks')
+                  blocks')
         insert-opts {:sibling? sibling?
                      :replace-empty-target? replace-empty-target?
                      :keep-uuid? keep-uuid?
@@ -693,51 +711,53 @@
   "Indent or outdent `blocks`."
   [blocks indent?]
   {:pre [(seq blocks) (boolean? indent?)]}
-  (let [first-block (db/entity (:db/id (first blocks)))
-        left (db/entity (:db/id (:block/left first-block)))
-        parent (:block/parent first-block)
-        db (db/get-db)
-        top-level-blocks (get-top-level-blocks blocks)
-        concat-tx-fn (fn [& results]
-                       {:tx-data (->> (map :tx-data results)
-                                      (apply util/concat-without-nil))
-                        :tx-meta (:tx-meta (first results))})
-        opts {:outliner-op :indent-outdent-blocks}]
-    (if indent?
-      (when (and left (not (page-first-child? first-block)))
-        (let [last-direct-child-id (db-model/get-block-last-direct-child db (:db/id left) false)
-              blocks' (drop-while (fn [b]
-                                    (= (:db/id (:block/parent b))
-                                       (:db/id left)))
-                                  top-level-blocks)]
-          (when (seq blocks')
-            (if last-direct-child-id
-              (let [last-direct-child (db/entity last-direct-child-id)
-                    result (move-blocks blocks' last-direct-child (merge opts {:sibling? true}))
-                    ;; expand `left` if it's collapsed
-                    collapsed-tx (when (:block/collapsed? left)
-                                   {:tx-data [{:db/id (:db/id left)
-                                               :block/collapsed? false}]})]
-                (concat-tx-fn result collapsed-tx))
-              (move-blocks blocks' left (merge opts {:sibling? false}))))))
-      (when (and parent (not (page-block? (db/entity (:db/id parent)))))
-        (let [blocks' (take-while (fn [b]
-                                    (not= (:db/id (:block/parent b))
-                                          (:db/id (:block/parent parent))))
-                                  top-level-blocks)
-              result (move-blocks blocks' parent (merge opts {:sibling? true}))]
-          (if (state/logical-outdenting?)
-            result
-            ;; direct outdenting (default behavior)
-            (let [last-top-block (db/pull (:db/id (last blocks')))
-                  right-siblings (->> (get-right-siblings (block last-top-block))
-                                      (map :data))]
-              (if (seq right-siblings)
-                (let [result2 (if-let [last-direct-child-id (db-model/get-block-last-direct-child db (:db/id last-top-block) false)]
-                                (move-blocks right-siblings (db/entity last-direct-child-id) (merge opts {:sibling? true}))
-                                (move-blocks right-siblings last-top-block (merge opts {:sibling? false})))]
-                  (concat-tx-fn result result2))
-                result))))))))
+  (let [non-consecutive-blocks (db-model/get-non-consecutive-blocks blocks)]
+    (when (empty? non-consecutive-blocks)
+      (let [first-block (db/entity (:db/id (first blocks)))
+            left (db/entity (:db/id (:block/left first-block)))
+            parent (:block/parent first-block)
+            db (db/get-db)
+            top-level-blocks (get-top-level-blocks blocks)
+            concat-tx-fn (fn [& results]
+                           {:tx-data (->> (map :tx-data results)
+                                          (apply util/concat-without-nil))
+                            :tx-meta (:tx-meta (first results))})
+            opts {:outliner-op :indent-outdent-blocks}]
+        (if indent?
+          (when (and left (not (page-first-child? first-block)))
+            (let [last-direct-child-id (db-model/get-block-last-direct-child db (:db/id left) false)
+                  blocks' (drop-while (fn [b]
+                                        (= (:db/id (:block/parent b))
+                                           (:db/id left)))
+                                      top-level-blocks)]
+              (when (seq blocks')
+                (if last-direct-child-id
+                  (let [last-direct-child (db/entity last-direct-child-id)
+                        result (move-blocks blocks' last-direct-child (merge opts {:sibling? true}))
+                        ;; expand `left` if it's collapsed
+                        collapsed-tx (when (:block/collapsed? left)
+                                       {:tx-data [{:db/id (:db/id left)
+                                                   :block/collapsed? false}]})]
+                    (concat-tx-fn result collapsed-tx))
+                  (move-blocks blocks' left (merge opts {:sibling? false}))))))
+          (when (and parent (not (page-block? (db/entity (:db/id parent)))))
+            (let [blocks' (take-while (fn [b]
+                                        (not= (:db/id (:block/parent b))
+                                              (:db/id (:block/parent parent))))
+                                      top-level-blocks)
+                  result (move-blocks blocks' parent (merge opts {:sibling? true}))]
+              (if (state/logical-outdenting?)
+                result
+                ;; direct outdenting (default behavior)
+                (let [last-top-block (db/pull (:db/id (last blocks')))
+                      right-siblings (->> (get-right-siblings (block last-top-block))
+                                          (map :data))]
+                  (if (seq right-siblings)
+                    (let [result2 (if-let [last-direct-child-id (db-model/get-block-last-direct-child db (:db/id last-top-block) false)]
+                                    (move-blocks right-siblings (db/entity last-direct-child-id) (merge opts {:sibling? true}))
+                                    (move-blocks right-siblings last-top-block (merge opts {:sibling? false})))]
+                      (concat-tx-fn result result2))
+                    result))))))))))
 
 ;;; ### write-operations have side-effects (do transactions) ;;;;;;;;;;;;;;;;
 

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

@@ -12,7 +12,8 @@
   (when (and (not (:from-disk? (:tx-meta tx-report)))
              (not (:new-graph? (:tx-meta tx-report))))
     (let [{:keys [pages blocks]} (ds-report/get-blocks-and-pages tx-report)]
-      (doseq [p (seq pages)] (updated-page-hook tx-report p))
+      (doseq [p (seq pages)]
+        (updated-page-hook tx-report p))
       (when (and state/lsp-enabled? (seq blocks))
         (state/pub-event! [:plugin/hook-db-tx
                            {:blocks  blocks

+ 5 - 7
src/main/frontend/modules/shortcut/config.cljs

@@ -145,6 +145,8 @@
 
    :editor/replace-block-reference-at-point {:binding "mod+shift+r"
                                              :fn      editor-handler/replace-block-reference-with-content-at-point}
+   :editor/copy-embed {:binding "mod+e"
+                       :fn      editor-handler/copy-current-block-embed}
 
    :editor/paste-text-in-one-block-at-point {:binding "mod+shift+v"
                                              :fn      editor-handler/paste-text-in-one-block-at-point}
@@ -343,10 +345,6 @@
    :ui/toggle-contents              {:binding "alt+shift+c"
                                      :fn      ui-handler/toggle-contents!}
 
-   :ui/open-new-window              {:binding "mod+n"
-                                     :inactive (not (util/electron?))
-                                     :fn      #(state/pub-event! [:graph/open-new-window nil])}
-
    :command/toggle-favorite         {:binding "mod+shift+f"
                                      :fn      page-handler/toggle-favorite!}
 
@@ -447,6 +445,7 @@
                           :editor/forward-kill-word
                           :editor/backward-kill-word
                           :editor/replace-block-reference-at-point
+                          :editor/copy-embed
                           :editor/paste-text-in-one-block-at-point
                           :editor/insert-youtube-timestamp])
      (with-meta {:before m/enable-when-editing-mode!}))
@@ -522,7 +521,6 @@
                           :ui/toggle-help
                           :ui/toggle-theme
                           :ui/toggle-contents
-                          :ui/open-new-window
                           :editor/open-file-in-default-app
                           :editor/open-file-in-directory
                           :editor/copy-current-file
@@ -578,8 +576,7 @@
     :go/tomorrow
     :go/next-journal
     :go/prev-journal
-    :go/keyboard-shortcuts
-    :ui/open-new-window]
+    :go/keyboard-shortcuts]
 
    :shortcut.category/block-editing
    [:editor/backspace
@@ -609,6 +606,7 @@
     :editor/forward-kill-word
     :editor/backward-kill-word
     :editor/replace-block-reference-at-point
+    :editor/copy-embed
     :editor/paste-text-in-one-block-at-point
     :editor/select-up
     :editor/select-down]

+ 62 - 9
src/main/frontend/modules/shortcut/dicts.cljc

@@ -38,6 +38,7 @@
    :editor/strike-through        "Strikethrough"
    :editor/clear-block           "Delete entire block content"
    :editor/kill-line-before      "Delete line before cursor position"
+   :editor/copy-embed            "Copy a block embed pointing to the current block"
    :editor/kill-line-after       "Delete line after cursor position"
    :editor/beginning-of-block    "Move cursor to the beginning of a block"
    :editor/end-of-block          "Move cursor to the end of a block"
@@ -105,7 +106,7 @@
    :ui/toggle-help                 "Toggle help"
    :ui/toggle-theme                "Toggle between dark/light theme"
    :ui/toggle-contents             "Toggle Contents in sidebar"
-   :ui/open-new-window             "Open another window"
+  ;;  :ui/open-new-window             "Open another window"
    :command/toggle-favorite        "Add to/remove from favorites"
    :editor/open-file-in-default-app "Open file in default app"
    :editor/open-file-in-directory   "Open file in parent directory"
@@ -488,7 +489,59 @@
              :command.editor/backward-kill-word       "Slett ett ord bakover"
              :command.editor/open-edit                "Rediger valgt blokk"
              :command.editor/delete-selection         "Slett valgte blokker"
-             :command.editor/toggle-open-blocks       "Veksle åpne blokker (slå sammen eller utvid alle blokker)"}
+             :command.editor/toggle-open-blocks       "Veksle åpne blokker (slå sammen eller utvid alle blokker)"
+             :command.auto-complete/complete "Autofullfør: Bruk valgt punkt"
+             :command.auto-complete/next "Autofullfør: Velg neste punkt"
+             :command.auto-complete/open-link "Autofullfør: Åpne valgt punkt i nettleser"
+             :command.auto-complete/prev "Autofullfør: Velg forrige punkt"
+             :command.auto-complete/shift-complete "Autofullfør: Åpne valgt punkt i sidestolpen"
+             :command.cards/forgotten "Kort: Glemte"
+             :command.cards/next-card "Kort: Neste kort"
+             :command.cards/recall "Kort: bruk litt tid på å huske"
+             :command.cards/remembered "Kort: husket"
+             :command.cards/toggle-answers "Kort: vis/skjul svar/clozes"
+             :command.command/run "Kjør git kommando"
+             :command.command/toggle-favorite "Legg til eller fjern fra favoritter"
+             :command.command-palette/toggle "Veksle kommandolinje"
+             :command.date-picker/complete "Datovelger: Bruk valgt dag"
+             :command.date-picker/next-day "Datovelger: Velg neste dag"
+             :command.date-picker/next-week "Datovelger: Velg neste uke"
+             :command.date-picker/prev-day "Datovelger: Velg forrige dag"
+             :command.date-picker/prev-week "Datovelger: Velg forrige uke"
+             :command.editor/copy-current-file "Kopier nåværende fil"
+             :command.editor/escape-editing "Avslutt redigering"
+             :command.editor/insert-youtube-timestamp "Sett inn YouTube tidsstempel"
+             :command.editor/open-file-in-default-app "Åpne fil i forhåndsvalgt app"
+             :command.editor/open-file-in-directory "Åpne fil i overordnet katalog"
+             :command.editor/paste-text-in-one-block-at-point "Lim inn tekst i blokk ved markør"
+             :command.editor/replace-block-reference-at-point "Erstatt blokkreferanse med dets innhold ved markør"
+             :command.editor/select-down "Velg innhold under"
+             :command.editor/select-up "Velg innhold over"
+             :command.editor/strike-through "Gjennomstreking"
+             :command.go/all-pages "Gå til alle sider"
+             :command.go/backward "Bakover"
+             :command.go/flashcards "Veksle flashcards"
+             :command.go/forward "Fremover"
+             :command.go/graph-view "Gå til graf visning"
+             :command.go/home "Gå hjem"
+             :command.go/keyboard-shortcuts "Gå til tastatursnarveier"
+             :command.go/next-journal "Gå til neste dagbok"
+             :command.go/prev-journal "Gå til forrige dagbok"
+             :command.go/tomorrow "Gå til i morgen"
+             :command.graph/add "Legg til graf"
+             :command.graph/open "Velg graf for å åpne"
+             :command.graph/remove "Fjern en graf"
+             :command.graph/save "Lagre nåværende graf til disk"
+             :command.misc/copy "mod+c"
+             :command.pdf/close "Lukk nåværende pdf leser"
+             :command.pdf/next-page "Neste side i nåværende pdf dok"
+             :command.pdf/previous-page "Forrige side i nåværende pdf dok"
+             :command.sidebar/clear "Fjern alt i høyre sidestolpe"
+             :command.sidebar/open-today-page "Åpne dagens side i høyre sidestolpe"
+             :command.ui/goto-plugins "Gå til dashbord for utvidelser"
+            ;;  :command.ui/open-new-window "Åpne et nytt vindu"
+             :command.ui/select-theme-color "Velg tilgjengelige temafarger"
+             :command.ui/toggle-cards "Veksle kort"}
 
    :pt-PT   {:shortcut.category/formatting            "Formatação"
              :shortcut.category/basics                "Básico"
@@ -666,7 +719,7 @@
              :command.graph/save                      "Salvar gráfico atual no computador"
              :command.misc/copy                       "Copiar (copiar seleção ou referência do bloco)"
              :command.ui/goto-plugins                 "Ir para o painel de plugins"
-             :command.ui/open-new-window              "Abra uma nova janela"
+            ;;  :command.ui/open-new-window              "Abra uma nova janela"
              :command.editor/select-down              "Selecione o conteúdo abaixo"
              :command.editor/select-up                "Selecione o conteúdo acima"}
 
@@ -708,7 +761,7 @@
              :command.go/next-journal                 "次の日誌へ移動"
              :command.go/prev-journal                 "前の日誌へ移動"
              :command.go/keyboard-shortcuts           "キーボードショートカットへ移動"
-             :command.ui/open-new-window              "別のウィンドウを開く"
+            ;;  :command.ui/open-new-window              "別のウィンドウを開く"
              :command.go/search-in-page               "ページ内を検索"
              :command.ui/toggle-document-mode         "ドキュメントモードのトグル"
              :command.ui/toggle-contents              "目次のトグル"
@@ -882,7 +935,7 @@
              :command.ui/toggle-help                 "Attiva/disattiva aiuto"
              :command.ui/toggle-theme                "Passa dal tema scuro a quello chiaro"
              :command.ui/toggle-contents             "Attiva/disattiva i contenuti nella barra laterale"
-             :command.ui/open-new-window             "Apri un'altra finestra"
+            ;;  :command.ui/open-new-window             "Apri un'altra finestra"
              :command.command/toggle-favorite        "Aggiungi a/rimuovi dai preferiti"
              :command.editor/open-file-in-default-app "Apri file nell'app predefinita"
              :command.editor/open-file-in-directory   "Apri file nella directory principale"
@@ -900,9 +953,9 @@
              :shortcut.category/block-command-editing "Modifica comandi blocco"
              :shortcut.category/block-selection       "Selezione blocco (premi Esc per uscire dalla selezione)"
              :shortcut.category/toggle                "Attiva/disattiva"
-             :shortcut.category/others                "Altri"
+             :shortcut.category/others                "Altri"}
 
-   }
+   
    :tr      {:shortcut.category/basics "Temel bilgiler"
              :shortcut.category/formatting "Biçimlendirme"
              :shortcut.category/navigating "Gezinme"
@@ -1008,7 +1061,7 @@
              :command.ui/toggle-help                 "Yardımı aç/kapat"
              :command.ui/toggle-theme                "Koyu ve açık tema arasında geçiş yap"
              :command.ui/toggle-contents             "Kenar çubuğundaki içeriği aç/kapat"
-             :command.ui/open-new-window             "Başka bir pencere aç"
+            ;;  :command.ui/open-new-window             "Başka bir pencere aç"
              :command.command/toggle-favorite        "Sık kullanılanlara ekle/çıkar"
              :command.editor/open-file-in-default-app "Dosyayı varsayılan uygulamada aç"
              :command.editor/open-file-in-directory   "Dosyayı üst dizinde aç"
@@ -1058,7 +1111,7 @@
              :command.go/next-journal                 "다음 일지로 이동"
              :command.go/prev-journal                 "이전 일지로 이동"
              :command.go/keyboard-shortcuts           "키보드 단축키로 이동"
-             :command.ui/open-new-window              "새 창 열기"
+            ;;  :command.ui/open-new-window              "새 창 열기"
              :command.go/search-in-page               "페이지 안에서 검색"
              :command.ui/toggle-document-mode         "문서 모드 토글"
              :command.ui/toggle-contents              "목차 토글"

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

@@ -2,7 +2,7 @@
   (:require [frontend.state :as state]
             [datascript.core :as d]
             [frontend.db :as db]
-            [logseq.graph-parser.db.schema :as db-schema]
+            [logseq.db.schema :as db-schema]
             [rum.core :as rum]
             [frontend.handler.route :as route]
             [frontend.page :as page]

+ 12 - 2
src/main/frontend/state.cljs

@@ -369,7 +369,12 @@
 (defn enable-journals?
   [repo]
   (not (false? (:feature/enable-journals?
-                 (get (sub-config) repo)))))
+                (get (sub-config) repo)))))
+
+(defn enable-flashcards?
+  [repo]
+  (not (false? (:feature/enable-flashcards?
+                (get (sub-config) repo)))))
 
 (defn export-heading-to-list?
   []
@@ -684,7 +689,8 @@
   (swap! state assoc
          :selection/mode false
          :selection/blocks nil
-         :selection/direction :down))
+         :selection/direction :down
+         :selection/start-block nil))
 
 (defn get-selection-blocks
   []
@@ -1706,3 +1712,7 @@
   []
   (some-> (get-current-whiteboard)
           (gobj/get "api")))
+
+(defn unlinked-dir?
+  [dir]
+  (contains? (:file/unlinked-dirs @state) dir))

+ 6 - 2
src/main/frontend/ui.cljs

@@ -302,9 +302,13 @@
 (defn setup-system-theme-effect!
   []
   (let [^js schemaMedia (js/window.matchMedia "(prefers-color-scheme: dark)")]
-    (.addEventListener schemaMedia "change" state/sync-system-theme!)
+    (try (.addEventListener schemaMedia "change" state/sync-system-theme!)
+         (catch js/Error _error
+           (.addListener schemaMedia state/sync-system-theme!)))
     (state/sync-system-theme!)
-    #(.removeEventListener schemaMedia "change" state/sync-system-theme!)))
+    #(try (.removeEventListener schemaMedia "change" state/sync-system-theme!)
+          (catch js/Error _error
+            (.removeListener schemaMedia state/sync-system-theme!)))))
 
 (defn set-global-active-keystroke [val]
   (.setAttribute js/document.body "data-active-keystroke" val))

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

@@ -1,3 +1,3 @@
 (ns frontend.version)
 
-(defonce version "0.7.1")
+(defonce version "0.7.4")

+ 37 - 1
src/test/frontend/modules/outliner/core_test.cljs

@@ -179,6 +179,42 @@
       (outliner-core/indent-outdent-blocks! [(get-block 6) (get-block 9)] true))
     (is (= [4 5 6 9] (get-children 3)))))
 
+(deftest test-indent-blocks-regression-5604
+  (testing "
+  [22 [[2 [[3
+           [[4]
+            [5]
+            [6 [[7 [[8]]]]]
+            [9 [[10]
+                [11]]]]]]]
+      [12 [[13]                         ; outdents 13, 14, 15
+           [14]
+           [15]]]
+      [16 [[17]]]]]
+  "
+    (transact-tree! tree)
+    (outliner-tx/transact!
+      {:graph test-db}
+      (outliner-core/indent-outdent-blocks! [(get-block 13) (get-block 14) (get-block 15)] false))
+    (is (= [2 12 13 14 15 16] (get-children 22))))
+  (testing "
+  [22 [[2 [[3
+           [[4]
+            [5]
+            [6 [[7 [[8]]]]]
+            [9 [[10]
+                [11]]]]]]]
+      [12 [[13]                         ; outdents 13, 14
+           [14]
+           [15]]]
+      [16 [[17]]]]]
+  "
+    (transact-tree! tree)
+    (outliner-tx/transact!
+      {:graph test-db}
+      (outliner-core/indent-outdent-blocks! [(get-block 13) (get-block 14)] false))
+    (is (= [2 12 13 14 16] (get-children 22)))))
+
 (deftest test-outdent-blocks
   (testing "
   [1 [[2 [[3]
@@ -415,7 +451,7 @@
     (transact-random-tree!)
     (let [c1 (get-blocks-ids)
           *random-blocks (atom c1)]
-      (dotimes [_i 200]
+      (dotimes [_i 100]
         ;; (prn "random insert: " i)
         (let [blocks (gen-blocks)]
           (swap! *random-blocks (fn [old]

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff