瀏覽代碼

Merge branch 'whiteboards' into enhance/whiteboards-ui

Konstantinos Kaloutas 3 年之前
父節點
當前提交
4ccd9e4206
共有 100 個文件被更改,包括 664 次插入1903 次删除
  1. 1 1
      deps/db/package.json
  2. 4 4
      deps/db/yarn.lock
  3. 1 1
      deps/graph-parser/package.json
  4. 1 1
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  5. 17 9
      deps/graph-parser/src/logseq/graph_parser/extract.cljc
  6. 2 0
      deps/graph-parser/src/logseq/graph_parser/property.cljs
  7. 4 5
      deps/graph-parser/src/logseq/graph_parser/util.cljs
  8. 11 8
      deps/graph-parser/test/logseq/graph_parser/nbb_test_runner.cljs
  9. 14 0
      deps/graph-parser/test/logseq/graph_parser/util_test.cljs
  10. 32 0
      deps/graph-parser/test/logseq/graph_parser_test.cljs
  11. 4 4
      deps/graph-parser/yarn.lock
  12. 35 13
      docs/develop-logseq-on-windows.md
  13. 3 2
      libs/src/LSPlugin.caller.ts
  14. 25 3
      libs/src/LSPlugin.core.ts
  15. 8 0
      libs/src/postmate/index.ts
  16. 6 6
      package.json
  17. 0 0
      resources/js/lsplugin.core.js
  18. 3 3
      resources/package.json
  19. 7 0
      src/electron/electron/handler.cljs
  20. 140 149
      src/main/frontend/components/block.cljs
  21. 1 2
      src/main/frontend/components/editor.cljs
  22. 85 74
      src/main/frontend/components/page.cljs
  23. 27 0
      src/main/frontend/components/plugins.cljs
  24. 1 1
      src/main/frontend/components/reference.cljs
  25. 2 2
      src/main/frontend/components/right_sidebar.cljs
  26. 4 0
      src/main/frontend/components/right_sidebar.css
  27. 3 2
      src/main/frontend/components/sidebar.cljs
  28. 2 18
      src/main/frontend/dicts.cljc
  29. 7 8
      src/main/frontend/handler/block.cljs
  30. 8 2
      src/main/frontend/handler/editor.cljs
  31. 6 0
      src/main/frontend/handler/events.cljs
  32. 2 1
      src/main/frontend/handler/export.cljs
  33. 19 2
      src/main/frontend/handler/plugin.cljs
  34. 3 0
      src/main/frontend/modules/shortcut/dicts.cljc
  35. 1 1
      src/main/frontend/ui.cljs
  36. 5 1
      src/main/frontend/ui.css
  37. 2 2
      src/main/frontend/util/text.cljs
  38. 3 0
      src/main/logseq/api.cljs
  39. 2 1
      templates/config.edn
  40. 4 4
      tldraw/apps/tldraw-logseq/package.json
  41. 17 18
      tldraw/apps/tldraw-logseq/src/app.tsx
  42. 3 3
      tldraw/apps/tldraw-logseq/src/components/ActionBar/ActionBar.tsx
  43. 0 2
      tldraw/apps/tldraw-logseq/src/components/Button/Button.tsx
  44. 18 18
      tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx
  45. 1 0
      tldraw/apps/tldraw-logseq/src/components/ContextBar/index.ts
  46. 2 2
      tldraw/apps/tldraw-logseq/src/components/Minimap/Minimap.tsx
  47. 7 3
      tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx
  48. 1 1
      tldraw/apps/tldraw-logseq/src/components/StatusBar/StatusBar.tsx
  49. 1 1
      tldraw/apps/tldraw-logseq/src/components/Toolbar/ToolBar.tsx
  50. 0 2
      tldraw/apps/tldraw-logseq/src/components/icons/LogseqIcon.tsx
  51. 1 1
      tldraw/apps/tldraw-logseq/src/components/icons/TablerIcon.tsx
  52. 0 2
      tldraw/apps/tldraw-logseq/src/components/inputs/NumberInput.tsx
  53. 1 1
      tldraw/apps/tldraw-logseq/src/components/inputs/SelectInput.tsx
  54. 1 1
      tldraw/apps/tldraw-logseq/src/components/inputs/ToggleGroupInput.tsx
  55. 7 1
      tldraw/apps/tldraw-logseq/src/components/inputs/ToggleInput.tsx
  56. 2 3
      tldraw/apps/tldraw-logseq/src/hooks/useFileDrop.ts
  57. 11 3
      tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts
  58. 1 1
      tldraw/apps/tldraw-logseq/src/hooks/useQuickAdd.ts
  59. 0 2
      tldraw/apps/tldraw-logseq/src/lib/shapes/BindingIndicator.tsx
  60. 0 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/DotShape.tsx
  61. 0 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx
  62. 2 2
      tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx
  63. 0 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/HighlighterShape.tsx
  64. 1 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/ImageShape.tsx
  65. 1 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx
  66. 5 5
      tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx
  67. 0 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/PolygonShape.tsx
  68. 1 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/TextShape.tsx
  69. 3 3
      tldraw/apps/tldraw-logseq/src/lib/shapes/VideoShape.tsx
  70. 1 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/YouTubeShape.tsx
  71. 0 2
      tldraw/apps/tldraw-logseq/src/lib/shapes/arrow/ArrowHead.tsx
  72. 3 3
      tldraw/apps/tldraw-logseq/src/lib/shapes/style-props.tsx
  73. 0 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/text/LabelMask.tsx
  74. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/BoxTool.tsx
  75. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/DotTool.tsx
  76. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/EllipseTool.tsx
  77. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/EraseTool.tsx
  78. 2 3
      tldraw/apps/tldraw-logseq/src/lib/tools/HTMLTool.tsx
  79. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/HighlighterTool.tsx
  80. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/LineTool.tsx
  81. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/LogseqPortalTool.tsx
  82. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/states/CreatingState.tsx
  83. 2 2
      tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/states/IdleState.tsx
  84. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/PencilTool.tsx
  85. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/PolygonTool.tsx
  86. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/TextTool.tsx
  87. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/YouTubeTool.tsx
  88. 4 0
      tldraw/apps/tldraw-logseq/src/styles.css
  89. 4 3
      tldraw/apps/tldraw-logseq/tsconfig.json
  90. 1 1
      tldraw/apps/tldraw-logseq/tsup.config.ts
  91. 1 4
      tldraw/demo/postcss.config.js
  92. 2 19
      tldraw/package.json
  93. 0 459
      tldraw/packages/core/architecture.tldr
  94. 4 43
      tldraw/packages/core/package.json
  95. 8 1
      tldraw/packages/core/src/constants.ts
  96. 4 3
      tldraw/packages/core/src/lib/TLApi/TLApi.ts
  97. 0 821
      tldraw/packages/core/src/lib/TLApp/TLApp.test.ts
  98. 17 23
      tldraw/packages/core/src/lib/TLApp/TLApp.ts
  99. 0 85
      tldraw/packages/core/src/lib/TLApp/__snapshots__/TLApp.test.ts.snap
  100. 7 5
      tldraw/packages/core/src/lib/TLBaseLineBindingState.ts

+ 1 - 1
deps/db/package.json

@@ -3,6 +3,6 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "^0.6.125"
+    "@logseq/nbb-logseq": "^0.7.133"
   }
 }

+ 4 - 4
deps/db/yarn.lock

@@ -2,10 +2,10 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@^0.6.125":
-  version "0.6.125"
-  resolved "https://registry.yarnpkg.com/@logseq/nbb-logseq/-/nbb-logseq-0.6.125.tgz#197dbb01040f9cfdf7040399b9fbed9b862dee5b"
-  integrity sha512-1UB4Urt6O95Cwwni68B/f05x+wsL+ju+dCGLE47WTvF9F8WQwhiADfWhMbFOt35ImswLSzM1rgVGIMIj0g6fkQ==
+"@logseq/nbb-logseq@^0.7.133":
+  version "0.7.133"
+  resolved "https://registry.yarnpkg.com/@logseq/nbb-logseq/-/nbb-logseq-0.7.133.tgz#793492c6f0bc3089f394795052ca0b8503018161"
+  integrity sha512-eraxs2j1HT4RjxYCB51Rlb3KBx5oihIKoFueB1QHZYnMOwPMLIn6iMzHvyGyEGweqp422PcdDXfK3Nl4iTw/wA==
   dependencies:
     import-meta-resolve "^1.1.1"
 

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

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "^0.6.125"
+    "@logseq/nbb-logseq": "^0.7.133"
   },
   "dependencies": {
     "mldoc": "^1.3.9"

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

@@ -211,7 +211,7 @@
                                              (string/replace " " "-")
                                              (string/replace "_" "-")
                                              (string/replace #"[\"|^|(|)|{|}]+" ""))]
-                                   (if (gp-util/valid-edn-keyword? (str ":" k))
+                                   (if (gp-property/valid-property-name? (str ":" k))
                                      (let [k (if (contains? #{"custom_id" "custom-id"} k)
                                                "id"
                                                k)

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

@@ -47,12 +47,12 @@
 (defn- build-page-entity
   [properties file page-name page ref-tags {:keys [date-formatter db]}]
   (let [alias (:alias properties)
-        alias (if (string? alias) [alias] alias)
-        aliases (and alias
+        alias' (if (string? alias) [alias] alias)
+        aliases (and alias'
                      (seq (remove #(or (= page-name (gp-util/page-name-sanity-lc %))
                                        (string/blank? %)) ;; disable blank alias
-                                  alias)))
-        aliases (->>
+                                  alias')))
+        aliases' (->>
                  (map
                   (fn [alias]
                     (let [page-name (gp-util/page-name-sanity-lc alias)
@@ -70,18 +70,26 @@
                          :block/alias aliases}
                         {:block/name page-name})))
                   aliases)
-                 (remove nil?))]
+                 (remove nil?))
+        [*valid-properties *invalid-properties]
+        ((juxt filter remove)
+         (fn [[k _v]] (gp-property/valid-property-name? (str k))) properties)
+        valid-properties (into {} *valid-properties)
+        invalid-properties (set (map (comp name first) *invalid-properties))]
     (cond->
      (gp-util/remove-nils
       (assoc
        (gp-block/page-name->map page false db true date-formatter)
        :block/file {:file/path (gp-util/path-normalize file)}))
 
-      (seq properties)
-      (assoc :block/properties properties)
+     (seq valid-properties)
+     (assoc :block/properties valid-properties)
 
-      (seq aliases)
-      (assoc :block/alias aliases)
+     (seq invalid-properties)
+     (assoc :block/invalid-properties invalid-properties)
+
+     (seq aliases')
+     (assoc :block/alias aliases')
 
       (:tags properties)
       (assoc :block/tags (let [tags (:tags properties)

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

@@ -21,6 +21,8 @@
   (second (re-find (re-pattern (str property colons "\\s+(.*)"))
                    content)))
 
+(def valid-property-name? gp-util/valid-edn-keyword?)
+
 (defn properties-ast?
   [block]
   (and

+ 4 - 5
deps/graph-parser/src/logseq/graph_parser/util.cljs

@@ -152,12 +152,11 @@
     (normalize-format (keyword (string/lower-case (last (string/split file #"\.")))))))
 
 (defn valid-edn-keyword?
-  [k]
+  "Determine if string is a valid edn keyword"
+  [s]
   (try
-    (let [s (str k)]
-      (and (= \: (first s))
-           (edn/read-string (str "{" s " nil}"))))
-    true
+    (boolean (and (= \: (first s))
+                  (edn/read-string (str "{" s " nil}"))))
     (catch :default _
       false)))
 

+ 11 - 8
deps/graph-parser/test/logseq/graph_parser/nbb_test_runner.cljs

@@ -8,6 +8,7 @@
             [logseq.graph-parser.extract-test]
             [logseq.graph-parser.cli-test]
             [logseq.graph-parser.util.page-ref-test]
+            [logseq.graph-parser.util-test]
             [logseq.graph-parser-test]))
 
 (defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m]
@@ -16,11 +17,13 @@
 
 ;; run this function with: nbb-logseq -m logseq.test.nbb-test-runner/run-tests
 (defn run-tests []
-  (t/run-tests 'logseq.graph-parser.mldoc-test
-               'logseq.graph-parser.text-test
-               'logseq.graph-parser.property-test
-               'logseq.graph-parser.block-test
-               'logseq.graph-parser.extract-test
-               'logseq.graph-parser.cli-test
-               'logseq.graph-parser.util.page-ref-test
-               'logseq.graph-parser-test))
+  (t/run-tests
+   'logseq.graph-parser.mldoc-test
+   'logseq.graph-parser.text-test
+   'logseq.graph-parser.property-test
+   'logseq.graph-parser.block-test
+   'logseq.graph-parser.extract-test
+   'logseq.graph-parser.cli-test
+   'logseq.graph-parser.util.page-ref-test
+   'logseq.graph-parser-test
+   'logseq.graph-parser.util-test))

+ 14 - 0
deps/graph-parser/test/logseq/graph_parser/util_test.cljs

@@ -0,0 +1,14 @@
+(ns logseq.graph-parser.util-test
+  (:require [clojure.test :refer [deftest are]]
+            [logseq.graph-parser.util :as gp-util]))
+
+(deftest valid-edn-keyword?
+  (are [x y]
+       (= (gp-util/valid-edn-keyword? x) y)
+
+       ":foo-bar"  true
+       ":foo!"     true
+       ":foo,bar"  false
+       "4"         false
+       "foo bar"   false
+       "`property" false))

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

@@ -234,3 +234,35 @@
         :desc #{"link"}
         :comma-prop "one, two,three"}
        {:rich-property-values? true}))))
+
+(deftest invalid-properties
+  (let [conn (ldb/start-conn)
+        properties {"foo" "valid"
+                    "[[foo]]" "invalid"
+                    "some,prop" "invalid"}
+        body (str (gp-property/->block-content properties)
+                  "\n- " (gp-property/->block-content properties))]
+    (graph-parser/parse-file conn "foo.md" body {})
+
+    (is (= [{:block/properties {:foo "valid"}
+             :block/invalid-properties #{"[[foo]]" "some,prop"}}]
+           (->> (d/q '[:find (pull ?b [*])
+                       :in $
+                       :where
+                       [?b :block/properties]
+                       [(missing? $ ?b :block/pre-block?)]
+                       [(missing? $ ?b :block/name)]]
+                     @conn)
+                (map first)
+                (map #(select-keys % [:block/properties :block/invalid-properties]))))
+        "Has correct (in)valid block properties")
+
+    (is (= [{:block/properties {:foo "valid"}
+             :block/invalid-properties #{"[[foo]]" "some,prop"}}]
+           (->> (d/q '[:find (pull ?b [*])
+                       :in $
+                       :where [?b :block/properties] [?b :block/name]]
+                     @conn)
+                (map first)
+                (map #(select-keys % [:block/properties :block/invalid-properties]))))
+        "Has correct (in)valid page properties")))

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

@@ -2,10 +2,10 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@^0.6.125":
-  version "0.6.125"
-  resolved "https://registry.yarnpkg.com/@logseq/nbb-logseq/-/nbb-logseq-0.6.125.tgz#197dbb01040f9cfdf7040399b9fbed9b862dee5b"
-  integrity sha512-1UB4Urt6O95Cwwni68B/f05x+wsL+ju+dCGLE47WTvF9F8WQwhiADfWhMbFOt35ImswLSzM1rgVGIMIj0g6fkQ==
+"@logseq/nbb-logseq@^0.7.133":
+  version "0.7.133"
+  resolved "https://registry.yarnpkg.com/@logseq/nbb-logseq/-/nbb-logseq-0.7.133.tgz#793492c6f0bc3089f394795052ca0b8503018161"
+  integrity sha512-eraxs2j1HT4RjxYCB51Rlb3KBx5oihIKoFueB1QHZYnMOwPMLIn6iMzHvyGyEGweqp422PcdDXfK3Nl4iTw/wA==
   dependencies:
     import-meta-resolve "^1.1.1"
 

+ 35 - 13
docs/develop-logseq-on-windows.md

@@ -9,6 +9,7 @@ This is a guide on creating Logseq development environment on Windows with `Powe
 * Node.js 16.x
 * Clojure (follow this [Guidance](https://clojure.org/guides/getting_started#_installation_on_windows))
 * JRE 8 (required for Clojure)
+* Visual Studio (required for desktop app)
 
 (updated 20220218. May confirm via JAVA_VERSION and NODE_VERSION in [THIS FILE](https://github.com/logseq/logseq/blob/master/.github/workflows/build.yml))
 
@@ -28,32 +29,53 @@ This is a guide on creating Logseq development environment on Windows with `Powe
 Congrats! The pre-requisites are ready.
 
 ## Set-up development environment (web app)
+
 The basic idea is replacing the `clojure` commands in [package.json](https://github.com/logseq/logseq/blob/master/package.json) to `clj`.  
-Go to your cloned Logseq repo. Then install dependencies, execute the `clj` equivalent of `yarn watch` via doing the `gulp`'s job manually (as it's not available on Windows). Refer [THIS](#an-example-of-setting-up-proxy-in-powershell) if you want to setup proxy in `PowerShell`.
-* copy files in `resources` to `static`
-* ```
-  yarn
-  clj -M:cljs watch app electron
+Go to your cloned Logseq repo. Then install dependencies, execute the `clj` equivalent of `yarn watch`. Refer [THIS](#an-example-of-setting-up-proxy-in-powershell) if you want to setup proxy in `PowerShell`.
+
+* Copy files in `resources` to `static`
+
+* Compile static assets(css, icons...)
   ```
-* ```
-  yarn css:watch
+  yarn add --dev gulp
+  yarn
+  yarn gulp:watch
   ```
 
+* Open another powershell window, and run `yarn cljs:watch`. Clojure CLI will pull dependencies from Maven and Clojars, build the app and start the development server. Refer [THIS](#set-up-clojure-cli-repository-mirror) if your network access to Maven and Clojars is unstable.
+
 Now you can access the app via `http://localhost:3001` and all changes to the code will be watched.
 
 ## Set-up development environment (desktop)
 To run the desktop app in development mode, after setting up web app development environment, run following commands which are equivalent to `yarn dev-electron-app`:
-* ```
-  cd static
-  yarn
-  yarn electron:dev
-  ```
+
+```
+cd static
+yarn
+yarn electron:dev
+```
+
 The desktop app should pop-up on your screen.
 
+During the build process `node-gyp` may complain that it cannot find Visual Studio. Try building the app in Developer Powershell for VS(shipped with Visual Studio). If this does not work for you, [This issue](https://github.com/nodejs/node-gyp/issues/2203) may be helpful.
+
 ## An example of setting up proxy in PowerShell
 ```
 $env:GLOBAL_AGENT_HTTPS_PROXY='http://<proxy-host>:<proxy-port>'
 $env:ELECTRON_GET_USE_PROXY='true'
 $env:HTTPS_PROXY='http://<proxy-host>:<proxy-port>'
 $env:HTTP_PROXY='http://<proxy-host>:<proxy-port>'
-```
+```
+
+## Set up Clojure CLI repository mirror
+
+add the following pair to `deps.edn`:
+
+```
+:mvn/repos {
+  "central" {:url "https://maven.aliyun.com/repository/public"}
+  "clojars" {:url "https://mirrors.tuna.tsinghua.edu.cn/clojars"}
+}
+```
+
+The mirrors above are friendly to Chinese developers(with bad network), developers with self-hosted repositories can use their own services.

+ 3 - 2
libs/src/LSPlugin.caller.ts

@@ -72,7 +72,7 @@ class LSPluginCaller extends EventEmitter {
     let syncGCTimer: any = 0
     let syncTag = 0
     const syncActors = new Map<number, DeferredActor>()
-    const readyDeferred = deferred(1000 * 5)
+    const readyDeferred = deferred(1000 * 60)
 
     const model: any = this._extendUserModel({
       [LSPMSG_READY]: async (baseInfo) => {
@@ -266,7 +266,8 @@ class LSPluginCaller extends EventEmitter {
     return new Promise((resolve, reject) => {
       timer = setTimeout(() => {
         reject(new Error(`handshake Timeout`))
-      }, 8 * 1000) // 8 secs
+        pt.destroy()
+      }, 4 * 1000) // 4 secs
 
       handshake
         .then((refChild: ParentAPI) => {

+ 25 - 3
libs/src/LSPlugin.core.ts

@@ -1128,6 +1128,7 @@ class LSPluginCore
     | 'registered'
     | 'error'
     | 'unregistered'
+    | 'ready'
     | 'themes-changed'
     | 'theme-selected'
     | 'reset-custom-theme'
@@ -1261,7 +1262,26 @@ class LSPluginCore
 
       await this.loadUserPreferences()
 
-      const externals = new Set(this._userPreferences.externals)
+      let externals = new Set(this._userPreferences.externals)
+
+      // valid externals
+      if (externals?.size) {
+        try {
+          const validatedExternals: Record<string, boolean> = await invokeHostExportedApi(
+            'validate_external_plugins', [...externals]
+          )
+
+          externals = new Set([...Object.entries(validatedExternals)].reduce(
+            (a, [k, v]) => {
+              if (v) {
+                a.push(k)
+              }
+              return a
+            }, []))
+        } catch (e) {
+          console.error('[validatedExternals Error]', e)
+        }
+      }
 
       if (initial) {
         plugins = plugins.concat(
@@ -1287,10 +1307,12 @@ class LSPluginCore
         )
 
         const perfInfo = { o: pluginLocal, s: performance.now(), e: 0 }
-        perfTable.set(pluginLocal.id, perfInfo)
+        perfTable.set(url, perfInfo)
 
         await pluginLocal.load({ indicator: readyIndicator })
 
+        perfInfo.e = performance.now()
+
         const { loadErr } = pluginLocal
 
         if (loadErr) {
@@ -1307,7 +1329,6 @@ class LSPluginCore
           }
         }
 
-        perfInfo.e = performance.now()
 
         pluginLocal.settings?.on('change', (a) => {
           this.emit('settings-changed', pluginLocal.id, a)
@@ -1331,6 +1352,7 @@ class LSPluginCore
       console.error(e)
     } finally {
       this._isRegistering = false
+      this.emit('ready', perfTable)
       debugPerfInfo()
     }
   }

+ 8 - 0
libs/src/postmate/index.ts

@@ -374,6 +374,14 @@ export class Postmate {
       this.frame.src = url
     })
   }
+
+
+  destroy() {
+    if (process.env.NODE_ENV !== 'production') {
+      log('Postmate: Destroying Postmate instance')
+    }
+    this.frame.parentNode.removeChild(this.frame)
+  }
 }
 
 /**

+ 6 - 6
package.json

@@ -45,21 +45,21 @@
         "clean": "gulp clean",
         "test": "run-s cljs:test cljs:run-test",
         "report": "run-s cljs:report",
-        "style:lint": "stylelint \"src/**/*.css\" ",
+        "style:lint": "stylelint \"src/**/*.css\"",
         "gulp:watch": "gulp watch",
         "gulp:build": "cross-env NODE_ENV=production gulp build",
         "css:build": "postcss tailwind.all.css -o static/css/style.css --verbose --env production",
         "css:watch": "cross-env TAILWIND_MODE=watch postcss tailwind.all.css -o static/css/style.css --verbose --watch",
         "cljs:watch": "clojure -M:cljs watch app electron",
         "cljs:app-watch": "clojure -M:cljs watch app",
-        "cljs:electron-watch": "clojure -M:cljs watch app electron --config-merge '{:asset-path \"./js\"}'",
+        "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 --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: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",
-        "cljs:dev-release-app": "clojure -M:cljs release app --config-merge '{:closure-defines {frontend.config/DEV-RELEASE true}}'",
+        "cljs:dev-release-app": "clojure -M:cljs release app --config-merge \"{:closure-defines {frontend.config/DEV-RELEASE true}}\"",
         "cljs:debug": "clojure -M:cljs release app --debug",
         "cljs:report": "clojure -M:cljs run shadow.cljs.build-report app report.html",
         "cljs:build-electron": "clojure -A:cljs compile app electron",
@@ -94,7 +94,7 @@
         "codemirror": "5.58.1",
         "d3-force": "3.0.0",
         "diff": "5.0.0",
-        "electron": "19.0.10",
+        "electron": "19.0.12",
         "electron-dl": "3.3.0",
         "fs": "0.0.1-security",
         "fs-extra": "9.1.0",

File diff suppressed because it is too large
+ 0 - 0
resources/js/lsplugin.core.js


+ 3 - 3
resources/package.json

@@ -12,7 +12,7 @@
     "electron:make": "electron-forge make",
     "electron:make-macos-arm64": "electron-forge make --platform=darwin --arch=arm64",
     "electron:publish:github": "electron-forge publish",
-    "rebuild:better-sqlite3": "electron-rebuild -v 19.0.10 -f -w better-sqlite3",
+    "rebuild:better-sqlite3": "electron-rebuild -v 19.0.12 -f -w better-sqlite3",
     "postinstall": "install-app-deps"
   },
   "config": {
@@ -47,13 +47,13 @@
     "@electron-forge/maker-rpm": "^6.0.0-beta.57",
     "@electron-forge/maker-squirrel": "^6.0.0-beta.57",
     "@electron-forge/maker-zip": "^6.0.0-beta.57",
-    "electron": "19.0.10",
+    "electron": "19.0.12",
     "electron-builder": "^22.11.7",
     "electron-forge-maker-appimage": "trusktr/electron-forge-maker-appimage#patch-1",
     "electron-rebuild": "3.2.5"
   },
   "resolutions": {
-    "**/electron": "19.0.10",
+    "**/electron": "19.0.12",
     "**/node-gyp": "9.0.0"
   }
 }

+ 7 - 0
src/electron/electron/handler.cljs

@@ -305,6 +305,13 @@
 (defmethod handle :getUserDefaultPlugins []
   (utils/get-ls-default-plugins))
 
+(defmethod handle :validateUserExternalPlugins [_win [_ urls]]
+  (zipmap urls (for [url urls]
+                 (try
+                   (and (fs-extra/pathExistsSync url)
+                        (fs-extra/pathExistsSync (path/join url "package.json")))
+                   (catch js/Error _e false)))))
+
 (defmethod handle :relaunchApp []
   (.relaunch app) (.quit app))
 

+ 140 - 149
src/main/frontend/components/block.cljs

@@ -196,25 +196,25 @@
      (ui/resize-consumer
       (if-not (mobile-util/native-ios?)
         (cond->
-         {:className "resize image-resize"
-          :onSizeChanged (fn [value]
-                           (when (and (not @*resizing-image?)
-                                      (some? @size)
-                                      (not= value @size))
-                             (reset! *resizing-image? true))
-                           (reset! size value))
-          :onMouseUp (fn []
-                       (when (and @size @*resizing-image?)
-                         (when-let [block-id (:block/uuid config)]
-                           (let [size (bean/->clj @size)]
-                             (editor-handler/resize-image! block-id metadata full_text size))))
-                       (when @*resizing-image?
+            {:className "resize image-resize"
+             :onSizeChanged (fn [value]
+                              (when (and (not @*resizing-image?)
+                                         (some? @size)
+                                         (not= value @size))
+                                (reset! *resizing-image? true))
+                              (reset! size value))
+             :onMouseUp (fn []
+                          (when (and @size @*resizing-image?)
+                            (when-let [block-id (:block/uuid config)]
+                              (let [size (bean/->clj @size)]
+                                (editor-handler/resize-image! block-id metadata full_text size))))
+                          (when @*resizing-image?
                             ;; TODO: need a better way to prevent the clicking to edit current block
-                         (js/setTimeout #(reset! *resizing-image? false) 200)))
-          :onClick (fn [e]
-                     (when @*resizing-image? (util/stop e)))}
-          (and (:width metadata) (not (util/mobile?)))
-          (assoc :style {:width (:width metadata)}))
+                            (js/setTimeout #(reset! *resizing-image? false) 200)))
+             :onClick (fn [e]
+                        (when @*resizing-image? (util/stop e)))}
+            (and (:width metadata) (not (util/mobile?)))
+            (assoc :style {:width (:width metadata)}))
         {})
       [:div.asset-container {:key "resize-asset-container"}
        [:img.rounded-sm.shadow-xl.relative
@@ -305,7 +305,7 @@
           [:a.asset-ref.is-plaintext {:href (rfe/href :file {:path path})
                                       :on-click (fn [_event]
                                                   (p/let [result (fs/read-file repo-dir path)]
-                                                    (db/set-file-content! repo path result)))}
+                                                    (db/set-file-content! repo path result )))}
            title]
 
           (= ext :pdf)
@@ -426,7 +426,7 @@
 (declare page-reference)
 
 (defn open-page-ref
-  [e page-name redirect-page-name page-name-in-block contents-page?]
+  [e page-name redirect-page-name page-name-in-block contents-page? whiteboard-page?]
   (util/stop e)
   (cond
     (gobj/get e "shiftKey")
@@ -436,6 +436,9 @@
        (:db/id page-entity)
        :page))
 
+    whiteboard-page?
+    (route-handler/redirect-to-whiteboard! page-name)
+
     (not= redirect-page-name page-name)
     (route-handler/redirect-to-page! redirect-page-name)
 
@@ -456,33 +459,14 @@
   (let [tag? (:tag? config)
         config (assoc config :whiteboard-page? whiteboard-page?)]
     [:a
-     {:class (cond-> (if tag? "tag" "page-ref")
+     {:tabindex "0"
+      :class (cond-> (if tag? "tag" "page-ref")
                (:property? config)
-               (str " page-property-key"))
+               (str " page-property-key block-property"))
       :data-ref page-name
-      :on-mouse-down
-      (fn [e]
-        (util/stop e)
-        (cond
-          (gobj/get e "shiftKey")
-          (when-let [page-entity (db/entity [:block/name redirect-page-name])]
-            (state/sidebar-add-block!
-             (state/get-current-repo)
-             (:db/id page-entity)
-             :page))
-
-          whiteboard-page?
-          (route-handler/redirect-to-whiteboard! page-name)
-
-          (not= redirect-page-name page-name)
-          (route-handler/redirect-to-page! redirect-page-name)
-
-          :else
-          (state/pub-event! [:page/create page-name-in-block]))
-        (when (and contents-page?
-                   (util/mobile?)
-                   (state/get-left-sidebar-open?))
-          (ui-handler/close-left-sidebar!)))}
+      :on-mouse-down (fn [e] (open-page-ref e page-name redirect-page-name page-name-in-block contents-page? whiteboard-page?))
+      :on-key-up (fn [e] ((when (= (.-key e) "Enter")
+                           (open-page-ref e page-name redirect-page-name page-name-in-block contents-page? whiteboard-page?))))}
 
      (if (and (coll? children) (seq children))
        (for [child children]
@@ -772,10 +756,11 @@
           block (when db-id (db/pull-block db-id))
           block-type (keyword (get-in block [:block/properties :ls-type]))
           hl-type (get-in block [:block/properties :hl-type])
-          repo (state/get-current-repo)]
+          repo (state/get-current-repo)
+          stop-inner-events? (= block-type :whiteboard-shape)]
       (if (and block (:block/content block))
-        (let [title [:span {:class "block-ref"}
-                     (block-content (assoc config :block-ref? true)
+        (let [title [:span.block-ref
+                     (block-content (assoc config :block-ref? true :stop-events? stop-inner-events?)
                                     block nil (:block/uuid block)
                                     (:slide? config))]
               inner (if label
@@ -1212,25 +1197,25 @@
   (when-let [url (first arguments)]
     (let [results (text-util/get-matched-video url)
           src (match results
-                [_ _ _ (:or "youtube.com" "youtu.be" "y2u.be") _ id _]
-                (if (= (count id) 11) ["youtube-player" id] url)
+                     [_ _ _ (:or "youtube.com" "youtu.be" "y2u.be") _ id _]
+                     (if (= (count id) 11) ["youtube-player" id] url)
 
-                [_ _ _ "youtube-nocookie.com" _ id _]
-                (str "https://www.youtube-nocookie.com/embed/" id)
+                     [_ _ _ "youtube-nocookie.com" _ id _]
+                     (str "https://www.youtube-nocookie.com/embed/" id)
 
-                [_ _ _ "loom.com" _ id _]
-                (str "https://www.loom.com/embed/" id)
+                     [_ _ _ "loom.com" _ id _]
+                     (str "https://www.loom.com/embed/" id)
 
-                [_ _ _ (_ :guard #(string/ends-with? % "vimeo.com")) _ id _]
-                (str "https://player.vimeo.com/video/" id)
+                     [_ _ _ (_ :guard #(string/ends-with? % "vimeo.com")) _ id _]
+                     (str "https://player.vimeo.com/video/" id)
 
-                [_ _ _ "bilibili.com" _ id & query]
-                (str "https://player.bilibili.com/player.html?bvid=" id "&high_quality=1"
-                     (when-let [page (second query)]
-                       (str "&page=" page)))
+                     [_ _ _ "bilibili.com" _ id & query]
+                     (str "https://player.bilibili.com/player.html?bvid=" id "&high_quality=1"
+                          (when-let [page (second query)]
+                            (str "&page=" page)))
 
-                :else
-                url)]
+                     :else
+                     url)]
       (if (and (coll? src)
                (= (first src) "youtube-player"))
         (youtube/youtube-video (last src))
@@ -1427,103 +1412,103 @@
 (defn inline
   [{:keys [html-export?] :as config} item]
   (match item
-    [(:or "Plain" "Spaces") s]
-    s
+         [(:or "Plain" "Spaces") s]
+         s
 
-    ["Superscript" l]
-    (->elem :sup (map-inline config l))
-    ["Subscript" l]
-    (->elem :sub (map-inline config l))
+         ["Superscript" l]
+         (->elem :sup (map-inline config l))
+         ["Subscript" l]
+         (->elem :sub (map-inline config l))
 
-    ["Tag" _]
-    (when-let [s (gp-block/get-tag item)]
-      (let [s (text/page-ref-un-brackets! s)]
-        (page-cp (assoc config :tag? true) {:block/name s})))
+         ["Tag" _]
+         (when-let [s (gp-block/get-tag item)]
+           (let [s (text/page-ref-un-brackets! s)]
+             (page-cp (assoc config :tag? true) {:block/name s})))
 
-    ["Emphasis" [[kind] data]]
-    (emphasis-cp config kind data)
+         ["Emphasis" [[kind] data]]
+         (emphasis-cp config kind data)
 
-    ["Entity" e]
-    [:span {:dangerouslySetInnerHTML
-            {:__html (:html e)}}]
+         ["Entity" e]
+         [:span {:dangerouslySetInnerHTML
+                 {:__html (:html e)}}]
 
-    ["Latex_Fragment" [display s]] ;display can be "Displayed" or "Inline"
-    (if html-export?
-      (latex/html-export s false true)
-      (latex/latex (str (d/squuid)) s false (not= display "Inline")))
+         ["Latex_Fragment" [display s]] ;display can be "Displayed" or "Inline"
+         (if html-export?
+           (latex/html-export s false true)
+           (latex/latex (str (d/squuid)) s false (not= display "Inline")))
 
-    [(:or "Target" "Radio_Target") s]
-    [:a {:id s} s]
+         [(:or "Target" "Radio_Target") s]
+         [:a {:id s} s]
 
-    ["Email" address]
-    (let [{:keys [local_part domain]} address
-          address (str local_part "@" domain)]
-      [:a {:href (str "mailto:" address)} address])
+         ["Email" address]
+         (let [{:keys [local_part domain]} address
+               address (str local_part "@" domain)]
+           [:a {:href (str "mailto:" address)} address])
 
-    ["Nested_link" link]
-    (nested-link config html-export? link)
+         ["Nested_link" link]
+         (nested-link config html-export? link)
 
-    ["Link" link]
-    (link-cp config html-export? link)
+         ["Link" link]
+         (link-cp config html-export? link)
 
-    [(:or "Verbatim" "Code") s]
-    [:code s]
+         [(:or "Verbatim" "Code") s]
+         [:code s]
 
-    ["Inline_Source_Block" x]
-    [:code (:code x)]
+         ["Inline_Source_Block" x]
+         [:code (:code x)]
 
-    ["Export_Snippet" "html" s]
-    (when (not html-export?)
-      [:span {:dangerouslySetInnerHTML
-              {:__html s}}])
+         ["Export_Snippet" "html" s]
+         (when (not html-export?)
+           [:span {:dangerouslySetInnerHTML
+                   {:__html s}}])
 
-    ["Inline_Hiccup" s] ;; String to hiccup
-    (ui/catch-error
-     [:div.warning {:title "Invalid hiccup"} s]
-     (-> (safe-read-string s)
-         (security/remove-javascript-links-in-href)))
+         ["Inline_Hiccup" s] ;; String to hiccup
+         (ui/catch-error
+          [:div.warning {:title "Invalid hiccup"} s]
+          (-> (safe-read-string s)
+              (security/remove-javascript-links-in-href)))
 
-    ["Inline_Html" s]
-    (when (not html-export?)
+         ["Inline_Html" s]
+         (when (not html-export?)
            ;; TODO: how to remove span and only export the content of `s`?
-      [:span {:dangerouslySetInnerHTML {:__html s}}])
-
-    [(:or "Break_Line" "Hard_Break_Line")]
-    [:br]
-
-    ["Timestamp" [(:or "Scheduled" "Deadline") _timestamp]]
-    nil
-    ["Timestamp" ["Date" t]]
-    (timestamp t "Date")
-    ["Timestamp" ["Closed" t]]
-    (timestamp t "Closed")
-    ["Timestamp" ["Range" t]]
-    (range t false)
-    ["Timestamp" ["Clock" ["Stopped" t]]]
-    (range t true)
-    ["Timestamp" ["Clock" ["Started" t]]]
-    (timestamp t "Started")
-
-    ["Cookie" ["Percent" n]]
-    [:span {:class "cookie-percent"}
-     (util/format "[d%%]" n)]
-    ["Cookie" ["Absolute" current total]]
-    [:span {:class "cookie-absolute"}
-     (util/format "[%d/%d]" current total)]
-
-    ["Footnote_Reference" options]
-    (let [{:keys [name]} options
-          encode-name (util/url-encode name)]
-      [:sup.fn
-       [:a {:id (str "fnr." encode-name)
-            :class "footref"
-            :on-click #(route-handler/jump-to-anchor! (str "fn." encode-name))}
-        name]])
-
-    ["Macro" options]
-    (macro-cp config options)
-
-    :else ""))
+           [:span {:dangerouslySetInnerHTML {:__html s}}])
+
+         [(:or "Break_Line" "Hard_Break_Line")]
+         [:br]
+
+         ["Timestamp" [(:or "Scheduled" "Deadline") _timestamp]]
+         nil
+         ["Timestamp" ["Date" t]]
+         (timestamp t "Date")
+         ["Timestamp" ["Closed" t]]
+         (timestamp t "Closed")
+         ["Timestamp" ["Range" t]]
+         (range t false)
+         ["Timestamp" ["Clock" ["Stopped" t]]]
+         (range t true)
+         ["Timestamp" ["Clock" ["Started" t]]]
+         (timestamp t "Started")
+
+         ["Cookie" ["Percent" n]]
+         [:span {:class "cookie-percent"}
+          (util/format "[d%%]" n)]
+         ["Cookie" ["Absolute" current total]]
+         [:span {:class "cookie-absolute"}
+          (util/format "[%d/%d]" current total)]
+
+         ["Footnote_Reference" options]
+         (let [{:keys [name]} options
+               encode-name (util/url-encode name)]
+           [:sup.fn
+            [:a {:id (str "fnr." encode-name)
+                 :class "footref"
+                 :on-click #(route-handler/jump-to-anchor! (str "fn." encode-name))}
+             name]])
+
+         ["Macro" options]
+         (macro-cp config options)
+
+         :else ""))
 
 (rum/defc block-child
   [block]
@@ -1942,10 +1927,10 @@
   (when (seq invalid-properties)
     [:div.invalid-properties.mb-2
      [:div.warning {:title "Invalid properties"}
-      "Invalid property keys: "
+      "Invalid property names: "
       (for [p invalid-properties]
         [:button.p-1.mr-2 p])]
-     [:code "Property key begins with a non-numeric character and can contain alphanumeric characters and . * + ! - _ ? $ % & = < >. If -, + or . are the first character, the second character (if any) must be non-numeric."]]))
+     [:code "Property name begins with a non-numeric character and can contain alphanumeric characters and . * + ! - _ ? $ % & = < >. If -, + or . are the first character, the second character (if any) must be non-numeric."]]))
 
 (rum/defcs timestamp-cp < rum/reactive
   (rum/local false ::show?)
@@ -2103,6 +2088,7 @@
                                                  (merge block (block/parse-title-and-body uuid format pre-block? content)))
         collapsed? (util/collapsed? block)
         block-ref? (:block-ref? config)
+        stop-events? (:stop-events? config)
         block-ref-with-title? (and block-ref? (seq title))
         block-type (or (:ls-type properties) :default)
         content (if (string? content) (string/trim content) "")
@@ -2113,7 +2099,7 @@
         attrs (cond->
                {:blockid       (str uuid)
                 :data-type (name block-type)
-                :style {:width "100%"}}
+                :style {:width "100%" :pointer-events (when stop-events? "none")}}
                 (not block-ref?)
                 (assoc mouse-down-key (fn [e]
                                         (block-content-on-mouse-down e block block-id content edit-input-id))))]
@@ -2129,7 +2115,7 @@
                                (util/clear-selection!)))}
        (not slide?)
        (merge attrs))
-     
+
      [:<>
       [:div.flex.flex-row.justify-between.block-content-inner
        [:div.flex-1.w-full
@@ -2524,7 +2510,10 @@
         *navigating-block (get state ::navigating-block)
         navigating-block (rum/react *navigating-block)
         navigated? (and (not= (:block/uuid block) navigating-block) navigating-block)
-        block (if (or (and custom-query? (empty? (:block/children block)))
+        block (if (or (and custom-query?
+                           (empty? (:block/children block))
+                           (not (and (:dsl-query? config)
+                                     (string/includes? (:query config) "not"))))
                       navigated?)
                 (let [block (db/pull [:block/uuid navigating-block])
                       blocks (db/get-paginated-blocks repo (:db/id block)
@@ -3019,6 +3008,8 @@
                (and (seq result) (or only-blocks? blocks-grouped-by-page?))
                (->hiccup result (cond-> (assoc config
                                                :custom-query? true
+                                               :dsl-query? dsl-query?
+                                               :query query
                                                :breadcrumb-show? (if (some? breadcrumb-show?)
                                                                    breadcrumb-show?
                                                                    true)

+ 1 - 2
src/main/frontend/components/editor.cljs

@@ -1,8 +1,7 @@
 (ns frontend.components.editor
   (:require [clojure.string :as string]
             [frontend.commands :as commands
-             :refer [*first-command-group *matched-block-commands
-                     *matched-commands]]
+             :refer [*first-command-group *matched-block-commands *matched-commands]]
             [frontend.components.block :as block]
             [frontend.components.datetime :as datetime-comp]
             [frontend.components.search :as search]

+ 85 - 74
src/main/frontend/components/page.cljs

@@ -190,6 +190,82 @@
               original-name]])]
          {:default-collapsed? false})]])))
 
+(rum/defc page-title-editor < rum/reactive
+  [{:keys [*input-value *title-value *edit? untitled? page-name old-name title whiteboard-page?]}]
+  (let [input-ref (rum/create-ref)
+        collide? #(and (not= (util/page-name-sanity-lc page-name)
+                             (util/page-name-sanity-lc @*title-value))
+                       (db/page-exists? page-name)
+                       (db/page-exists? @*title-value))
+        confirm-fn (fn []
+                     (let [new-page-name (string/trim @*title-value)]
+                       (ui/make-confirm-modal
+                        {:title         (if (collide?)
+                                          (str "Page “" @*title-value "” already exists, merge to it?")
+                                          (str "Do you really want to change the page name to “" new-page-name "”?"))
+                         :on-confirm    (fn [_e {:keys [close-fn]}]
+                                          (close-fn)
+                                          (page-handler/rename! (or title page-name) @*title-value)
+                                          (reset! *edit? false))
+                         :on-cancel     (fn []
+                                          (reset! *title-value old-name)
+                                          (gobj/set (rum/deref input-ref) "value" old-name)
+                                          (reset! *edit? true)
+                                          (.focus (rum/deref input-ref)))})))
+        rollback-fn #(do
+                       (reset! *title-value old-name)
+                       (gobj/set (rum/deref input-ref) "value" old-name)
+                       (reset! *edit? false)
+                       (when-not untitled? (notification/show! "Illegal page name, can not rename!" :warning)))
+        blur-fn (fn [e]
+                  (when (gp-util/wrapped-by-quotes? @*title-value)
+                    (swap! *title-value gp-util/unquote-string)
+                    (gobj/set (rum/deref input-ref) "value" @*title-value))
+                  (cond
+                    (= old-name @*title-value)
+                    (reset! *edit? false)
+
+                    (string/blank? @*title-value)
+                    (rollback-fn)
+
+                    (and (collide?) whiteboard-page?)
+                    (notification/show! (str "Page “" @*title-value "” already exists!") :error)
+
+                    untitled?
+                    (page-handler/rename! (or title page-name) @*title-value)
+
+                    :else
+                    (state/set-modal! (confirm-fn)))
+                  (util/stop e))]
+    [:span.absolute.inset-0
+     {:class (util/classnames [{:editing @*edit?}])}
+     [:input.edit-input
+      {:type          "text"
+       :ref           input-ref
+       :auto-focus    true
+       :style         {:outline "none"
+                       :width "100%"
+                       :font-weight "inherit"}
+       :auto-complete (if (util/chrome?) "chrome-off" "off") ; off not working here
+       :value         (rum/react *input-value)
+       :on-change     (fn [^js e]
+                        (let [value (util/evalue e)]
+                          (reset! *title-value (string/trim value))
+                          (reset! *input-value value)))
+       :on-blur       blur-fn
+       :on-key-down   (fn [^js e]
+                        (when (= (gobj/get e "key") "Enter")
+                          (blur-fn e)))
+       :placeholder   (when untitled? (t :untitled))
+       :on-key-up     (fn [^js e]
+                            ;; Esc
+                        (when (= 27 (.-keyCode e))
+                          (reset! *title-value old-name)
+                          (reset! *edit? false)))
+       :on-focus (fn []
+                   (when untitled? (reset! *title-value ""))
+                   (js/setTimeout #(when-let [input (rum/deref input-ref)] (.select input))))}]]))
+
 (rum/defcs page-title < rum/reactive
   (rum/local false ::edit?)
   (rum/local "" ::input-value)
@@ -200,7 +276,6 @@
     (let [*title-value (get state ::title-value)
           *edit? (get state ::edit?)
           *input-value (get state ::input-value)
-          input-ref (rum/create-ref)
           repo (state/get-current-repo)
           hls-file? (pdf-assets/hls-file? title)
           whiteboard-page? (model/whiteboard-page? page-name)
@@ -208,51 +283,7 @@
           title (if hls-file?
                   (pdf-assets/human-hls-filename-display title)
                   (if fmt-journal? (date/journal-title->custom-format title) title))
-          old-name (or title page-name)
-          collide? #(and (not= (util/page-name-sanity-lc page-name)
-                               (util/page-name-sanity-lc @*title-value))
-                         (db/page-exists? page-name)
-                         (db/page-exists? @*title-value))
-          confirm-fn (fn []
-                       (let [new-page-name (string/trim @*title-value)]
-                         (ui/make-confirm-modal
-                          {:title         (if (collide?)
-                                            (str "Page “" @*title-value "” already exists, merge to it?")
-                                            (str "Do you really want to change the page name to “" new-page-name "”?"))
-                           :on-confirm    (fn [_e {:keys [close-fn]}]
-                                            (close-fn)
-                                            (page-handler/rename! (or title page-name) @*title-value)
-                                            (reset! *edit? false))
-                           :on-cancel     (fn []
-                                            (reset! *title-value old-name)
-                                            (gobj/set (rum/deref input-ref) "value" old-name)
-                                            (reset! *edit? true)
-                                            (.focus (rum/deref input-ref)))})))
-          rollback-fn #(do
-                         (reset! *title-value old-name)
-                         (gobj/set (rum/deref input-ref) "value" old-name)
-                         (reset! *edit? false)
-                         (when-not untitled? (notification/show! "Illegal page name, can not rename!" :warning)))
-          blur-fn (fn [e]
-                    (when (gp-util/wrapped-by-quotes? @*title-value)
-                      (swap! *title-value gp-util/unquote-string)
-                      (gobj/set (rum/deref input-ref) "value" @*title-value))
-                    (cond
-                      (= old-name @*title-value)
-                      (reset! *edit? false)
-
-                      (string/blank? @*title-value)
-                      (rollback-fn)
-
-                      (and (collide?) whiteboard-page?)
-                      (notification/show! (str "Page “" @*title-value "” already exists!") :error)
-
-                      untitled?
-                      (page-handler/rename! (or title page-name) @*title-value)
-
-                      :else
-                      (state/set-modal! (confirm-fn)))
-                    (util/stop e))]
+          old-name (or title page-name)]
       [:h1.page-title.flex.gap-1
        {:on-mouse-down (fn [e]
                          (when (util/right-click? e)
@@ -271,34 +302,14 @@
        (when (not= icon "") [:span.page-icon icon])
        [:div.page-title-sizer-wrapper.relative
         (when (rum/react *edit?)
-          [:span.absolute.inset-0
-           {:class (util/classnames [{:editing @*edit?}])}
-           [:input.edit-input
-            {:type          "text"
-             :ref           input-ref
-             :auto-focus    true
-             :style         {:outline "none"
-                             :width "100%"
-                             :font-weight "inherit"}
-             :auto-complete (if (util/chrome?) "chrome-off" "off") ; off not working here
-             :value         (rum/react *input-value)
-             :on-change     (fn [^js e]
-                              (let [value (util/evalue e)]
-                                (reset! *title-value (string/trim value))
-                                (reset! *input-value value)))
-             :on-blur       blur-fn
-             :on-key-down   (fn [^js e]
-                              (when (= (gobj/get e "key") "Enter")
-                                (blur-fn e)))
-             :placeholder   (when untitled? (t :untitled))
-             :on-key-up     (fn [^js e]
-                            ;; Esc
-                              (when (= 27 (.-keyCode e))
-                                (reset! *title-value old-name)
-                                (reset! *edit? false)))
-             :on-focus (fn []
-                         (when untitled? (reset! *title-value ""))
-                         (js/setTimeout #(when-let [input (rum/deref input-ref)] (.select input))))}]])
+          (page-title-editor {:*title-value *title-value
+                              :*edit? *edit?
+                              :*input-value *input-value
+                              :title title
+                              :page-name page-name
+                              :old-name old-name
+                              :untitled? untitled?
+                              :whiteboard-page? whiteboard-page?}))
         [:span.title.inline-block
          {:data-value (rum/react *input-value)
           :data-ref page-name

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

@@ -996,6 +996,33 @@
    [current-repo db-restoring? nfs-granted?])
   nil)
 
+(rum/defc perf-tip-content
+  [pid name url]
+  [:div
+   [:span.block.whitespace-normal
+    "This plugin "
+    [:strong.text-red-500 "#" name]
+    " takes too long to load, affecting the application startup time and
+     potentially causing other plugins to fail to load."]
+
+   [:path.opacity-50
+    [:small [:span.pr-1 (ui/icon "folder")] url]]
+
+   [:p
+    (ui/button "Disable now"
+               :small? true
+               :on-click
+               (fn []
+                 (-> (js/LSPluginCore.disable pid)
+                     (p/then #(do
+                                (notification/clear! pid)
+                                (notification/show!
+                                 [:span "The plugin "
+                                  [:strong.text-red-500 "#" name]
+                                  " is disabled."] :success
+                                 true nil 3000)))
+                     (p/catch #(js/console.error %)))))]])
+
 (defn open-plugins-modal!
   []
   (state/set-modal!

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

@@ -36,7 +36,7 @@
                                               (dissoc % lc-reference)))
                        (page-handler/save-filter! page-name @filters-atom))
            :small? true
-           :intent "link"
+           :intent "border-link"
            :key ref-name))))])
 
 (rum/defcs filter-dialog-inner < rum/reactive (rum/local "" ::filterSearch)

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

@@ -113,7 +113,7 @@
   ([on-close]
    (close nil on-close))
   ([class on-close]
-   [:a.close.opacity-50.hover:opacity-100.flex.items-center
+   [:a.close.flex.items-center
     (cond-> {:on-click on-close
              :style {:margin-right -4}}
       class
@@ -214,7 +214,7 @@
                                                             repo
                                                             page
                                                             :page-graph)))}
-          (t :right-side-bar/page)]]
+          (t :right-side-bar/page-graph)]]
 
         [:div.text-sm
          [:button.button.cp__right-sidebar-settings-btn {:on-click (fn [_e]

+ 4 - 0
src/main/frontend/components/right_sidebar.css

@@ -8,6 +8,10 @@ html[data-theme=light] {
       --ls-page-inline-code-bg-color: var(--ls-quaternary-background-color);
       --ls-page-blockquote-bg-color: var(--ls-quaternary-background-color);
     }
+
+    .cp__right-sidebar-topbar button {
+      opacity: 1;
+    }
   }
 }
 

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

@@ -235,7 +235,8 @@
        (repo/repos-dropdown)
 
        [:div.nav-header.flex.gap-1.flex-col
-        (if-let [page (:page default-home)]
+        (let [page (:page default-home)]
+          (if (and page (not (state/enable-journals? (state/get-current-repo))))
           (sidebar-item
            {:class            "home-nav"
             :title            page
@@ -250,7 +251,7 @@
                                    (or (= route-name :all-journals) (= route-name :home)))
             :title            (t :left-side-bar/journals)
             :on-click-handler route-handler/go-to-journals!
-            :icon             "calendar"}))
+            :icon             "calendar"})))
 
         (when (state/enable-flashcards? (state/get-current-repo))
           [:div.flashcards-nav

+ 2 - 18
src/main/frontend/dicts.cljc

@@ -59,7 +59,6 @@
         :right-side-bar/help "Help"
         :right-side-bar/switch-theme "Theme modes"
         :right-side-bar/theme "{1} theme"
-        :right-side-bar/page "Page graph"
         :right-side-bar/recent "Recent"
         :right-side-bar/contents "Contents"
         :right-side-bar/favorites "Favorites"
@@ -388,7 +387,6 @@
         :right-side-bar/help "Hilfe"
         :right-side-bar/switch-theme "Zu {1} Thema wechseln"
         :right-side-bar/theme "{1} Thema"
-        :right-side-bar/page "Seiten-Graph"
         :right-side-bar/recent "Neueste"
         :right-side-bar/contents "Inhalt"
         :right-side-bar/block-ref "Blockreferenz"
@@ -895,7 +893,6 @@
         :right-side-bar/graph-view "Grafiekweergave"
         :right-side-bar/help "Help"
         :right-side-bar/new-page "Nieuwe pagina"
-        :right-side-bar/page "Pagina grafiek"
         :right-side-bar/page-graph "Pagina grafiek"
         :right-side-bar/recent "Recent"
         :right-side-bar/show-journals "Toon Journaals"
@@ -1003,7 +1000,6 @@
         :right-side-bar/help "Aide"
         :right-side-bar/switch-theme "Basculer sur le thème {1}"
         :right-side-bar/theme "Thème {1}"
-        :right-side-bar/page "Graphe de la page"
         :right-side-bar/recent "Récents"
         :right-side-bar/contents "Contenus"
         :right-side-bar/block-ref "Référence des blocs"
@@ -1184,7 +1180,6 @@
            :right-side-bar/help "帮助"
            :right-side-bar/switch-theme "主题模式"
            :right-side-bar/theme "{1}主题"
-           :right-side-bar/page "页面图谱"
            :right-side-bar/recent "最近"
            :right-side-bar/contents "目录"
            :right-side-bar/favorites "收藏"
@@ -1483,7 +1478,6 @@
              :right-side-bar/help "幫助"
              :right-side-bar/switch-theme "主題模式"
              :right-side-bar/theme "{1}主題"
-             :right-side-bar/page "頁面圖譜"
              :right-side-bar/recent "最近"
              :right-side-bar/contents "目錄"
              :right-side-bar/block-ref "塊引用"
@@ -1646,7 +1640,6 @@
         :right-side-bar/help "Hulp"
         :right-side-bar/switch-theme "Skakel oor na die {1} tema"
         :right-side-bar/theme "{1} tema"
-        :right-side-bar/page "Grafiek bladsy"
         :right-side-bar/recent "Onlangs"
         :right-side-bar/contents "Inhoud"
         :right-side-bar/block-ref "Blok verwysing"
@@ -1802,7 +1795,6 @@
         :right-side-bar/help "Ayuda"
         :right-side-bar/switch-theme "Temas"
         :right-side-bar/theme "Tema {1}"
-        :right-side-bar/page "Gráfico de la página"
         :right-side-bar/recent "Reciente"
         :right-side-bar/contents "Contenido"
         :right-side-bar/favorites "Favoritos"
@@ -2123,7 +2115,6 @@
            :right-side-bar/help "Hjelp"
            :right-side-bar/switch-theme "Tema moduser"
            :right-side-bar/theme "{1} tema"
-           :right-side-bar/page "Sidegraf"
            :right-side-bar/recent "Siste"
            :right-side-bar/contents "Innhold"
            :right-side-bar/favorites "Favoritter"
@@ -2442,7 +2433,6 @@
            :right-side-bar/help "Ajuda"
            :right-side-bar/switch-theme "Temas"
            :right-side-bar/theme "Tema {1}"
-           :right-side-bar/page "Gráfico da página"
            :right-side-bar/recent "Recente"
            :right-side-bar/contents "Conteúdo"
            :right-side-bar/favorites "Favoritos"
@@ -2763,7 +2753,6 @@
            :right-side-bar/help "Ajuda"
            :right-side-bar/switch-theme "Temas"
            :right-side-bar/theme "Tema {1}"
-           :right-side-bar/page "Grafo da página"
            :right-side-bar/recent "Recente"
            :right-side-bar/contents "Conteúdo"
            :right-side-bar/favorites "Favoritos"
@@ -3091,7 +3080,6 @@
         :right-side-bar/help "Справка"
         :right-side-bar/switch-theme "Тема"
         :right-side-bar/theme "{1} тема"
-        :right-side-bar/page "Граф страницы"
         :right-side-bar/recent "Недавние"
         :right-side-bar/contents "Содержание"
         :right-side-bar/favorites "Избранные"
@@ -3423,7 +3411,6 @@
         :right-side-bar/help "ヘルプ"
         :right-side-bar/switch-theme "テーマ"
         :right-side-bar/theme "{1} テーマ"
-        :right-side-bar/page "ページグラフ"
         :right-side-bar/recent "最新"
         :right-side-bar/contents "目次"
         :right-side-bar/favorites "Favorites"
@@ -3746,7 +3733,6 @@
         :right-side-bar/help "Aiuto"
         :right-side-bar/switch-theme "Modalità tema"
         :right-side-bar/theme "{1} tema"
-        :right-side-bar/page "Grafico della pagina"
         :right-side-bar/recent "Recenti"
         :right-side-bar/contents "Contenuti"
         :right-side-bar/favorites "Preferiti"
@@ -4075,7 +4061,6 @@
         :right-side-bar/help "Yardım"
         :right-side-bar/switch-theme "Tema modları"
         :right-side-bar/theme "{1} tema"
-        :right-side-bar/page "Sayfa grafiği"
         :right-side-bar/recent "En son"
         :right-side-bar/contents "İçindekiler"
         :right-side-bar/favorites "Sık kullanılanlar"
@@ -4268,7 +4253,8 @@
         :themes "Temelar"
         :developer-mode-alert "Eklenti sistemini etkinleştirmek için uygulamayı yeniden başlatmanız gerekir. Şimdi yeniden başlatmak istiyor musunuz?"
         :relaunch-confirm-to-work "Çalışması için uygulama yeniden başlatılmalı. Şimdi yeniden başlatmak istiyor musunuz?"
-        :import "İçe aktar"
+        :import "İçeri aktar"
+        :importing "İçeri aktarılıyor"
         :join-community "Topluluğa katıl"
         :sponsor-us "Bize Sponsor Olun"
         :discourse-title "Forum sayfamız!"
@@ -4405,7 +4391,6 @@
         :right-side-bar/help "도움말"
         :right-side-bar/switch-theme "테마 모드"
         :right-side-bar/theme "{1} 테마"
-        :right-side-bar/page "페이지 그래프"
         :right-side-bar/recent "최근 페이지"
         :right-side-bar/contents "콘텐츠"
         :right-side-bar/favorites "즐겨찾기"
@@ -4733,7 +4718,6 @@
         :right-side-bar/help "Pomoc"
         :right-side-bar/switch-theme "Zmień motyw"
         :right-side-bar/theme "Motyw {1}"
-        :right-side-bar/page "Graf strony"
         :right-side-bar/recent "Ostatnio edytowane"
         :right-side-bar/contents "Treść"
         :right-side-bar/favorites "Ulubione"

+ 7 - 8
src/main/frontend/handler/block.cljs

@@ -248,18 +248,17 @@
   (reset! *swipe nil))
 
 (defn get-blocks-refed-pages
-  [aliases ref-blocks]
-  (let [refs (->> (mapcat :block/refs ref-blocks)
-                  (remove #(aliases (:db/id %))))
-        pages (->> (map :block/page ref-blocks)
-                   (distinct)
-                   (remove #(aliases (:db/id %))))
-        all-refs (concat pages refs)]
+  [aliases [block & children]]
+  (let [children-refs (mapcat :block/refs children)
+        refs (->>
+              (:block/path-refs block)
+              (concat children-refs)
+              (remove #(aliases (:db/id %))))]
     (keep (fn [ref]
             (when (:block/name ref)
               {:db/id (:db/id ref)
                :block/name (:block/name ref)
-               :block/original-name (:block/original-name ref)})) all-refs)))
+               :block/original-name (:block/original-name ref)})) refs)))
 
 (defn filter-blocks
   [ref-blocks filters]

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

@@ -2735,9 +2735,15 @@
         (do (util/stop e)
             (autopair input-id "(" format nil))
 
+        ;; If you type `xyz`, the last backtick should close the first and not add another autopair
+        ;; If you type several backticks in a row, each one should autopair to accommodate multiline code (```)        
         (contains? (set (keys autopair-map)) key)
-        (do (util/stop e)
-            (autopair input-id key format nil))
+        (let [curr (get-current-input-char input)
+                  prev (util/nth-safe value (dec pos))]
+            (util/stop e) 
+            (if (and (= key "`") (= "`" curr) (not= "`" prev))
+              (cursor/move-cursor-forward input)
+              (autopair input-id key format nil)))
 
         (and hashtag? (or (zero? pos) (re-matches #"\s" (get value (dec pos)))))
         (do

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

@@ -439,6 +439,12 @@
     (plugin-handler/hook-plugin-db :changed payload)
     (plugin-handler/hook-plugin-block-changes payload)))
 
+(defmethod handle :plugin/loader-perf-tip [[_ {:keys [^js o _s _e]}]]
+  (when-let [opts (.-options o)]
+    (notification/show!
+     (plugin/perf-tip-content (.-id o) (.-name opts) (.-url opts))
+     :warning false (.-id o))))
+
 (defmethod handle :backup/broken-config [[_ repo content]]
   (when (and repo content)
     (let [path (config/get-config-path)

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

@@ -25,6 +25,7 @@
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.util.block-ref :as block-ref]
             [logseq.graph-parser.util.page-ref :as page-ref]
+            [logseq.graph-parser.property :as gp-property]
             [promesa.core :as p]
             [frontend.handler.notification :as notification])
   (:import
@@ -450,7 +451,7 @@
           (fn [properties]
             (when (seq properties)
               (->> (filter (fn [[k _v]]
-                             (gp-util/valid-edn-keyword? k)) properties)
+                             (gp-property/valid-property-name? (str k))) properties)
                    (into {}))))))
 
 (defn- blocks [db]

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

@@ -585,7 +585,7 @@
         [:small.scale-250.opacity-70.mb-10.animate-pulse (svg/logo false)]
         [:small.block.text-sm.relative.opacity-50 {:style {:right "-8px"}} text]]])))
 
-(defn init-plugins!
+(defn ^:large-vars/cleanup-todo init-plugins!
   [callback]
 
   (let [el (js/document.createElement "div")]
@@ -665,7 +665,24 @@
                                                           (let [id (keyword id)]
                                                             (when (and settings
                                                                        (contains? (:plugin/installed-plugins @state/state) id))
-                                                              (update-plugin-settings-state id (bean/->clj settings)))))))
+                                                              (update-plugin-settings-state id (bean/->clj settings))))))
+
+                                (.on "ready" (fn [^js perf-table]
+                                               (when-let [plugins (and perf-table (.entries perf-table))]
+                                                 (->> plugins
+                                                      (keep
+                                                       (fn [[_k ^js v]]
+                                                         (when-let [end (and (some-> v (.-o) (.-disabled) (not))
+                                                                             (.-e v))]
+                                                           (when (and (number? end)
+                                                                      ;; valid end time
+                                                                      (> end 0)
+                                                                      ;; greater than 3s
+                                                                      (> (- end (.-s v)) 3000))
+                                                             v))))
+                                                      ((fn [perfs]
+                                                         (doseq [perf perfs]
+                                                           (state/pub-event! [:plugin/loader-perf-tip (bean/->clj perf)])))))))))
 
               default-plugins (get-user-default-plugins)
 

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

@@ -1321,6 +1321,9 @@
              :command.editor/zoom-out                "Düzenlenen bloğu uzaklaştır / Aksi takdirde geri git"
              :command.ui/toggle-brackets             "Köşeli ayraçların görüntülenip görüntülenmeyeceğini değiştir"
              :command.go/search-in-page              "Geçerli sayfada ara"
+             :command.go/electron-find-in-page       "Sayfada bul"
+             :command.go/electron-jump-to-the-next   "Aramanız için bir sonraki eşleşmeye atlayın"
+             :command.go/electron-jump-to-the-previous "Aramanız için bir önceki eşleşmeye atlayın"
              :command.go/search                      "Tam metin araması"
              :command.go/journals                    "Günlüğe git"
              :command.go/backward                    "Geriye git"

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

@@ -231,7 +231,7 @@
                   "exiting" "transition ease-in duration-100 opacity-100"
                   "exited" "transition ease-in duration-100 opacity-0")}
         [:div.rounded-lg.shadow-xs {:style {:max-height "calc(100vh - 200px)"
-                                            :overflow-y "scroll"
+                                            :overflow-y "auto"
                                             :overflow-x "hidden"}}
          [:div.p-4
           [:div.flex.items-start

+ 5 - 1
src/main/frontend/ui.css

@@ -254,7 +254,7 @@ html.is-mobile {
     }
   }
 
-  &[intent='link'] {
+  &[intent='link'], &[intent='border-link'] {
     @apply focus:border-gray-500 dark:hover:text-gray-200;
 
     color: var(--ls-primary-text-color);
@@ -265,6 +265,10 @@ html.is-mobile {
     }
   }
 
+  &[intent='border-link'] {
+      border: 1px solid;
+  }
+
   &.p-1 {
     padding: 0.25rem 0.5rem !important;
   }

+ 2 - 2
src/main/frontend/util/text.cljs

@@ -66,7 +66,7 @@
       result)))
 
 (defn surround-by?
-  "`pos` must be surrounded by `before` and `and` in string `value`, e.g. ((|))"
+  "`pos` must be surrounded by `before` and `end` in string `value`, e.g. ((|))"
   [value pos before end]
   (let [start-pos (if (= :start before) 0 (- pos (count before)))
         end-pos (if (= :end end) (count value) (+ pos (count end)))]
@@ -97,7 +97,7 @@
        acc))))
 
 (defn wrapped-by?
-  "`pos` must be wrapped by `before` and `and` in string `value`, e.g. ((a|b))"
+  "`pos` must be wrapped by `before` and `end` in string `value`, e.g. ((a|b))"
   [value pos before end]
   ;; Increment 'before' matches by (length of before string - 0.5) to make them be just before the cursor position they precede.
   ;; Increment 'after' matches by 0.5 to make them be just after the cursor position they follow.

+ 3 - 0
src/main/logseq/api.cljs

@@ -711,6 +711,9 @@
         (insert_block src content (bean/->js opts))))))
 
 ;; plugins
+(defn ^:export validate_external_plugins [urls]
+  (ipc/ipc :validateUserExternalPlugins urls))
+
 (def ^:export __install_plugin
   (fn [^js manifest]
     (when-let [{:keys [repo id] :as mft} (bean/->clj manifest)]

+ 2 - 1
templates/config.edn

@@ -229,7 +229,8 @@
  ;; :property-pages/excludelist
 
  ;; Enables property values to contain a mix of tags, page-refs, special
- ;; punctuation and free-form text
+ ;; punctuation and free-form text.
+ ;; Re-index current graph for config to take effect
  ;; :rich-property-values? true
 
  ;; logbook setup

+ 4 - 4
tldraw/apps/tldraw-logseq/package.json

@@ -25,7 +25,7 @@
     "@types/react-dom": "^17.0.0",
     "autoprefixer": "^10.4.7",
     "concurrently": "^7.2.1",
-    "esbuild": "^0.14.48",
+    "esbuild": "^0.15.5",
     "mobx": "^6.6.0",
     "mobx-react-lite": "^3.4.0",
     "perfect-freehand": "^1.1.0",
@@ -35,9 +35,9 @@
     "react-dom": "^17.0.0",
     "rimraf": "3.0.2",
     "shadow-cljs": "^2.19.5",
-    "tsup": "^6.1.2",
-    "typescript": "^4.7.3",
-    "zx": "^6.2.4"
+    "tsup": "^6.2.3",
+    "typescript": "^4.8.2",
+    "zx": "^7.0.8"
   },
   "peerDependencies": {
     "react": "^16.8.0 || ^17.0.0 || ^18.0.0",

+ 17 - 18
tldraw/apps/tldraw-logseq/src/app.tsx

@@ -1,6 +1,6 @@
 /* eslint-disable @typescript-eslint/no-non-null-assertion */
 /* eslint-disable @typescript-eslint/no-explicit-any */
-import { deepEqual, TLApp, TLAsset, TLDocumentModel } from '@tldraw/core'
+import { deepEqual, TLDocumentModel } from '@tldraw/core'
 import {
   AppCanvas,
   AppProvider,
@@ -9,17 +9,14 @@ import {
   TLReactToolConstructor,
 } from '@tldraw/react'
 import * as React from 'react'
-import { AppUI } from '~components/AppUI'
-import { ContextBar } from '~components/ContextBar/ContextBar'
+import { AppUI } from './components/AppUI'
+import { ContextBar } from './components/ContextBar'
 import { ContextMenu } from '~components/ContextMenu/ContextMenu'
-import { useFileDrop } from '~hooks/useFileDrop'
-import { usePaste } from '~hooks/usePaste'
-import { useQuickAdd } from '~hooks/useQuickAdd'
-import { LogseqContext, LogseqContextValue } from '~lib/logseq-context'
-import { Shape, shapes } from '~lib/shapes'
+import { useFileDrop } from './hooks/useFileDrop'
+import { usePaste } from './hooks/usePaste'
+import { useQuickAdd } from './hooks/useQuickAdd'
 import {
   BoxTool,
-  // DotTool,
   EllipseTool,
   HighlighterTool,
   HTMLTool,
@@ -28,9 +25,12 @@ import {
   NuEraseTool,
   PencilTool,
   PolygonTool,
+  shapes,
   TextTool,
   YouTubeTool,
-} from '~lib/tools'
+  type Shape,
+} from './lib'
+import { LogseqContext, type LogseqContextValue } from './lib/logseq-context'
 
 const components: TLReactComponents<Shape> = {
   ContextBar: ContextBar,
@@ -103,14 +103,13 @@ export const App = function App({
         model={model}
         {...rest}
       >
-      <ContextMenu>
-        <div className="logseq-tldraw logseq-tldraw-wrapper">
-          <AppCanvas components={components}>
-            <AppUI />
-          </AppCanvas>
-        </div>
-      </ContextMenu>
-
+        <ContextMenu>
+          <div className="logseq-tldraw logseq-tldraw-wrapper">
+            <AppCanvas components={components}>
+              <AppUI />
+            </AppCanvas>
+          </div>
+        </ContextMenu>
       </AppProvider>
     </LogseqContext.Provider>
   )

+ 3 - 3
tldraw/apps/tldraw-logseq/src/components/ActionBar/ActionBar.tsx

@@ -3,9 +3,9 @@
 import { useApp } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'
 import * as React from 'react'
-import { TablerIcon } from '~components/icons'
-import { ZoomMenu } from '~components/ZoomMenu'
-import type { Shape } from '~lib'
+import type { Shape } from '../../lib'
+import { TablerIcon } from '../icons'
+import { ZoomMenu } from '../ZoomMenu'
 
 export const ActionBar = observer(function ActionBar(): JSX.Element {
   const app = useApp<Shape>()

+ 0 - 2
tldraw/apps/tldraw-logseq/src/components/Button/Button.tsx

@@ -1,5 +1,3 @@
-import * as React from 'react'
-
 export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
   children: React.ReactNode
 }

+ 18 - 18
tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx

@@ -2,29 +2,29 @@ import { debounce, Decoration, isNonNullable } from '@tldraw/core'
 import { useApp } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'
 import React from 'react'
-import { TablerIcon } from '~components/icons'
-import { ColorInput } from '~components/inputs/ColorInput'
-import { SelectInput, SelectOption } from '~components/inputs/SelectInput'
-import { TextInput } from '~components/inputs/TextInput'
-import {
-  ToggleGroupInput,
-  ToggleGroupInputOption,
-  ToggleGroupMultipleInput,
-} from '~components/inputs/ToggleGroupInput'
-import { ToggleInput } from '~components/inputs/ToggleInput'
 import type {
+  Shape,
+  LogseqPortalShape,
+  TextShape,
+  HTMLShape,
+  YouTubeShape,
   BoxShape,
+  PolygonShape,
   EllipseShape,
-  HTMLShape,
   LineShape,
-  LogseqPortalShape,
   PencilShape,
-  PolygonShape,
-  Shape,
-  TextShape,
-  YouTubeShape,
-} from '~lib'
-import { LogseqContext } from '~lib/logseq-context'
+} from '../../lib'
+import { LogseqContext } from '../../lib/logseq-context'
+import { TablerIcon } from '../icons'
+import { ColorInput } from '../inputs/ColorInput'
+import { type SelectOption, SelectInput } from '../inputs/SelectInput'
+import { TextInput } from '../inputs/TextInput'
+import {
+  type ToggleGroupInputOption,
+  ToggleGroupInput,
+  ToggleGroupMultipleInput,
+} from '../inputs/ToggleGroupInput'
+import { ToggleInput } from '../inputs/ToggleInput'
 
 export const contextBarActionTypes = [
   // Order matters

+ 1 - 0
tldraw/apps/tldraw-logseq/src/components/ContextBar/index.ts

@@ -1 +1,2 @@
 export * from './ContextBar'
+export * from './contextBarActionFactory'

+ 2 - 2
tldraw/apps/tldraw-logseq/src/components/Minimap/Minimap.tsx

@@ -3,8 +3,8 @@ import { useApp, useMinimapEvents } from '@tldraw/react'
 import { reaction } from 'mobx'
 import { observer } from 'mobx-react-lite'
 import React from 'react'
-import { TablerIcon } from '~components/icons'
-import { PreviewManager } from '~lib'
+import { PreviewManager } from '../../lib'
+import { TablerIcon } from '../icons'
 
 export const Minimap = observer(function Minimap() {
   const app = useApp()

+ 7 - 3
tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx

@@ -2,8 +2,8 @@ import { TLMoveTool, TLSelectTool } from '@tldraw/core'
 import { useApp } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'
 import * as React from 'react'
-import { Button } from '~components/Button'
-import { LogseqIcon, TablerIcon } from '~components/icons'
+import { Button } from '../Button'
+import { TablerIcon, LogseqIcon } from '../icons'
 
 interface ToolButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
   id: string
@@ -97,7 +97,11 @@ export const PrimaryTools = observer(function PrimaryTools() {
     <div className="tl-primary-tools">
       <div className="tl-tools-floating-panel" data-tool-locked={app.settings.isToolLocked}>
         <ToolButton title="Select" id="select" icon="select-cursor" />
-        <ToolButton title="Move" id="move" icon={app.isIn('move.panning') ? "hand-grab" : "hand-stop" } />
+        <ToolButton
+          title="Move"
+          id="move"
+          icon={app.isIn('move.panning') ? 'hand-grab' : 'hand-stop'}
+        />
         <ToolButton title="Draw" id="pencil" icon="ballpen" />
         <ToolButton title="Highlight" id="highlighter" icon="highlight" />
         <ToolButton title="Eraser" id="erase" icon="eraser" />

+ 1 - 1
tldraw/apps/tldraw-logseq/src/components/StatusBar/StatusBar.tsx

@@ -3,7 +3,7 @@
 import * as React from 'react'
 import { observer } from 'mobx-react-lite'
 import { useApp } from '@tldraw/react'
-import type { Shape } from '~lib'
+import type { Shape } from '../../lib'
 
 export const StatusBar = observer(function StatusBar() {
   const app = useApp<Shape>()

+ 1 - 1
tldraw/apps/tldraw-logseq/src/components/Toolbar/ToolBar.tsx

@@ -2,8 +2,8 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 import * as React from 'react'
 import { observer } from 'mobx-react-lite'
-import type { Shape } from '~lib'
 import { useApp } from '@tldraw/react'
+import type { Shape } from '../../lib'
 
 export const ToolBar = observer(function ToolBar(): JSX.Element {
   const app = useApp<Shape>()

File diff suppressed because it is too large
+ 0 - 2
tldraw/apps/tldraw-logseq/src/components/icons/LogseqIcon.tsx


+ 1 - 1
tldraw/apps/tldraw-logseq/src/components/icons/TablerIcon.tsx

@@ -11,7 +11,7 @@ const extendedIcons = [
   'whiteboard-element',
   'select-cursor',
   'text',
-  'connector'
+  'connector',
 ]
 
 const cx = (...args: (string | undefined)[]) => args.join(' ')

+ 0 - 2
tldraw/apps/tldraw-logseq/src/components/inputs/NumberInput.tsx

@@ -1,5 +1,3 @@
-import * as React from 'react'
-
 interface NumberInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
   label: string
 }

+ 1 - 1
tldraw/apps/tldraw-logseq/src/components/inputs/SelectInput.tsx

@@ -1,6 +1,6 @@
 import * as React from 'react'
 import * as Select from '@radix-ui/react-select'
-import { TablerIcon } from '~components/icons'
+import { TablerIcon } from '../icons'
 
 export interface SelectOption {
   value: string

+ 1 - 1
tldraw/apps/tldraw-logseq/src/components/inputs/ToggleGroupInput.tsx

@@ -1,5 +1,5 @@
 import * as ToggleGroup from '@radix-ui/react-toggle-group'
-import { TablerIcon } from '~components/icons'
+import { TablerIcon } from '../icons'
 
 export interface ToggleGroupInputOption {
   value: string

+ 7 - 1
tldraw/apps/tldraw-logseq/src/components/inputs/ToggleInput.tsx

@@ -6,7 +6,13 @@ interface ToggleInputProps extends React.HTMLAttributes<HTMLElement> {
   onPressedChange: (value: boolean) => void
 }
 
-export function ToggleInput({ toggle = true, pressed, onPressedChange, className, ...rest }: ToggleInputProps) {
+export function ToggleInput({
+  toggle = true,
+  pressed,
+  onPressedChange,
+  className,
+  ...rest
+}: ToggleInputProps) {
   return (
     <Toggle.Root
       {...rest}

+ 2 - 3
tldraw/apps/tldraw-logseq/src/hooks/useFileDrop.ts

@@ -1,8 +1,7 @@
-import { fileToBase64, getSizeFromSrc, TLAsset, uniqueId } from '@tldraw/core'
 import type { TLReactCallbacks } from '@tldraw/react'
 import * as React from 'react'
-import type { Shape } from '~lib'
-import type { LogseqContextValue } from '~lib/logseq-context'
+import type { Shape } from '../lib'
+import type { LogseqContextValue } from '../lib/logseq-context'
 import { usePaste } from './usePaste'
 
 export function useFileDrop(context: LogseqContextValue) {

+ 11 - 3
tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts

@@ -11,8 +11,15 @@ import type { TLReactCallbacks } from '@tldraw/react'
 import Vec from '@tldraw/vec'
 import * as React from 'react'
 import { NIL as NIL_UUID } from 'uuid'
-import { HTMLShape, LogseqPortalShape, Shape, YouTubeShape, ImageShape, VideoShape } from '~lib'
-import type { LogseqContextValue } from '~lib/logseq-context'
+import {
+  type Shape,
+  HTMLShape,
+  YouTubeShape,
+  LogseqPortalShape,
+  VideoShape,
+  ImageShape,
+} from '../lib'
+import type { LogseqContextValue } from '../lib/logseq-context'
 
 const isValidURL = (url: string) => {
   try {
@@ -200,7 +207,8 @@ export function usePaste(context: LogseqContextValue) {
       function handleURL(rawText: string) {
         if (isValidURL(rawText)) {
           const isYoutubeUrl = (url: string) => {
-            const youtubeRegex = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/
+            const youtubeRegex =
+              /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/
             return youtubeRegex.test(url)
           }
           if (isYoutubeUrl(rawText)) {

+ 1 - 1
tldraw/apps/tldraw-logseq/src/hooks/useQuickAdd.ts

@@ -1,6 +1,6 @@
 import type { TLReactCallbacks } from '@tldraw/react'
 import React from 'react'
-import type { Shape } from '~lib'
+import type { Shape } from '../lib'
 
 export function useQuickAdd() {
   return React.useCallback<TLReactCallbacks<Shape>['onCanvasDBClick']>(async app => {

+ 0 - 2
tldraw/apps/tldraw-logseq/src/lib/shapes/BindingIndicator.tsx

@@ -1,5 +1,3 @@
-import * as React from 'react'
-
 interface BindingIndicatorProps {
   strokeWidth: number
   size: number[]

+ 0 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/DotShape.tsx

@@ -1,4 +1,3 @@
-import * as React from 'react'
 import { TLDotShape, TLDotShapeProps } from '@tldraw/core'
 import { SVGContainer, TLComponentProps } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'

+ 0 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx

@@ -1,5 +1,4 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
-import * as React from 'react'
 import { TLEllipseShapeProps, TLEllipseShape } from '@tldraw/core'
 import { SVGContainer, TLComponentProps } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'

+ 2 - 2
tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx

@@ -5,8 +5,8 @@ import Vec from '@tldraw/vec'
 import { action, computed } from 'mobx'
 import { observer } from 'mobx-react-lite'
 import * as React from 'react'
-import { useCameraMovingRef } from '~hooks/useCameraMoving'
-import type { Shape, SizeLevel } from '~lib'
+import type { SizeLevel, Shape } from '.'
+import { useCameraMovingRef } from '../../hooks/useCameraMoving'
 import { withClampedStyles } from './style-props'
 
 export interface HTMLShapeProps extends TLBoxShapeProps {

+ 0 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/HighlighterShape.tsx

@@ -1,5 +1,4 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
-import * as React from 'react'
 import { SvgPathUtils, TLDrawShape, TLDrawShapeProps } from '@tldraw/core'
 import { SVGContainer, TLComponentProps } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/ImageShape.tsx

@@ -3,7 +3,7 @@ import * as React from 'react'
 import { HTMLContainer, TLComponentProps } from '@tldraw/react'
 import { TLAsset, TLImageShape, TLImageShapeProps } from '@tldraw/core'
 import { observer } from 'mobx-react-lite'
-import { LogseqContext } from '~lib/logseq-context'
+import { LogseqContext } from '../logseq-context'
 
 export interface ImageShapeProps extends TLImageShapeProps {
   type: 'image'

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx

@@ -166,7 +166,7 @@ export class LineShape extends TLLineShape<LineShapeProps> {
             stroke,
             fill,
             strokeWidth,
-            strokeType
+            strokeType,
           }}
           start={start.point}
           end={end.point}

+ 5 - 5
tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx

@@ -12,11 +12,11 @@ import Vec from '@tldraw/vec'
 import { action, computed, makeObservable } from 'mobx'
 import { observer } from 'mobx-react-lite'
 import * as React from 'react'
-import { TablerIcon } from '~components/icons'
-import { TextInput } from '~components/inputs/TextInput'
-import { useCameraMovingRef } from '~hooks/useCameraMoving'
-import type { Shape, SizeLevel } from '~lib'
-import { LogseqContext, SearchResult } from '~lib/logseq-context'
+import type { SizeLevel, Shape } from '.'
+import { TablerIcon } from '../../components/icons'
+import { TextInput } from '../../components/inputs/TextInput'
+import { useCameraMovingRef } from '../../hooks/useCameraMoving'
+import { LogseqContext, type SearchResult } from '../logseq-context'
 import { CustomStyleProps, withClampedStyles } from './style-props'
 
 const HEADER_HEIGHT = 40

+ 0 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/PolygonShape.tsx

@@ -1,5 +1,4 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
-import * as React from 'react'
 import { TLPolygonShape, TLPolygonShapeProps } from '@tldraw/core'
 import { SVGContainer, TLComponentProps } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/TextShape.tsx

@@ -4,7 +4,7 @@ import { HTMLContainer, TLComponentProps, TLTextMeasure } from '@tldraw/react'
 import { action, computed } from 'mobx'
 import { observer } from 'mobx-react-lite'
 import * as React from 'react'
-import type { SizeLevel } from '~lib'
+import type { SizeLevel } from '.'
 import { CustomStyleProps, withClampedStyles } from './style-props'
 import { TextAreaUtils } from './text/TextAreaUtils'
 

+ 3 - 3
tldraw/apps/tldraw-logseq/src/lib/shapes/VideoShape.tsx

@@ -3,9 +3,9 @@ import { TLBoxShape, TLBoxShapeProps } from '@tldraw/core'
 import { HTMLContainer, TLComponentProps, useApp } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'
 import * as React from 'react'
-import { useCameraMovingRef } from '~hooks/useCameraMoving'
-import type { Shape } from '~lib'
-import { LogseqContext } from '~lib/logseq-context'
+import type { Shape } from '.'
+import { useCameraMovingRef } from '../../hooks/useCameraMoving'
+import { LogseqContext } from '../logseq-context'
 
 export interface VideoShapeProps extends TLBoxShapeProps {
   type: 'video'

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/YouTubeShape.tsx

@@ -56,7 +56,7 @@ export class YouTubeShape extends TLBoxShape<YouTubeShapeProps> {
         <div
           className="rounded-lg w-full h-full relative overflow-hidden shadow-xl"
           style={{
-            pointerEvents: (isEditing || isSelected) ? 'all' : 'none',
+            pointerEvents: isEditing || isSelected ? 'all' : 'none',
             userSelect: 'none',
           }}
         >

+ 0 - 2
tldraw/apps/tldraw-logseq/src/lib/shapes/arrow/ArrowHead.tsx

@@ -1,5 +1,3 @@
-import * as React from 'react'
-
 export interface ArrowheadProps {
   left: number[]
   middle: number[]

+ 3 - 3
tldraw/apps/tldraw-logseq/src/lib/shapes/style-props.tsx

@@ -1,7 +1,7 @@
 import { darken } from 'polished'
-import { withFillShapes } from '~components/ContextBar/contextBarActionFactory'
-import type { Shape } from '~lib'
-import { getComputedColor } from '~lib/color'
+import type { Shape } from '.'
+import { withFillShapes } from '../../components/ContextBar/contextBarActionFactory'
+import { getComputedColor } from '../color'
 
 export interface CustomStyleProps {
   stroke: string

+ 0 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/text/LabelMask.tsx

@@ -1,5 +1,4 @@
 import type { TLBounds } from '@tldraw/core'
-import * as React from 'react'
 
 interface WithLabelMaskProps {
   id: string

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/BoxTool.tsx

@@ -1,6 +1,6 @@
 import { TLBoxTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
-import { Shape, BoxShape } from '~lib'
+import { BoxShape, type Shape } from '../shapes'
 
 export class BoxTool extends TLBoxTool<BoxShape, Shape, TLReactEventMap> {
   static id = 'box'

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/DotTool.tsx

@@ -1,6 +1,6 @@
 import { TLDotTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
-import { Shape, DotShape } from '~lib/shapes'
+import { DotShape, type Shape } from '../shapes'
 
 export class DotTool extends TLDotTool<DotShape, Shape, TLReactEventMap> {
   static id = 'dot'

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/EllipseTool.tsx

@@ -1,6 +1,6 @@
 import { TLBoxTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
-import { EllipseShape, Shape } from '~lib'
+import { EllipseShape, type Shape } from '../shapes'
 
 export class EllipseTool extends TLBoxTool<EllipseShape, Shape, TLReactEventMap> {
   static id = 'ellipse'

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/EraseTool.tsx

@@ -1,6 +1,6 @@
 import { TLEraseTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
-import type { Shape } from '~lib'
+import type { Shape } from '../shapes'
 
 export class NuEraseTool extends TLEraseTool<Shape, TLReactEventMap> {
   static id = 'erase'

+ 2 - 3
tldraw/apps/tldraw-logseq/src/lib/tools/HTMLTool.tsx

@@ -1,11 +1,10 @@
 import { TLBoxTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
-import { HTMLShape, Shape } from '~lib/shapes'
+import { HTMLShape, type Shape } from '../shapes'
 
 export class HTMLTool extends TLBoxTool<HTMLShape, Shape, TLReactEventMap> {
   static id = 'youtube'
   Shape = HTMLShape
 }
 
-export { }
-
+export {}

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/HighlighterTool.tsx

@@ -1,6 +1,6 @@
 import { TLDrawTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
-import { HighlighterShape, Shape } from '~lib'
+import { HighlighterShape, type Shape } from '../shapes'
 
 export class HighlighterTool extends TLDrawTool<HighlighterShape, Shape, TLReactEventMap> {
   static id = 'highlighter'

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/LineTool.tsx

@@ -1,6 +1,6 @@
 import { TLLineTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
-import { Shape, LineShape } from '~lib'
+import { LineShape, type Shape } from '../shapes'
 
 // @ts-expect-error maybe later
 export class LineTool extends TLLineTool<LineShape, Shape, TLReactEventMap> {

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/LogseqPortalTool.tsx

@@ -1,7 +1,7 @@
 import { TLApp, TLEvents, TLTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
 import Vec from '@tldraw/vec'
-import { LogseqPortalShape, Shape } from '~lib/shapes'
+import { type Shape, LogseqPortalShape } from '../../shapes'
 import { CreatingState, IdleState } from './states'
 
 export class LogseqPortalTool extends TLTool<

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/states/CreatingState.tsx

@@ -2,7 +2,7 @@ import { TLApp, TLTargetType, TLToolState, uniqueId } from '@tldraw/core'
 import type { TLReactEventMap, TLReactEvents } from '@tldraw/react'
 import Vec from '@tldraw/vec'
 import { transaction } from 'mobx'
-import { LogseqPortalShape, Shape } from '~lib/shapes'
+import { type Shape, LogseqPortalShape } from '../../../shapes'
 import type { LogseqPortalTool } from '../LogseqPortalTool'
 
 export class CreatingState extends TLToolState<

+ 2 - 2
tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/states/IdleState.tsx

@@ -1,6 +1,6 @@
-import { TLApp, TLCursor, TLStateEvents, TLToolState } from '@tldraw/core'
+import { TLApp, TLCursor, TLToolState } from '@tldraw/core'
 import type { TLReactEventMap, TLReactEvents } from '@tldraw/react'
-import type { Shape } from '~lib/shapes'
+import type { Shape } from '../../../shapes'
 import type { LogseqPortalTool } from '../LogseqPortalTool'
 
 export class IdleState extends TLToolState<

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/PencilTool.tsx

@@ -1,6 +1,6 @@
 import { TLDrawTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
-import { PencilShape, Shape } from '~lib'
+import { PencilShape, type Shape } from '../shapes'
 
 export class PencilTool extends TLDrawTool<PencilShape, Shape, TLReactEventMap> {
   static id = 'pencil'

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/PolygonTool.tsx

@@ -1,6 +1,6 @@
 import { TLBoxTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
-import { PolygonShape, Shape } from '~lib'
+import { PolygonShape, type Shape } from '../shapes'
 
 export class PolygonTool extends TLBoxTool<PolygonShape, Shape, TLReactEventMap> {
   static id = 'polygon'

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/TextTool.tsx

@@ -1,6 +1,6 @@
 import { TLTextTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
-import { Shape, TextShape } from '~lib'
+import { TextShape, type Shape } from '../shapes'
 
 export class TextTool extends TLTextTool<TextShape, Shape, TLReactEventMap> {
   static id = 'text'

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/YouTubeTool.tsx

@@ -1,6 +1,6 @@
 import { TLBoxTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
-import { Shape, YouTubeShape } from '~lib/shapes'
+import { YouTubeShape, type Shape } from '../shapes'
 
 export class YouTubeTool extends TLBoxTool<YouTubeShape, Shape, TLReactEventMap> {
   static id = 'youtube'

+ 4 - 0
tldraw/apps/tldraw-logseq/src/styles.css

@@ -16,6 +16,10 @@
 
 .logseq-tldraw-wrapper {
   @apply flex flex-col w-full h-full relative;
+
+  img {
+    -webkit-user-drag: none;
+  }
 }
 
 .tl-toolbar {

+ 4 - 3
tldraw/apps/tldraw-logseq/tsconfig.json

@@ -3,10 +3,11 @@
   "exclude": ["node_modules", "dist", "docs", "tsup.config.ts"],
   "compilerOptions": {
     "outDir": "./dist/types",
-    "rootDir": "src",
-    "baseUrl": ".",
     "paths": {
-      "~*": ["src/*"]
+      "@tldraw/core": ["../../../core/src/*"],
+      "@tldraw/react": ["../../../react/src/*"],
+      "@tldraw/vec": ["../../../utils/vec/src/*"],
+      "@tldraw/intersect": ["../../../utils/intersect/src/*"]
     }
   },
   "references": [

+ 1 - 1
tldraw/apps/tldraw-logseq/tsup.config.ts

@@ -5,7 +5,7 @@ export default defineConfig({
   platform: 'browser',
   format: ['cjs'],
   entry: ['src/index.ts'],
-  clean: false,
+  clean: true,
   loader: {
     '.png': 'base64',
   },

+ 1 - 4
tldraw/demo/postcss.config.js

@@ -5,10 +5,7 @@ module.exports = {
     'postcss-import-ext-glob': {},
     'tailwindcss/nesting': {},
     tailwindcss: {
-      content: [
-        './**/*.jsx',
-        '../apps/**/*.{js,jsx,ts,tsx}',
-      ]
+      content: ['./**/*.jsx', '../apps/**/*.{js,jsx,ts,tsx}'],
     },
     autoprefixer: {},
   },

+ 2 - 19
tldraw/package.json

@@ -17,30 +17,14 @@
     "demo"
   ],
   "scripts": {
-    "build": "lerna run build --stream",
-    "build:shapes": "lerna run build:shapes --stream",
-    "build:tools": "lerna run build:tools --stream",
-    "build:packages": "lerna run build:packages --stream",
-    "start:shapes": "lerna run start:shapes --stream",
-    "start:tools": "lerna run start:tools --stream",
-    "start": "lerna run start --stream --parallel",
-    "start:packages": "lerna run start:packages --parallel",
+    "build": "cd apps/tldraw-logseq && yarn build",
+    "postinstall": "yarn build",
     "dev": "cd demo && yarn dev",
     "fix:style": "yarn run pretty-quick",
-    "lerna": "lerna",
-    "test": "lerna run test --stream",
-    "test:ci": "lerna run test:ci --stream",
-    "test:watch": "lerna run test:watch --stream",
-    "docs": "lerna run typedoc",
-    "docs:watch": "lerna run typedoc --watch",
-    "postinstall": "yarn build:packages",
     "pretty-quick": "pretty-quick"
   },
   "devDependencies": {
     "@swc-node/jest": "^1.5.2",
-    "@testing-library/jest-dom": "^5.16.4",
-    "@testing-library/react": "^13.3.0",
-    "@types/jest": "^28.1.1",
     "@types/node": "^17.0.42",
     "@types/react": "^17.0.0",
     "@types/react-dom": "^17.0.0",
@@ -49,7 +33,6 @@
     "eslint": "^8.17.0",
     "fake-indexeddb": "^3.1.8",
     "init-package-json": "^3.0.2",
-    "jest": "^28.1.1",
     "lerna": "^5.1.1",
     "lint-staged": "^13.0.1",
     "prettier": "^2.6.2",

+ 0 - 459
tldraw/packages/core/architecture.tldr

@@ -1,459 +0,0 @@
-{
-  "document": {
-    "id": "doc",
-    "name": "New Document",
-    "version": 14,
-    "pages": {
-      "page": {
-        "id": "page",
-        "name": "Page 1",
-        "childIndex": 1,
-        "shapes": {
-          "ae6376d5-72ef-4973-0b26-9be1e92e8633": {
-            "id": "ae6376d5-72ef-4973-0b26-9be1e92e8633",
-            "type": "text",
-            "name": "Text",
-            "parentId": "page",
-            "childIndex": 1,
-            "point": [
-              105.76,
-              169.98
-            ],
-            "rotation": 0,
-            "text": "App",
-            "style": {
-              "color": "black",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "font": "script",
-              "textAlign": "middle"
-            }
-          },
-          "0eb29f9f-f5ab-4478-1af6-2de8f9248423": {
-            "id": "0eb29f9f-f5ab-4478-1af6-2de8f9248423",
-            "type": "text",
-            "name": "Text",
-            "parentId": "page",
-            "childIndex": 2,
-            "point": [
-              115.25,
-              236.13
-            ],
-            "rotation": 0,
-            "text": "Tool",
-            "style": {
-              "color": "black",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "font": "script",
-              "textAlign": "middle"
-            }
-          },
-          "db073a1b-6cfb-4eaf-2742-46fc81e48aee": {
-            "id": "db073a1b-6cfb-4eaf-2742-46fc81e48aee",
-            "type": "text",
-            "name": "Text",
-            "parentId": "page",
-            "childIndex": 3,
-            "point": [
-              150.56,
-              318.81
-            ],
-            "rotation": 0,
-            "text": "State",
-            "style": {
-              "color": "black",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "font": "script",
-              "textAlign": "middle"
-            }
-          },
-          "be3325cf-a4f1-4012-0842-1321de9f473b": {
-            "id": "be3325cf-a4f1-4012-0842-1321de9f473b",
-            "type": "rectangle",
-            "name": "Rectangle",
-            "parentId": "page",
-            "childIndex": 0.5625,
-            "point": [
-              126.56,
-              308.09
-            ],
-            "size": [
-              104.99,
-              67.45
-            ],
-            "rotation": 0,
-            "style": {
-              "color": "black",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "textAlign": "middle"
-            }
-          },
-          "fca5189a-078f-4212-02d5-ee491c56dedb": {
-            "id": "fca5189a-078f-4212-02d5-ee491c56dedb",
-            "type": "rectangle",
-            "name": "Rectangle",
-            "parentId": "page",
-            "childIndex": 0.0625,
-            "point": [
-              106.53,
-              228.98
-            ],
-            "size": [
-              268.64,
-              165.02
-            ],
-            "rotation": 0,
-            "style": {
-              "color": "black",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "textAlign": "middle"
-            }
-          },
-          "c2ea2ed5-8c4f-47d5-37c7-2c5b7a60bf5d": {
-            "id": "c2ea2ed5-8c4f-47d5-37c7-2c5b7a60bf5d",
-            "type": "rectangle",
-            "name": "Rectangle",
-            "parentId": "page",
-            "childIndex": 0.03125,
-            "point": [
-              79.97,
-              165.72
-            ],
-            "size": [
-              604.49,
-              243.84
-            ],
-            "rotation": 0,
-            "style": {
-              "color": "black",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "textAlign": "middle"
-            }
-          },
-          "7160c909-b728-4cc4-32c8-a2c254d0c0b4": {
-            "id": "7160c909-b728-4cc4-32c8-a2c254d0c0b4",
-            "type": "rectangle",
-            "name": "Rectangle",
-            "parentId": "page",
-            "childIndex": 0.5625,
-            "point": [
-              393.05,
-              228.98
-            ],
-            "size": [
-              268.64,
-              165.02
-            ],
-            "rotation": 0,
-            "style": {
-              "color": "gray",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "textAlign": "middle"
-            }
-          },
-          "ea5de193-8d92-47d6-283c-3de5805e4c75": {
-            "id": "ea5de193-8d92-47d6-283c-3de5805e4c75",
-            "type": "text",
-            "name": "Text",
-            "parentId": "page",
-            "childIndex": 3,
-            "point": [
-              401.77,
-              236.13
-            ],
-            "rotation": 0,
-            "text": "Tool",
-            "style": {
-              "color": "gray",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "font": "script",
-              "textAlign": "middle"
-            }
-          },
-          "f79712d8-d342-4127-39ee-272904e440fd": {
-            "id": "f79712d8-d342-4127-39ee-272904e440fd",
-            "type": "text",
-            "name": "Text",
-            "parentId": "page",
-            "childIndex": 3,
-            "point": [
-              268.08,
-              318.81
-            ],
-            "rotation": 0,
-            "text": "State",
-            "style": {
-              "color": "gray",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "font": "script",
-              "textAlign": "middle"
-            }
-          },
-          "c3944938-9d11-476f-02e6-eb80086d2642": {
-            "id": "c3944938-9d11-476f-02e6-eb80086d2642",
-            "type": "rectangle",
-            "name": "Rectangle",
-            "parentId": "page",
-            "childIndex": 0.5625,
-            "point": [
-              239.83,
-              308.09
-            ],
-            "size": [
-              103.74,
-              67.45
-            ],
-            "rotation": 0,
-            "style": {
-              "color": "gray",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "textAlign": "middle"
-            }
-          },
-          "11b741b9-ec58-4b0c-33fe-69fd1cce889b": {
-            "id": "11b741b9-ec58-4b0c-33fe-69fd1cce889b",
-            "type": "text",
-            "name": "Text",
-            "parentId": "page",
-            "childIndex": 3,
-            "point": [
-              437.35,
-              318.81
-            ],
-            "rotation": 0,
-            "text": "State",
-            "style": {
-              "color": "gray",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "font": "script",
-              "textAlign": "middle"
-            }
-          },
-          "8512a2c1-3913-483f-0496-0b88c2b98b3a": {
-            "id": "8512a2c1-3913-483f-0496-0b88c2b98b3a",
-            "type": "rectangle",
-            "name": "Rectangle",
-            "parentId": "page",
-            "childIndex": 0.5625,
-            "point": [
-              413.35,
-              308.09
-            ],
-            "size": [
-              104.99,
-              67.45
-            ],
-            "rotation": 0,
-            "style": {
-              "color": "gray",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "textAlign": "middle"
-            }
-          },
-          "04995c47-7a34-4513-071c-62d8330627d3": {
-            "id": "04995c47-7a34-4513-071c-62d8330627d3",
-            "type": "text",
-            "name": "Text",
-            "parentId": "page",
-            "childIndex": 4,
-            "point": [
-              554.87,
-              318.81
-            ],
-            "rotation": 0,
-            "text": "State",
-            "style": {
-              "color": "gray",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "font": "script",
-              "textAlign": "middle"
-            }
-          },
-          "4c68dd3f-0956-4429-29eb-203cc85efb50": {
-            "id": "4c68dd3f-0956-4429-29eb-203cc85efb50",
-            "type": "rectangle",
-            "name": "Rectangle",
-            "parentId": "page",
-            "childIndex": 1,
-            "point": [
-              526.62,
-              308.09
-            ],
-            "size": [
-              103.74,
-              67.45
-            ],
-            "rotation": 0,
-            "style": {
-              "color": "gray",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "textAlign": "middle"
-            }
-          },
-          "9268086e-d5a6-42b2-3a33-2d9670505d3e": {
-            "id": "9268086e-d5a6-42b2-3a33-2d9670505d3e",
-            "type": "arrow",
-            "name": "Arrow",
-            "parentId": "page",
-            "childIndex": 5,
-            "point": [
-              194.91,
-              215.71
-            ],
-            "rotation": 0,
-            "bend": 0,
-            "handles": {
-              "start": {
-                "id": "start",
-                "index": 0,
-                "point": [
-                  1.1,
-                  0
-                ],
-                "canBind": true
-              },
-              "end": {
-                "id": "end",
-                "index": 1,
-                "point": [
-                  0,
-                  36.46
-                ],
-                "canBind": true
-              },
-              "bend": {
-                "id": "bend",
-                "index": 2,
-                "point": [
-                  0.55,
-                  18.23
-                ]
-              }
-            },
-            "decorations": {
-              "end": "arrow"
-            },
-            "style": {
-              "color": "white",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "textAlign": "middle"
-            }
-          },
-          "e84d07ea-09ae-49b3-2bf0-7055b74c325f": {
-            "id": "e84d07ea-09ae-49b3-2bf0-7055b74c325f",
-            "type": "arrow",
-            "name": "Arrow",
-            "parentId": "page",
-            "childIndex": 6,
-            "point": [
-              193.99,
-              294.36
-            ],
-            "rotation": 0,
-            "bend": 0,
-            "handles": {
-              "start": {
-                "id": "start",
-                "index": 0,
-                "point": [
-                  0.51,
-                  0
-                ],
-                "canBind": true
-              },
-              "end": {
-                "id": "end",
-                "index": 1,
-                "point": [
-                  0,
-                  30.96
-                ],
-                "canBind": true
-              },
-              "bend": {
-                "id": "bend",
-                "index": 2,
-                "point": [
-                  0.26,
-                  15.48
-                ]
-              }
-            },
-            "decorations": {
-              "end": "arrow"
-            },
-            "style": {
-              "color": "white",
-              "size": "small",
-              "isFilled": false,
-              "dash": "draw",
-              "scale": 1,
-              "textAlign": "middle"
-            }
-          }
-        },
-        "bindings": {}
-      }
-    },
-    "pageStates": {
-      "page": {
-        "id": "page",
-        "selectedIds": [
-          "ae6376d5-72ef-4973-0b26-9be1e92e8633"
-        ],
-        "camera": {
-          "point": [
-            0,
-            0
-          ],
-          "zoom": 1
-        }
-      }
-    }
-  },
-  "assets": {}
-}

+ 4 - 43
tldraw/packages/core/package.json

@@ -18,9 +18,7 @@
   "files": [
     "dist/**/*"
   ],
-  "main": "./dist/cjs/index.js",
-  "module": "./dist/esm/index.js",
-  "types": "./dist/types/index.d.ts",
+  "main": "./src/index.ts",
   "scripts": {
     "start:next": "yarn start",
     "start:packages": "yarn start",
@@ -42,20 +40,16 @@
     "@use-gesture/react": "^10.2.15",
     "fast-copy": "^2.1.3",
     "fast-deep-equal": "^3.1.3",
-    "hotkeys-js": "^3.9.4",
+    "hotkeys-js": "^3.9.5",
     "is-plain-object": "^5.0.0",
     "mobx": "^6.6.0",
     "mobx-react-lite": "^3.4.0",
     "mousetrap": "^1.6.5",
-    "uuid": "^8.0.0",
-    "rbush": "^3.0.1"
+    "rbush": "^3.0.1",
+    "uuid": "^8.0.0"
   },
   "devDependencies": {
-    "@swc-node/jest": "^1.3.3",
-    "@testing-library/jest-dom": "^5.14.1",
-    "@testing-library/react": "^12.0.0",
     "@types/is-plain-object": "^2.0.4",
-    "@types/jest": "^27.0.2",
     "@types/mousetrap": "^1.6.8",
     "@types/node": "^16.11.6",
     "@types/rbush": "^3.0.0",
@@ -63,38 +57,5 @@
     "@types/react-dom": "^17.0.0",
     "tsconfig-replace-paths": "^0.0.11"
   },
-  "jest": {
-    "setupFilesAfterEnv": [
-      "<rootDir>/../../setupTests.ts"
-    ],
-    "transform": {
-      "^.+\\.(tsx|jsx|ts|js|mjs)?$": [
-        "@swc-node/jest",
-        {
-          "dynamicImport": true,
-          "experimentalDecorators": true,
-          "emitDecoratorMetadata": true
-        }
-      ]
-    },
-    "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
-    "moduleFileExtensions": [
-      "ts",
-      "tsx",
-      "js",
-      "jsx",
-      "json",
-      "node"
-    ],
-    "testEnvironment": "jsdom",
-    "modulePathIgnorePatterns": [
-      "<rootDir>/dist/",
-      "<rootDir>/src/test/"
-    ],
-    "moduleNameMapper": {
-      "@tldraw/core": "<rootDir>/src",
-      "\\~(.*)": "<rootDir>/src/$1"
-    }
-  },
   "gitHead": "3ab5db27b9e83736fdae934474e80e90c854922c"
 }

+ 8 - 1
tldraw/packages/core/src/constants.ts

@@ -1,5 +1,12 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
-import { TLResizeCorner, TLResizeEdge, TLSelectionHandle, TLCursor, TLRotateCorner } from '~types'
+
+import {
+  type TLSelectionHandle,
+  TLCursor,
+  TLResizeEdge,
+  TLResizeCorner,
+  TLRotateCorner,
+} from './types'
 
 export const PI = Math.PI
 

+ 4 - 3
tldraw/packages/core/src/lib/TLApi/TLApi.ts

@@ -1,7 +1,8 @@
 import Vec from '@tldraw/vec'
-import type { TLApp, TLPage, TLShapeModel, TLShape } from '~lib'
-import type { TLEventMap } from '~types'
-import { BoundsUtils } from '~utils'
+import type { TLEventMap } from '../../types'
+import { BoundsUtils } from '../../utils'
+import type { TLShape, TLShapeModel } from '../shapes'
+import type { TLApp } from '../TLApp'
 
 export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMap> {
   private app: TLApp<S, K>

+ 0 - 821
tldraw/packages/core/src/lib/TLApp/TLApp.test.ts

@@ -1,821 +0,0 @@
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
-import { TLResizeCorner, TLRotateCorner, TLTargetType } from '~types'
-import { BoxShape } from '~lib/shapes/TLBoxShape/TLBoxShape.test'
-import { TLTestApp } from '~test/TLTestApp'
-import Vec from '@tldraw/vec'
-
-describe('TLTestApp', () => {
-  it('creates a new app', () => {
-    const app = new TLTestApp()
-    expect(app).toBeTruthy()
-  })
-
-  it('creates a new test app', () => {
-    const app = new TLTestApp()
-    expect(app).toBeTruthy()
-  })
-})
-
-describe('When creating a TLTestApp', () => {
-  it.todo('Loads serialized document via constructor')
-  it.todo('Registers shape classes via constructor')
-  it.todo('Registers tool classes via constructor')
-})
-
-describe('When adding event subscriptions', () => {
-  it.todo('Notifies onPersist subscription')
-  it.todo('Notifies onSave subscription')
-  it.todo('Notifies onSaveAs subscription')
-})
-
-describe('When interacting with the public API', () => {
-  it.todo('Registers shape classes (provided in constructor')
-  it.todo('Registers tools via tools prop')
-  it.todo('Changes selected tool...')
-
-  it('Handles events', () => {
-    const app = new TLTestApp()
-    const spy = jest.fn()
-    app.subscribe('mount', spy)
-    app.notify('mount', null)
-    expect(spy).toHaveBeenCalled()
-  })
-
-  it.todo('Changes pages') // changePage
-  it.todo('Creates shapes') // create
-  it.todo('Updates shapes') // update
-  it.todo('Deletes shapes') // delete
-  it.todo('Deselects shapes') // deselect
-  it.todo('Selects all shapes') // selectAll
-  it.todo('Deselects all shapes') // deselectAll
-  it.todo('Zooms in') // zoomIn
-  it.todo('Zooms out') // zoomOut
-  it.todo('Resets zoom') // resetZoom
-  it.todo('Zooms to fit') // zoomToFit
-  it.todo('Zooms to selection') // zoomToSelection
-  it.todo('Toggles the grid') // toggleGrid
-  it.todo('Saves (triggers onSave prop)') // save
-  it.todo('Saves as (triggers onSaveAs prop)') // saveAs
-})
-
-/* ---------------------- Pages --------------------- */
-
-describe('app.getPageById', () => {
-  it.todo('Returns a page when passed an id')
-})
-
-describe('app.setCurrentPage', () => {
-  it.todo('Sets the current page when passed an id')
-  it.todo('Sets the current page when passed a page instance')
-})
-
-describe('app.addPages', () => {
-  it.todo('adds pages when passed an array of page instances')
-})
-
-describe('app.removePages', () => {
-  it.todo('removes pages when passed an array of page instances')
-})
-
-/* ---------------------- Tools --------------------- */
-
-describe('app.selectTool', () => {
-  it.todo('Selects a tool when passed a tool id')
-})
-
-/* ------------------ Shape Classes ----------------- */
-
-describe('app.registerShapes', () => {
-  it.todo('Registers a shape class when passed an array of shape classes')
-})
-
-describe('app.deregisterShapes', () => {
-  it.todo('Deregisters a shape class when passed an array of shape classes')
-})
-
-describe('app.getShapeClass', () => {
-  it.todo('Accesses a tool class when passed an id')
-})
-
-/* ------------------ Tool Classes ----------------- */
-
-describe('app.registerTools', () => {
-  it.todo('Registers a tool class when passed an array of tool classes')
-})
-
-describe('app.deregisterTools', () => {
-  it.todo('Deregisters a tool class when passed an array of tool classes')
-})
-
-/* ------------------ Subscriptions ----------------- */
-
-describe('app.subscribe', () => {
-  it('Subscribes to an event and calls the callback', () => {
-    const app = new TLTestApp()
-    const spy = jest.fn()
-    app.subscribe('mount', spy)
-    app.notify('mount', null)
-    expect(spy).toHaveBeenCalled()
-  })
-})
-
-describe('app.unsubscribe', () => {
-  it('Unsubscribes to an event and no longer calls the callback', () => {
-    const app = new TLTestApp()
-    const spy = jest.fn()
-    const unsub = app.subscribe('mount', spy)
-    unsub()
-    app.notify('mount', null)
-    expect(spy).not.toHaveBeenCalled()
-  })
-})
-
-describe('app.notify', () => {
-  it('Calls all subscribed callbacks', () => {
-    const app = new TLTestApp()
-    const spy1 = jest.fn()
-    const spy2 = jest.fn()
-    app.subscribe('mount', spy1)
-    app.subscribe('mount', spy2)
-    app.notify('mount', null)
-    expect(spy1).toHaveBeenCalled()
-    expect(spy2).toHaveBeenCalled()
-  })
-})
-
-/* --------------------- Events --------------------- */
-
-describe('When receiving an onTransition event', () => {
-  it('Sets `isToolLocked` to false', () => {
-    const app = new TLTestApp()
-    app.settings.update({ isToolLocked: true })
-    app.transition('select')
-    expect(app.settings.isToolLocked).toBe(false)
-  })
-})
-
-describe('When receiving an onWheel event', () => {
-  it('Updates the viewport', () => {
-    const app = new TLTestApp()
-    app.wheel([-1, -1], [0, 0])
-    expect(app.viewport.camera).toMatchObject({
-      point: [1, 1],
-      zoom: 1,
-    })
-    expect(app.viewport.currentView).toMatchObject({
-      minX: -1,
-      minY: -1,
-      maxX: 1079,
-      maxY: 719,
-      width: 1080,
-      height: 720,
-    })
-  })
-})
-
-describe('Updates the inputs when receiving events', () => {
-  it.todo('Updates the inputs onWheel')
-  it.todo('Updates the inputs onPointerDown')
-  it.todo('Updates the inputs onPointerUp')
-  it.todo('Updates the inputs onPointerMove')
-  it.todo('Updates the inputs onKeyDown')
-  it.todo('Updates the inputs onKeyUp')
-  it.todo('Updates the inputs onPinchStart')
-  it.todo('Updates the inputs onPinch')
-  it.todo('Updates the inputs onPinchEnd')
-})
-
-/* --------------------- Shapes --------------------- */
-
-describe('app.getShapeById', () => {
-  it.todo('Returns a shape instance when passed an id')
-})
-
-describe('app.createShapes', () => {
-  it('Creates shapes when passed a serialized shape', () => {
-    const app = new TLTestApp()
-    app
-      .createShapes([
-        {
-          id: 'newbox1',
-          parentId: app.currentPageId,
-          type: 'box',
-          point: [120, 120],
-        },
-      ])
-      .expectShapesToBeDefined(['newbox1'])
-      .expectShapesToHaveProps({
-        newbox1: {
-          id: 'newbox1',
-          point: [120, 120],
-        },
-      })
-  })
-
-  it('Creates shapes when passed a shape instance', () => {
-    const app = new TLTestApp()
-    app
-      .createShapes([
-        new BoxShape({
-          id: 'newbox2',
-          parentId: app.currentPageId,
-          type: 'box',
-          point: [220, 220],
-        }),
-      ])
-      .expectShapesToBeDefined(['newbox2'])
-      .expectShapesToHaveProps({
-        newbox2: {
-          id: 'newbox2',
-          point: [220, 220],
-        },
-      })
-  })
-})
-
-describe('app.updateShapes', () => {
-  it('Updates shapes when passed an array of new props', () => {
-    const app = new TLTestApp()
-    app
-      .updateShapes([{ id: 'box1', point: [200, 200] }])
-      .expectShapesToHaveProps({ box1: { point: [200, 200] } })
-      .updateShapes([
-        { id: 'box1', point: [300, 300] },
-        { id: 'box2', point: [300, 300] },
-      ])
-      .expectShapesToHaveProps({ box1: { point: [300, 300] } })
-  })
-})
-
-describe('app.deleteShapes', () => {
-  it('Deletes shapes when passed an array of ids', () => {
-    const app = new TLTestApp()
-    app.deleteShapes(['box1', 'box2']).expectShapesToBeUndefined(['box1', 'box2'])
-  })
-
-  it('Deletes shapes when passed an array of shape instances', () => {
-    const app = new TLTestApp()
-    app
-      .deleteShapes(app.getShapesById(['box1', 'box2']))
-      .expectShapesToBeUndefined(['box1', 'box2'])
-  })
-})
-
-describe('app.setSelectedShapes', () => {
-  it('Sets selected shapes when passed an array of ids', () => {
-    const app = new TLTestApp()
-      .setSelectedShapes(['box1', 'box2'])
-      .expectSelectedIdsToBe(['box1', 'box2'])
-      .expectSelectedShapesToBe(['box1', 'box2'])
-    expect(app.selectedShapesArray.length).toBe(2)
-    expect(
-      ['box1', 'box2'].every(id => app.selectedShapesArray.includes(app.getShapeById(id)))
-    ).toBe(true)
-  })
-
-  it('Sets selected shapes when passed an array of shape instances', () => {
-    const app = new TLTestApp()
-    app
-      .setSelectedShapes(app.getShapesById(['box1', 'box2']))
-      .expectSelectedIdsToBe(['box1', 'box2'])
-      .expectSelectedShapesToBe(['box1', 'box2'])
-    expect(app.selectedShapesArray.length).toBe(2)
-    expect(
-      ['box1', 'box2'].every(id => app.selectedShapesArray.includes(app.getShapeById(id)))
-    ).toBe(true)
-  })
-
-  it('Clears selected shapes when passed an empty array', () => {
-    const app = new TLTestApp()
-      .setSelectedShapes([])
-      .expectSelectedIdsToBe([])
-      .expectSelectedShapesToBe([])
-    expect(app.selectedShapesArray.length).toBe(0)
-  })
-})
-
-describe('app.setSelectionRotation', () => {
-  it.todo('Sets the bounds rotation')
-})
-
-describe('app.setHoveredShape', () => {
-  it('Sets hovered shape when passed a shape id', () => {
-    const app = new TLTestApp().setHoveredShape('box1')
-    expect(app.hoveredId).toBe('box1')
-    expect(app.hoveredShape).toBe(app.getShapeById('box1'))
-  })
-
-  it('Sets hovered shape when passed a shape instance', () => {
-    const app = new TLTestApp()
-    app.setHoveredShape(app.getShapeById('box1'))
-    expect(app.hoveredId).toBe('box1')
-    expect(app.hoveredShape).toBe(app.getShapeById('box1'))
-  })
-
-  it('Clears hovered shape when passed undefined', () => {
-    const app = new TLTestApp().setHoveredShape('box1').setHoveredShape(undefined)
-    expect(app.hoveredId).toBeUndefined()
-    expect(app.hoveredShape).toBeUndefined()
-  })
-})
-
-describe('app.setEditingShape', () => {
-  it('Sets editing shape when passed a shape id', () => {
-    const app = new TLTestApp().setEditingShape('box3')
-    expect(app.editingId).toBe('box3')
-    expect(app.editingShape).toBe(app.getShapeById('box3'))
-  })
-
-  it('Sets editing shape when passed a shape instance', () => {
-    const app = new TLTestApp()
-    app.setEditingShape(app.getShapeById('box3'))
-    expect(app.editingId).toBe('box3')
-    expect(app.editingShape).toBe(app.getShapeById('box3'))
-  })
-
-  it('Clears editing shape when passed undefined', () => {
-    const app = new TLTestApp().setEditingShape('box3').setEditingShape(undefined)
-    expect(app.editingId).toBeUndefined()
-    expect(app.editingShape).toBeUndefined()
-  })
-})
-
-/* --------------------- Camera --------------------- */
-
-describe('app.setCamera', () => {
-  it('Sets the camera when passed a point and zoom', () => {
-    const app = new TLTestApp().setCamera([100, 100], 0.5)
-    expect(app.viewport.camera).toEqual({ point: [100, 100], zoom: 0.5 })
-  })
-})
-
-describe('app.getPagePoint', () => {
-  it('Converts a screen point to a page point', () => {
-    const app = new TLTestApp()
-    const points = [
-      [100, 120],
-      [200, 500],
-      [300, 200],
-      [-500, -1500],
-    ]
-    expect(points.map(p => app.getPagePoint(p))).toMatchSnapshot('points1')
-    app.setCamera([100, 100], 0.95)
-    expect(points.map(p => app.getPagePoint(p))).toMatchSnapshot('points2')
-  })
-
-  it('Converts a page point to a screen point', () => {
-    const app = new TLTestApp()
-    const points = [
-      [100, 120],
-      [200, 500],
-      [300, 200],
-      [-500, -1500],
-    ]
-    expect(points.map(p => app.getScreenPoint(p))).toMatchSnapshot('points1')
-    app.setCamera([100, 100], 0.95)
-    expect(points.map(p => app.getScreenPoint(p))).toMatchSnapshot('points2')
-  })
-})
-
-/* --------------------- Display -------------------- */
-
-describe('app.selectionBounds', () => {
-  it('Updates selected bounds when selected shapes change', () => {
-    const app = new TLTestApp()
-
-    app.setSelectedShapes(['box1'])
-    expect(app.selectionBounds).toMatchObject({
-      minX: 0,
-      minY: 0,
-      maxX: 100,
-      maxY: 100,
-      width: 100,
-      height: 100,
-    })
-    app.setSelectedShapes(['box1', 'box2'])
-    expect(app.selectionBounds).toMatchObject({
-      minX: 0,
-      minY: 0,
-      maxX: 350,
-      maxY: 350,
-      width: 350,
-      height: 350,
-    })
-  })
-
-  it('Clears selected bounds when selected shapes is empty', () => {
-    const app = new TLTestApp()
-    app.setSelectedShapes(['box1', 'box2']).setSelectedShapes([])
-    expect(app.selectionBounds).toBeUndefined()
-  })
-})
-
-describe('app.shapesInViewport', () => {
-  it('Updates shapes in viewport when shapes change', () => {
-    const app = new TLTestApp()
-    expect(app.shapesInViewport).toMatchObject(app.getShapesById(['box1', 'box2', 'box3']))
-    app.setCamera([-150, 0], 1)
-    expect(app.shapesInViewport).toMatchObject(app.getShapesById(['box2', 'box3']))
-    app.setCamera([-550, 0], 1)
-    expect(app.shapesInViewport).toMatchObject(app.getShapesById([]))
-    app.setCamera([0, 0], 5)
-    expect(app.shapesInViewport).toMatchObject(app.getShapesById(['box1']))
-    app.setCamera([0, 0], 1)
-    expect(app.shapesInViewport).toMatchObject(app.getShapesById(['box1', 'box2', 'box3']))
-  })
-  it.todo('Updates shapes in viewport when viewport bounds change')
-})
-
-describe('app.selectionDirectionHint', () => {
-  it('Is undefined when the selection is on screen', () => {
-    const app = new TLTestApp().setSelectedShapes(['box1'])
-    expect(app.selectionDirectionHint).toBeUndefined()
-    app.setCamera([-150, 0], 1)
-    expect(Vec.toFixed(app.selectionDirectionHint!, 2)).toMatchObject([-0.59, -0.43])
-  })
-
-  it('Is positioned correctly when the bounds are non-zero', () => {
-    const app = new TLTestApp().setSelectedShapes(['box1'])
-    app.viewport.updateBounds({
-      minX: 100,
-      minY: 100,
-      maxX: 1180,
-      maxY: 820,
-      width: 1080,
-      height: 720,
-    })
-    app.setCamera([-150, 0], 1)
-    expect(Vec.toFixed(app.selectionDirectionHint!, 2)).toMatchObject([-0.59, -0.43])
-  })
-})
-
-describe('app.showSelection', () => {
-  it('Shows selection only if the select tool is active and there are selected shapes', () => {
-    const app = new TLTestApp()
-    app.setSelectedShapes(['box1'])
-    expect(app.showSelection).toBe(true)
-  })
-  it.todo('Hides selection if the only selected shape has hideSelection=true')
-  it.todo('Shows when more than one shape is selected, even if some/all have hideSelection=true')
-})
-
-describe('app.showSelectionDetail', () => {
-  it.todo('Shows detail only if the select tool is active and there are selected shapes')
-  it.todo('Hides detail if all selected shapes have hideSelection=true')
-  it.todo('Shows when more than one shape is selected, even if some/all have hideSelection=true')
-})
-
-describe('app.showSelectionRotation', () => {
-  it.todo('Shows rotation only if showing selection detail')
-  it.todo('Shows rotation only if select tool is in rotating or pointingRotateHandle state')
-})
-
-describe('app.showContextBar', () => {
-  it('Hides context bar when there are no shapes selected', () => {
-    const app = new TLTestApp()
-    app.setSelectedShapes([])
-    expect(app.showContextBar).toBe(false)
-  })
-
-  it('Shows context bar if any of the selected shapes has hideContextBar=false', () => {
-    const app = new TLTestApp()
-    app.setSelectedShapes(['box1'])
-    expect(app.showResizeHandles).toBe(true)
-
-    class TLNoContextBarBoxShape extends BoxShape {
-      static id = 'nocontextbarbox'
-      hideContextBar = true
-    }
-
-    app.registerShapes([TLNoContextBarBoxShape])
-    app.createShapes([
-      {
-        id: 'nocontextbarbox1',
-        type: 'nocontextbarbox',
-        point: [0, 0],
-        parentId: app.currentPageId,
-      },
-    ])
-    app.setSelectedShapes(['box1', 'nocontextbarbox1'])
-    expect(app.showContextBar).toBe(true)
-  })
-
-  it('Hides context bar if all selected shapes have hideContextBar=true', () => {
-    const app = new TLTestApp()
-    app.setSelectedShapes(['box1'])
-
-    class TLNoContextBarBoxShape extends BoxShape {
-      static id = 'nocontextbarbox'
-      hideContextBar = true
-    }
-    app.registerShapes([TLNoContextBarBoxShape])
-    app.createShapes([
-      {
-        id: 'nocontextbarbox1',
-        type: 'nocontextbarbox',
-        point: [0, 0],
-        parentId: app.currentPageId,
-      },
-    ])
-    app.setSelectedShapes(['nocontextbarbox1'])
-    expect(app.showContextBar).toBe(false)
-  })
-
-  it('Hides context bar when the state is not select.idle/hoveringSelectionHandle', () => {
-    const app = new TLTestApp()
-    app.setSelectedShapes(['box1'])
-    expect(app.isIn('select.idle')).toBe(true)
-    expect(app.showContextBar).toBe(true)
-    app.pointerDown([0, 0], 'box1')
-    expect(app.isIn('select.pointingSelectedShape')).toBe(true)
-    expect(app.showContextBar).toBe(false)
-    app.pointerUp([0, 0], 'box1')
-    expect(app.isIn('select.idle')).toBe(true)
-    expect(app.showContextBar).toBe(true)
-    app.pointerEnter([0, 0], {
-      type: TLTargetType.Selection,
-      handle: TLResizeCorner.TopLeft,
-    })
-    expect(app.isIn('select.hoveringSelectionHandle')).toBe(true)
-    expect(app.showContextBar).toBe(true)
-    app.pointerLeave([0, 0], {
-      type: TLTargetType.Selection,
-      handle: TLResizeCorner.TopLeft,
-    })
-    app.pointerDown([-10, -10], { type: TLTargetType.Canvas })
-    expect(app.isIn('select.pointingCanvas')).toBe(true)
-    expect(app.showContextBar).toBe(false)
-  })
-})
-
-// Resize handles
-
-describe('app.showResizeHandles', () => {
-  it('Hides resize handles when there are no shapes selected', () => {
-    const app = new TLTestApp()
-    app.setSelectedShapes([])
-    expect(app.showResizeHandles).toBe(false)
-  })
-
-  it('Shows resize handles if any of the selected shapes has hideResizeHandles=false', () => {
-    const app = new TLTestApp()
-    app.setSelectedShapes(['box1'])
-    expect(app.showResizeHandles).toBe(true)
-    class TLNoHandlesBoxShape extends BoxShape {
-      static id = 'noresizehandlesbox'
-      hideResizeHandles = true
-    }
-    app.registerShapes([TLNoHandlesBoxShape])
-    app.createShapes([
-      {
-        id: 'noresizehandlesbox1',
-        type: 'noresizehandlesbox',
-        point: [0, 0],
-        parentId: app.currentPageId,
-      },
-    ])
-    app.setSelectedShapes(['box1', 'noresizehandlesbox1'])
-    expect(app.showResizeHandles).toBe(true)
-  })
-
-  it('Hides resize handles if there is a selected shape with hideResizeHandles=true', () => {
-    const app = new TLTestApp()
-    class TLNoHandlesBoxShape extends BoxShape {
-      static id = 'noresizehandlesbox'
-      hideResizeHandles = true
-    }
-    app.registerShapes([TLNoHandlesBoxShape])
-    app.createShapes([
-      {
-        id: 'noresizehandlesbox1',
-        type: 'noresizehandlesbox',
-        point: [0, 0],
-        parentId: app.currentPageId,
-      },
-    ])
-    app.setSelectedShapes(['noresizehandlesbox1'])
-    expect(app.showResizeHandles).toBe(false)
-  })
-
-  it('Hides resize handles when the state is not select.idle/hoveringSelectionHandle/pointingResizeHandle/pointingRotateHandle', () => {
-    const app = new TLTestApp()
-    app.setSelectedShapes(['box1'])
-    expect(app.isIn('select.idle')).toBe(true)
-    expect(app.showResizeHandles).toBe(true)
-    app.pointerDown([0, 0], 'box1')
-    expect(app.isIn('select.pointingSelectedShape')).toBe(true)
-    expect(app.showResizeHandles).toBe(false)
-    app.pointerUp([0, 0], 'box1')
-    expect(app.isIn('select.idle')).toBe(true)
-    expect(app.showResizeHandles).toBe(true)
-    app.pointerEnter([0, 0], {
-      type: TLTargetType.Selection,
-      handle: TLResizeCorner.TopLeft,
-    })
-    expect(app.isIn('select.hoveringSelectionHandle')).toBe(true)
-    expect(app.showResizeHandles).toBe(true)
-    app.pointerDown([0, 0], {
-      type: TLTargetType.Selection,
-      handle: TLResizeCorner.TopLeft,
-    })
-    expect(app.isIn('select.pointingResizeHandle')).toBe(true)
-    expect(app.showResizeHandles).toBe(true)
-    app
-      .pointerUp([0, 0], {
-        type: TLTargetType.Selection,
-        handle: TLResizeCorner.TopLeft,
-      })
-      .pointerLeave([0, 0], {
-        type: TLTargetType.Selection,
-        handle: TLResizeCorner.TopLeft,
-      })
-      // test rotate handle
-      .pointerDown([-10, -10], { type: TLTargetType.Canvas })
-    expect(app.isIn('select.pointingCanvas')).toBe(true)
-    expect(app.showResizeHandles).toBe(false)
-  })
-})
-
-describe('app.showRotateHandles', () => {
-  it('Hides rotate handle when there are no shapes selected', () => {
-    const app = new TLTestApp()
-    app.setSelectedShapes([])
-    expect(app.showRotateHandles).toBe(false)
-  })
-
-  it('Shows rotate handle if any of the selected shapes has hideRotateHandle=false', () => {
-    const app = new TLTestApp()
-    app.setSelectedShapes(['box1'])
-    expect(app.showRotateHandles).toBe(true)
-
-    class TLNoRotateHandleBoxShape extends BoxShape {
-      static id = 'norotatehandlesbox'
-      hideRotateHandle = true
-    }
-    app.registerShapes([TLNoRotateHandleBoxShape])
-    app.createShapes([
-      {
-        id: 'norotatehandlesbox1',
-        type: 'norotatehandlesbox',
-        point: [0, 0],
-        parentId: app.currentPageId,
-      },
-    ])
-    app.setSelectedShapes(['box1', 'norotatehandlesbox1'])
-    expect(app.showRotateHandles).toBe(true)
-  })
-
-  it('Hides rotate handle if there is a selected shape with hideRotateHandles=true', () => {
-    const app = new TLTestApp()
-    class TLNoRotateHandleBoxShape extends BoxShape {
-      static id = 'norotatehandlesbox'
-      hideRotateHandle = true
-    }
-    app.registerShapes([TLNoRotateHandleBoxShape])
-    app.createShapes([
-      {
-        id: 'norotatehandlesbox1',
-        type: 'norotatehandlesbox',
-        point: [0, 0],
-        parentId: app.currentPageId,
-      },
-    ])
-    app.setSelectedShapes(['norotatehandlesbox1'])
-    expect(app.showRotateHandles).toBe(false)
-  })
-
-  it('Hides rotate handles when the state is not hoveringSelectionHandle/pointingResizeHandle/pointingRotateHandle', () => {
-    const app = new TLTestApp()
-    app.setSelectedShapes(['box1'])
-    expect(app.isIn('select.idle')).toBe(true)
-    expect(app.showRotateHandles).toBe(true)
-    app.pointerDown([0, 0], 'box1')
-    expect(app.isIn('select.pointingSelectedShape')).toBe(true)
-    expect(app.showRotateHandles).toBe(false)
-    app.pointerUp([0, 0], 'box1')
-    expect(app.isIn('select.idle')).toBe(true)
-    expect(app.showRotateHandles).toBe(true)
-    app.pointerEnter([0, 0], {
-      type: TLTargetType.Selection,
-      handle: 'rotate',
-    })
-    expect(app.isIn('select.hoveringSelectionHandle')).toBe(true)
-    expect(app.showRotateHandles).toBe(true)
-    app.pointerDown([0, 0], {
-      type: TLTargetType.Selection,
-      handle: TLRotateCorner.TopLeft,
-    })
-    expect(app.isIn('select.pointingRotateHandle')).toBe(true)
-    expect(app.showRotateHandles).toBe(true)
-    app
-      .pointerUp([0, 0], {
-        type: TLTargetType.Selection,
-        handle: 'rotate',
-      })
-      .pointerLeave([0, 0], {
-        type: TLTargetType.Selection,
-        handle: 'rotate',
-      })
-      // test resize handle
-      .pointerDown([-10, -10], { type: TLTargetType.Canvas })
-    expect(app.isIn('select.pointingCanvas')).toBe(true)
-    expect(app.showRotateHandles).toBe(false)
-  })
-})
-
-/* ---------------------- Brush --------------------- */
-
-describe('app.setBrush', () => {
-  it('Sets brush when passed a bounding box', () => {
-    const app = new TLTestApp()
-    app.setBrush({
-      minX: 0,
-      maxX: 100,
-      minY: 0,
-      maxY: 100,
-      width: 100,
-      height: 100,
-    })
-    expect(app.brush).toMatchObject({
-      minX: 0,
-      maxX: 100,
-      minY: 0,
-      maxY: 100,
-      width: 100,
-      height: 100,
-    })
-  })
-
-  it('Clears brush when passed undefined', () => {
-    const app = new TLTestApp()
-    app.setBrush({
-      minX: 0,
-      maxX: 100,
-      minY: 0,
-      maxY: 100,
-      width: 100,
-      height: 100,
-    })
-    app.setBrush(undefined)
-    expect(app.brush).toBeUndefined()
-  })
-})
-
-/* --------------------- History -------------------- */
-
-describe('app.undo', () => {
-  it.todo('undoes a change')
-  it.todo('does nothing if no past undo history')
-})
-
-describe('app.redo', () => {
-  it.todo('redoes a change')
-  it.todo('does nothing if no future undo history')
-})
-
-/* -------------------- Document -------------------- */
-
-describe('app.loadDocumentModel', () => {
-  it('Loads a document from JSON', () => {
-    const app = new TLTestApp()
-    app.loadDocumentModel({
-      currentPageId: 'page1',
-      selectedIds: ['jbox'],
-      pages: [
-        {
-          name: 'page1',
-          id: 'page1',
-          shapes: [
-            {
-              id: 'jbox',
-              type: 'box',
-              point: [0, 0],
-              parentId: 'page1',
-            },
-          ],
-          bindings: {},
-        },
-      ],
-    })
-
-    expect(app.currentPageId).toBe('page1')
-    expect(app.selectedIds.size).toBe(1)
-    expect(app.selectedShapesArray[0]).toBe(app.getShapeById('jbox'))
-    expect(app.pages.size).toBe(1)
-    expect(app.currentPage.shapes.length).toBe(1)
-    expect(app.getShapeById('jbox')).toBeDefined
-  })
-
-  it('Fails with warning if given a malformed document', () => {
-    const app = new TLTestApp()
-    const warn = jest.fn()
-    jest.spyOn(console, 'warn').mockImplementation(warn)
-    app.loadDocumentModel({
-      currentPageId: 'page1',
-      selectedIds: [],
-      // @ts-expect-error - we're testing the warning
-      pages: 'frog dog',
-    })
-    expect(warn).toHaveBeenCalled()
-  })
-})

+ 17 - 23
tldraw/packages/core/src/lib/TLApp/TLApp.ts

@@ -1,40 +1,34 @@
 /* eslint-disable @typescript-eslint/no-extra-semi */
 /* eslint-disable @typescript-eslint/no-non-null-assertion */
 /* eslint-disable @typescript-eslint/no-explicit-any */
+import type { TLBounds } from '@tldraw/intersect'
 import { Vec } from '@tldraw/vec'
 import { action, computed, makeObservable, observable, transaction } from 'mobx'
-import { GRID_SIZE } from '~constants'
-
-import {
-  TLInputs,
-  TLMoveTool,
-  TLPage,
-  TLPageModel,
-  TLSelectTool,
-  TLShape,
-  TLShapeConstructor,
-  TLShapeModel,
-  TLToolConstructor,
-  TLViewport,
-} from '~lib'
-import { TLApi } from '~lib/TLApi'
-import { TLCursors } from '~lib/TLCursors'
+import { GRID_SIZE } from '../../constants'
 import type {
   TLAsset,
-  TLBounds,
-  TLCallback,
   TLEventMap,
-  TLEvents,
   TLShortcut,
-  TLStateEvents,
   TLSubscription,
-  TLSubscriptionEventInfo,
   TLSubscriptionEventName,
-} from '~types'
-import { BoundsUtils, KeyUtils } from '~utils'
+  TLCallback,
+  TLSubscriptionEventInfo,
+  TLStateEvents,
+  TLEvents,
+} from '../../types'
+import { KeyUtils, BoundsUtils } from '../../utils'
+import type { TLShape, TLShapeConstructor, TLShapeModel } from '../shapes'
+import { TLApi } from '../TLApi'
+import { TLCursors } from '../TLCursors'
+
 import { TLHistory } from '../TLHistory'
+import { TLInputs } from '../TLInputs'
+import { type TLPageModel, TLPage } from '../TLPage'
 import { TLSettings } from '../TLSettings'
 import { TLRootState } from '../TLState'
+import type { TLToolConstructor } from '../TLTool'
+import { TLViewport } from '../TLViewport'
+import { TLSelectTool, TLMoveTool } from '../tools'
 
 export interface TLDocumentModel<S extends TLShape = TLShape, A extends TLAsset = TLAsset> {
   currentPageId: string

+ 0 - 85
tldraw/packages/core/src/lib/TLApp/__snapshots__/TLApp.test.ts.snap

@@ -1,85 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`app.getPagePoint Converts a page point to a screen point: points1 1`] = `
-Array [
-  Array [
-    100,
-    120,
-  ],
-  Array [
-    200,
-    500,
-  ],
-  Array [
-    300,
-    200,
-  ],
-  Array [
-    -500,
-    -1500,
-  ],
-]
-`;
-
-exports[`app.getPagePoint Converts a page point to a screen point: points2 1`] = `
-Array [
-  Array [
-    190,
-    209,
-  ],
-  Array [
-    285,
-    570,
-  ],
-  Array [
-    380,
-    285,
-  ],
-  Array [
-    -380,
-    -1330,
-  ],
-]
-`;
-
-exports[`app.getPagePoint Converts a screen point to a page point: points1 1`] = `
-Array [
-  Array [
-    100,
-    120,
-  ],
-  Array [
-    200,
-    500,
-  ],
-  Array [
-    300,
-    200,
-  ],
-  Array [
-    -500,
-    -1500,
-  ],
-]
-`;
-
-exports[`app.getPagePoint Converts a screen point to a page point: points2 1`] = `
-Array [
-  Array [
-    5.26315789473685,
-    26.31578947368422,
-  ],
-  Array [
-    110.5263157894737,
-    426.3157894736843,
-  ],
-  Array [
-    215.78947368421052,
-    110.5263157894737,
-  ],
-  Array [
-    -626.3157894736843,
-    -1678.9473684210527,
-  ],
-]
-`;

+ 7 - 5
tldraw/packages/core/src/lib/TLBaseLineBindingState.ts

@@ -1,8 +1,11 @@
 import Vec from '@tldraw/vec'
-import { toJS, transaction } from 'mobx'
-import { TLApp, TLLineShape, TLLineShapeProps, TLShape, TLTool, TLToolState } from '~lib'
-import type { TLBinding, TLEventMap, TLHandle, TLStateEvents } from '~types'
-import { deepMerge, GeomUtils } from '~utils'
+import { transaction } from 'mobx'
+import type { TLBinding, TLEventMap, TLHandle, TLStateEvents } from '../types'
+import { deepMerge, GeomUtils } from '../utils'
+import type { TLLineShape, TLLineShapeProps, TLShape } from './shapes'
+import type { TLApp } from './TLApp'
+import type { TLTool } from './TLTool'
+import { TLToolState } from './TLToolState'
 
 export class TLBaseLineBindingState<
   S extends TLShape,
@@ -49,7 +52,6 @@ export class TLBaseLineBindingState<
 
     const nextPoint = Vec.add(handles[handleId].point, delta)
 
-
     const handleChanges = {
       [handleId]: {
         ...handles[handleId],

Some files were not shown because too many files changed in this diff