Преглед изворни кода

Merge branch 'master' into feat/custom-children-list-style

charlie пре 2 година
родитељ
комит
f73d964899
40 измењених фајлова са 383 додато и 433 уклоњено
  1. 2 2
      e2e-tests/whiteboards.spec.ts
  2. 8 1
      src/main/frontend/components/container.css
  3. 5 0
      src/main/frontend/components/header.css
  4. 1 0
      src/main/frontend/components/shortcut.cljs
  5. 3 1
      src/main/frontend/components/whiteboard.cljs
  6. 8 1
      src/main/frontend/extensions/tldraw.cljs
  7. 29 2
      src/main/frontend/handler/editor.cljs
  8. 128 4
      src/main/frontend/modules/shortcut/config.cljs
  9. 25 0
      src/main/frontend/modules/shortcut/dicts.cljc
  10. 1 0
      src/main/frontend/ui.cljs
  11. 3 0
      src/main/frontend/util.cljc
  12. 16 15
      tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx
  13. 13 57
      tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx
  14. 16 0
      tldraw/apps/tldraw-logseq/src/components/KeyboardShortcut/KeyboardShortcut.tsx
  15. 1 0
      tldraw/apps/tldraw-logseq/src/components/KeyboardShortcut/index.ts
  16. 52 19
      tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx
  17. 30 30
      tldraw/apps/tldraw-logseq/src/components/ToolButton/ToolButton.tsx
  18. 7 25
      tldraw/apps/tldraw-logseq/src/components/ZoomMenu/ZoomMenu.tsx
  19. 1 1
      tldraw/apps/tldraw-logseq/src/components/inputs/ToggleInput.tsx
  20. 3 0
      tldraw/apps/tldraw-logseq/src/lib/logseq-context.ts
  21. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/BoxTool.tsx
  22. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/EllipseTool.tsx
  23. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/EraseTool.tsx
  24. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/HighlighterTool.tsx
  25. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/LineTool.tsx
  26. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/LogseqPortalTool.tsx
  27. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/PencilTool.tsx
  28. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/TextTool.tsx
  29. 1 1
      tldraw/apps/tldraw-logseq/src/styles.css
  30. 4 13
      tldraw/packages/core/src/lib/TLApi/TLApi.ts
  31. 5 170
      tldraw/packages/core/src/lib/TLApp/TLApp.ts
  32. 0 21
      tldraw/packages/core/src/lib/TLState.ts
  33. 1 1
      tldraw/packages/core/src/lib/tools/TLMoveTool/TLMoveTool.ts
  34. 1 1
      tldraw/packages/core/src/lib/tools/TLSelectTool/TLSelectTool.tsx
  35. 3 13
      tldraw/packages/core/src/types/types.ts
  36. 0 32
      tldraw/packages/core/src/utils/KeyUtils.ts
  37. 7 7
      tldraw/packages/core/src/utils/index.ts
  38. 0 1
      tldraw/packages/react/src/hooks/useAppSetup.ts
  39. 1 5
      tldraw/packages/react/src/hooks/useSetup.ts
  40. 0 2
      tldraw/packages/react/src/types/TLReactSubscriptions.tsx

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

@@ -296,7 +296,7 @@ test('copy/paste youtube video url to create a Youtube shape', async ({ page })
 test('zoom in', async ({ page }) => {
 test('zoom in', async ({ page }) => {
   await page.keyboard.press('Shift+0') // reset zoom
   await page.keyboard.press('Shift+0') // reset zoom
   await page.waitForTimeout(1500) // wait for the zoom animation to finish
   await page.waitForTimeout(1500) // wait for the zoom animation to finish
-  await page.keyboard.press(`${modKey}++`)
+  await page.keyboard.press('Shift+=')
   await page.waitForTimeout(1500) // wait for the zoom animation to finish
   await page.waitForTimeout(1500) // wait for the zoom animation to finish
   await expect(page.locator('#tl-zoom')).toContainText('125%')
   await expect(page.locator('#tl-zoom')).toContainText('125%')
 })
 })
@@ -304,7 +304,7 @@ test('zoom in', async ({ page }) => {
 test('zoom out', async ({ page }) => {
 test('zoom out', async ({ page }) => {
   await page.keyboard.press('Shift+0')
   await page.keyboard.press('Shift+0')
   await page.waitForTimeout(1500) // wait for the zoom animation to finish
   await page.waitForTimeout(1500) // wait for the zoom animation to finish
-  await page.keyboard.press(`${modKey}+-`)
+  await page.keyboard.press('Shift+-')
   await page.waitForTimeout(1500) // wait for the zoom animation to finish
   await page.waitForTimeout(1500) // wait for the zoom animation to finish
   await expect(page.locator('#tl-zoom')).toContainText('80%')
   await expect(page.locator('#tl-zoom')).toContainText('80%')
 })
 })

+ 8 - 1
src/main/frontend/components/container.css

@@ -226,7 +226,7 @@
         li {
         li {
           margin: 0;
           margin: 0;
         }
         }
-        
+
         a {
         a {
           width: 100%;
           width: 100%;
           padding: 4px 24px;
           padding: 4px 24px;
@@ -624,6 +624,13 @@ html[data-theme='dark'] {
   }
   }
 }
 }
 
 
+/* Workaround for Linux Intel GPU text rendering issue GH#7233 */
+.is-electron.is-linux .cp__menubar-repos {
+  #repo-switch, .nav-header .flex-1 {
+    position: relative;
+  }
+}
+
 @supports not (overflow-y: overlay) {
 @supports not (overflow-y: overlay) {
   .scrollbar-spacing {
   .scrollbar-spacing {
     overflow-y: auto;
     overflow-y: auto;

+ 5 - 0
src/main/frontend/components/header.css

@@ -128,6 +128,11 @@
   padding-left: 1rem;
   padding-left: 1rem;
 }
 }
 
 
+/* Workaround for Linux Intel GPU text rendering issue GH#7233 */
+.is-electron.is-linux .cp__header .dropdown-wrapper .title-wrap {
+  position: relative;
+}
+
 .cp__header a, .cp__header svg {
 .cp__header a, .cp__header svg {
   -webkit-app-region: no-drag;
   -webkit-app-region: no-drag;
 }
 }

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

@@ -181,4 +181,5 @@
    (shortcut-table :shortcut.category/block-selection true)
    (shortcut-table :shortcut.category/block-selection true)
    (shortcut-table :shortcut.category/formatting true)
    (shortcut-table :shortcut.category/formatting true)
    (shortcut-table :shortcut.category/toggle true)
    (shortcut-table :shortcut.category/toggle true)
+   (when (state/enable-whiteboards?) (shortcut-table :shortcut.category/whiteboard true))
    (shortcut-table :shortcut.category/others true)])
    (shortcut-table :shortcut.category/others true)])

+ 3 - 1
src/main/frontend/components/whiteboard.cljs

@@ -11,6 +11,7 @@
             [frontend.handler.common :as common-handler]
             [frontend.handler.common :as common-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.whiteboard :as whiteboard-handler]
             [frontend.handler.whiteboard :as whiteboard-handler]
+            [frontend.modules.shortcut.core :as shortcut]
             [frontend.rum :refer [use-bounding-client-rect use-breakpoint
             [frontend.rum :refer [use-bounding-client-rect use-breakpoint
                                   use-click-outside]]
                                   use-click-outside]]
             [frontend.state :as state]
             [frontend.state :as state]
@@ -290,7 +291,8 @@
                                                                       {:extension? true})])})]]
                                                                       {:extension? true})])})]]
      (tldraw-app page-name block-id)]))
      (tldraw-app page-name block-id)]))
 
 
-(rum/defc whiteboard-route
+(rum/defc whiteboard-route <
+(shortcut/mixin :shortcut.handler/whiteboard)
   [route-match]
   [route-match]
   (let [name (get-in route-match [:parameters :path :name])
   (let [name (get-in route-match [:parameters :path :name])
         {:keys [block-id]} (get-in route-match [:parameters :query])]
         {:keys [block-id]} (get-in route-match [:parameters :query])]

+ 8 - 1
src/main/frontend/extensions/tldraw.cljs

@@ -10,6 +10,7 @@
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.whiteboard :as whiteboard-handler]
             [frontend.handler.whiteboard :as whiteboard-handler]
             [frontend.handler.history :as history]
             [frontend.handler.history :as history]
+            [frontend.modules.shortcut.data-helper :as shortcut-helper]
             [frontend.rum :as r]
             [frontend.rum :as r]
             [frontend.search :as search]
             [frontend.search :as search]
             [frontend.state :as state]
             [frontend.state :as state]
@@ -75,13 +76,19 @@
   (apply whiteboard/references-count
   (apply whiteboard/references-count
          (map (fn [k] (js->clj (gobj/get props k) {:keywordize-keys true})) ["id" "className" "options"])))
          (map (fn [k] (js->clj (gobj/get props k) {:keywordize-keys true})) ["id" "className" "options"])))
 
 
+(rum/defc keyboard-shortcut
+  [props]
+  (let [shortcut (shortcut-helper/gen-shortcut-seq (keyword (gobj/get props "action")))]
+    (ui/render-keyboard-shortcut shortcut)))
+
 (def tldraw-renderers {:Page page-cp
 (def tldraw-renderers {:Page page-cp
                        :Block block-cp
                        :Block block-cp
                        :Breadcrumb breadcrumb
                        :Breadcrumb breadcrumb
                        :Tweet tweet
                        :Tweet tweet
                        :PageName page-name-link
                        :PageName page-name-link
                        :BacklinksCount references-count
                        :BacklinksCount references-count
-                       :BlockReference block-reference})
+                       :BlockReference block-reference
+                       :KeyboardShortcut keyboard-shortcut})
 
 
 (def undo (fn [] (history/undo! nil)))
 (def undo (fn [] (history/undo! nil)))
 (def redo (fn [] (history/redo! nil)))
 (def redo (fn [] (history/redo! nil)))

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

@@ -3214,6 +3214,11 @@
       :else
       :else
       (js/document.execCommand "copy"))))
       (js/document.execCommand "copy"))))
 
 
+(defn whiteboard?
+  []
+  (and (state/whiteboard-route?)
+       (.closest (.-activeElement js/document) ".logseq-tldraw")))
+
 (defn shortcut-cut
 (defn shortcut-cut
   "shortcut cut action:
   "shortcut cut action:
   * when in selection mode, cut selected blocks
   * when in selection mode, cut selected blocks
@@ -3227,14 +3232,24 @@
     (and (state/editing?) (util/input-text-selected?
     (and (state/editing?) (util/input-text-selected?
                            (gdom/getElement (state/get-edit-input-id))))
                            (gdom/getElement (state/get-edit-input-id))))
     (keydown-backspace-handler true e)
     (keydown-backspace-handler true e)
+    
+    (whiteboard?)
+    (.cut (state/active-tldraw-app))
 
 
     :else
     :else
     nil))
     nil))
 
 
 (defn delete-selection
 (defn delete-selection
   [e]
   [e]
-  (when (state/selection?)
-    (shortcut-delete-selection e)))
+  (cond
+    (state/selection?)
+    (shortcut-delete-selection e)
+
+    (whiteboard?)
+    (.deleteShapes (.-api ^js (state/active-tldraw-app)))
+
+    :else
+    nil))
 
 
 (defn editor-delete
 (defn editor-delete
   [_state e]
   [_state e]
@@ -3534,6 +3549,10 @@
                        expand-block!)))
                        expand-block!)))
             doall)
             doall)
        (and clear-selection? (clear-selection!)))
        (and clear-selection? (clear-selection!)))
+
+     (whiteboard?)
+     (.setCollapsed (.-api ^js (state/active-tldraw-app)) false)
+     
      :else
      :else
      ;; expand one level
      ;; expand one level
      (let [blocks-with-level (all-blocks-with-level {})
      (let [blocks-with-level (all-blocks-with-level {})
@@ -3567,6 +3586,9 @@
                        collapse-block!)))
                        collapse-block!)))
             doall)
             doall)
        (and clear-selection? (clear-selection!)))
        (and clear-selection? (clear-selection!)))
+     
+     (whiteboard?)
+     (.setCollapsed (.-api ^js (state/active-tldraw-app)) true)
 
 
      :else
      :else
      ;; collapse by one level from outside
      ;; collapse by one level from outside
@@ -3663,6 +3685,11 @@
         edit-block (state/get-edit-block)
         edit-block (state/get-edit-block)
         target-element (.-nodeName (.-target e))]
         target-element (.-nodeName (.-target e))]
     (cond
     (cond
+      (whiteboard?)
+      (do
+        (util/stop e)
+        (.selectAll (.-api ^js (state/active-tldraw-app))))
+
       ;; editing block fully selected
       ;; editing block fully selected
       (and edit-block edit-input
       (and edit-block edit-input
            (= (util/get-selected-text) (.-value edit-input)))
            (= (util/get-selected-text) (.-value edit-input)))

+ 128 - 4
src/main/frontend/modules/shortcut/config.cljs

@@ -71,6 +71,78 @@
    :pdf/find                     {:binding "alt+f"
    :pdf/find                     {:binding "alt+f"
                                   :fn      pdf-utils/open-finder}
                                   :fn      pdf-utils/open-finder}
 
 
+   :whiteboard/select            {:binding ["1" "s"]
+                                  :fn      #(.selectTool ^js (state/active-tldraw-app) "select")}
+   
+   :whiteboard/pan               {:binding ["2" "p"]
+                                  :fn      #(.selectTool ^js (state/active-tldraw-app) "move")}
+   
+   :whiteboard/portal            {:binding "3"
+                                  :fn      #(.selectTool ^js (state/active-tldraw-app) "logseq-portal")}
+
+   :whiteboard/pencil            {:binding ["4" "d"]
+                                  :fn      #(.selectTool ^js (state/active-tldraw-app) "pencil")}
+
+   :whiteboard/highlighter       {:binding ["5" "h"]
+                                  :fn      #(.selectTool ^js (state/active-tldraw-app) "highlighter")}
+   
+   :whiteboard/eraser            {:binding ["6" "e"]
+                                  :fn      #(.selectTool ^js (state/active-tldraw-app) "erase")}
+   
+   :whiteboard/connector         {:binding ["7" "c"]
+                                  :fn      #(.selectTool ^js (state/active-tldraw-app) "line")}
+   
+   :whiteboard/text              {:binding ["8" "t"]
+                                  :fn      #(.selectTool ^js (state/active-tldraw-app) "text")}
+
+   :whiteboard/rectangle         {:binding ["9" "r"]
+                                  :fn      #(.selectTool ^js (state/active-tldraw-app) "box")}
+
+   :whiteboard/ellipse           {:binding "o"
+                                  :fn      #(.selectTool ^js (state/active-tldraw-app) "ellipse")}
+
+   :whiteboard/reset-zoom        {:binding "shift+0"
+                                  :fn      #(.resetZoom (.-api ^js (state/active-tldraw-app)))}
+
+   :whiteboard/zoom-to-fit       {:binding "shift+1"
+                                  :fn      #(.zoomToFit (.-api ^js (state/active-tldraw-app)))}
+
+   :whiteboard/zoom-to-selection {:binding "shift+2"
+                                  :fn      #(.zoomToSelection (.-api ^js (state/active-tldraw-app)))}
+
+   :whiteboard/zoom-out          {:binding "shift+dash"
+                                  :fn      #(.zoomOut (.-api ^js (state/active-tldraw-app)) false)}
+
+   :whiteboard/zoom-in           {:binding "shift+="
+                                  :fn      #(.zoomIn (.-api ^js (state/active-tldraw-app)) false)}
+
+   :whiteboard/send-backward     {:binding "["
+                                  :fn      #(.sendBackward ^js (state/active-tldraw-app))}
+
+   :whiteboard/send-to-back      {:binding "shift+["
+                                  :fn      #(.sendToBack ^js (state/active-tldraw-app))}
+
+   :whiteboard/bring-forward     {:binding "]"
+                                  :fn      #(.bringForward ^js (state/active-tldraw-app))}
+
+   :whiteboard/bring-to-front    {:binding "shift+]"
+                                  :fn      #(.bringToFront ^js (state/active-tldraw-app))}
+
+   :whiteboard/lock              {:binding "mod+l"
+                                  :fn      #(.setLocked ^js (state/active-tldraw-app) true)}
+
+   :whiteboard/unlock            {:binding "mod+shift+l"
+                                  :fn      #(.setLocked ^js (state/active-tldraw-app) false)}
+
+   :whiteboard/group             {:binding "mod+g"
+                                  :fn      #(.doGroup (.-api ^js (state/active-tldraw-app)))}
+
+   :whiteboard/ungroup           {:binding "mod+shift+g"
+                                  :fn      #(.unGroup (.-api ^js (state/active-tldraw-app)))}
+
+   :whiteboard/toggle-grid       {:binding "shift+g"
+                                  :fn      #(.toggleGrid (.-api ^js (state/active-tldraw-app)))}
+
    :auto-complete/complete       {:binding "enter"
    :auto-complete/complete       {:binding "enter"
                                   :fn      ui-handler/auto-complete-complete}
                                   :fn      ui-handler/auto-complete-complete}
 
 
@@ -341,7 +413,7 @@
 
 
    :graph/re-index                 {:fn (fn []
    :graph/re-index                 {:fn (fn []
                                           (p/let [multiple-windows? (ipc/ipc "graphHasMultipleWindows" (state/get-current-repo))]
                                           (p/let [multiple-windows? (ipc/ipc "graphHasMultipleWindows" (state/get-current-repo))]
-                                                 (state/pub-event! [:graph/ask-for-re-index (atom multiple-windows?) nil])))
+                                            (state/pub-event! [:graph/ask-for-re-index (atom multiple-windows?) nil])))
                                     :binding false}
                                     :binding false}
 
 
    :command/run                    {:binding "mod+shift+1"
    :command/run                    {:binding "mod+shift+1"
@@ -510,6 +582,33 @@
                              :pdf/find])
                              :pdf/find])
         (with-meta {:before m/enable-when-not-editing-mode!}))
         (with-meta {:before m/enable-when-not-editing-mode!}))
 
 
+    :shortcut.handler/whiteboard
+    (-> (build-category-map [:whiteboard/select
+                             :whiteboard/pan
+                             :whiteboard/portal
+                             :whiteboard/pencil
+                             :whiteboard/highlighter
+                             :whiteboard/eraser
+                             :whiteboard/connector
+                             :whiteboard/text
+                             :whiteboard/rectangle
+                             :whiteboard/ellipse
+                             :whiteboard/reset-zoom
+                             :whiteboard/zoom-to-fit
+                             :whiteboard/zoom-to-selection
+                             :whiteboard/zoom-out
+                             :whiteboard/zoom-in
+                             :whiteboard/send-backward
+                             :whiteboard/send-to-back
+                             :whiteboard/bring-forward
+                             :whiteboard/bring-to-front
+                             :whiteboard/lock
+                             :whiteboard/unlock
+                             :whiteboard/group
+                             :whiteboard/ungroup
+                             :whiteboard/toggle-grid])
+        (with-meta {:before m/enable-when-not-editing-mode!}))
+
     :shortcut.handler/auto-complete
     :shortcut.handler/auto-complete
     (build-category-map [:auto-complete/complete
     (build-category-map [:auto-complete/complete
                          :auto-complete/prev
                          :auto-complete/prev
@@ -555,8 +654,7 @@
 
 
     :shortcut.handler/editor-global
     :shortcut.handler/editor-global
     (->
     (->
-     (build-category-map [
-                          :graph/export-as-html
+     (build-category-map [:graph/export-as-html
                           :graph/open
                           :graph/open
                           :graph/remove
                           :graph/remove
                           :graph/add
                           :graph/add
@@ -764,6 +862,33 @@
     :ui/toggle-settings
     :ui/toggle-settings
     :ui/toggle-contents]
     :ui/toggle-contents]
 
 
+   :shortcut.category/whiteboard
+   [:editor/new-whiteboard
+    :whiteboard/select
+    :whiteboard/pan
+    :whiteboard/portal
+    :whiteboard/pencil
+    :whiteboard/highlighter
+    :whiteboard/eraser
+    :whiteboard/connector
+    :whiteboard/text
+    :whiteboard/rectangle
+    :whiteboard/ellipse
+    :whiteboard/reset-zoom
+    :whiteboard/zoom-to-fit
+    :whiteboard/zoom-to-selection
+    :whiteboard/zoom-out
+    :whiteboard/zoom-in
+    :whiteboard/send-backward
+    :whiteboard/send-to-back
+    :whiteboard/bring-forward
+    :whiteboard/bring-to-front
+    :whiteboard/lock
+    :whiteboard/unlock
+    :whiteboard/group
+    :whiteboard/ungroup
+    :whiteboard/toggle-grid]
+   
    :shortcut.category/others
    :shortcut.category/others
    [:pdf/previous-page
    [:pdf/previous-page
     :pdf/next-page
     :pdf/next-page
@@ -786,7 +911,6 @@
     :editor/open-file-in-default-app
     :editor/open-file-in-default-app
     :editor/open-file-in-directory
     :editor/open-file-in-directory
     :editor/copy-page-url
     :editor/copy-page-url
-    :editor/new-whiteboard
     :auto-complete/prev
     :auto-complete/prev
     :auto-complete/next
     :auto-complete/next
     :auto-complete/complete
     :auto-complete/complete

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

@@ -80,6 +80,30 @@
    :editor/zoom-out                "Zoom out editing block / Backwards otherwise"
    :editor/zoom-out                "Zoom out editing block / Backwards otherwise"
    :editor/toggle-undo-redo-mode   "Toggle undo redo mode (global or page only)"
    :editor/toggle-undo-redo-mode   "Toggle undo redo mode (global or page only)"
    :editor/toggle-number-list      "Toggle number list"
    :editor/toggle-number-list      "Toggle number list"
+   :whiteboard/select              "Select tool"
+   :whiteboard/pan                 "Pan tool"
+   :whiteboard/portal              "Portal tool"
+   :whiteboard/pencil              "Pencil tool"
+   :whiteboard/highlighter         "Highlighter tool"
+   :whiteboard/eraser              "Eraser tool"
+   :whiteboard/connector           "Connector tool"
+   :whiteboard/text                "Text tool"
+   :whiteboard/rectangle           "Rectangle tool"
+   :whiteboard/ellipse             "Ellipse tool"
+   :whiteboard/reset-zoom          "Reset zoom"
+   :whiteboard/zoom-to-fit         "Zoom to drawing"
+   :whiteboard/zoom-to-selection   "Zoom to fit selection"
+   :whiteboard/zoom-out            "Zoom out"
+   :whiteboard/zoom-in             "Zoom in"
+   :whiteboard/send-backward       "Move backward"
+   :whiteboard/send-to-back        "Move to back"
+   :whiteboard/bring-forward       "Move forward"
+   :whiteboard/bring-to-front      "Move to front"
+   :whiteboard/lock                "Lock selection"
+   :whiteboard/unlock              "Unlock selection"
+   :whiteboard/group               "Group selection"
+   :whiteboard/ungroup             "Ungroup selection"
+   :whiteboard/toggle-grid         "Toggle the canvas grid"
    :ui/toggle-brackets             "Toggle whether to display brackets"
    :ui/toggle-brackets             "Toggle whether to display brackets"
    :go/search-in-page              "Search blocks in the current page"
    :go/search-in-page              "Search blocks in the current page"
    :go/electron-find-in-page       "Find text in page"
    :go/electron-find-in-page       "Find text in page"
@@ -146,6 +170,7 @@
    :shortcut.category/block-command-editing "Block command editing"
    :shortcut.category/block-command-editing "Block command editing"
    :shortcut.category/block-selection "Block selection (press Esc to quit selection)"
    :shortcut.category/block-selection "Block selection (press Esc to quit selection)"
    :shortcut.category/toggle "Toggle"
    :shortcut.category/toggle "Toggle"
+   :shortcut.category/whiteboard "Whiteboard"
    :shortcut.category/others "Others"})
    :shortcut.category/others "Others"})
 
 
 (def ^:large-vars/data-var dicts
 (def ^:large-vars/data-var dicts

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

@@ -353,6 +353,7 @@
     (when config/publishing? (.add cl "is-publish-mode"))
     (when config/publishing? (.add cl "is-publish-mode"))
     (when util/mac? (.add cl "is-mac"))
     (when util/mac? (.add cl "is-mac"))
     (when util/win32? (.add cl "is-win32"))
     (when util/win32? (.add cl "is-win32"))
+    (when util/linux? (.add cl "is-linux"))
     (when (util/electron?) (.add cl "is-electron"))
     (when (util/electron?) (.add cl "is-electron"))
     (when (util/ios?) (.add cl "is-ios"))
     (when (util/ios?) (.add cl "is-ios"))
     (when (util/mobile?) (.add cl "is-mobile"))
     (when (util/mobile?) (.add cl "is-mobile"))

+ 3 - 0
src/main/frontend/util.cljc

@@ -917,6 +917,9 @@
 (defonce win32? #?(:cljs goog.userAgent/WINDOWS
 (defonce win32? #?(:cljs goog.userAgent/WINDOWS
                    :clj nil))
                    :clj nil))
 
 
+(defonce linux? #?(:cljs goog.userAgent/LINUX
+                   :clj nil))
+
 (defn default-content-with-title
 (defn default-content-with-title
   [text-format]
   [text-format]
   (case (name text-format)
   (case (name text-format)

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

@@ -30,6 +30,7 @@ import {
 import { ToggleInput } from '../inputs/ToggleInput'
 import { ToggleInput } from '../inputs/ToggleInput'
 import { GeometryTools } from '../GeometryTools'
 import { GeometryTools } from '../GeometryTools'
 import { LogseqContext } from '../../lib/logseq-context'
 import { LogseqContext } from '../../lib/logseq-context'
+import { KeyboardShortcut } from '../KeyboardShortcut'
 
 
 export const contextBarActionTypes = [
 export const contextBarActionTypes = [
   // Order matters
   // Order matters
@@ -61,13 +62,7 @@ const contextBarActionMapping = new Map<ContextBarActionType, React.FC>()
 type ShapeType = Shape['props']['type']
 type ShapeType = Shape['props']['type']
 
 
 export const shapeMapping: Record<ShapeType, ContextBarActionType[]> = {
 export const shapeMapping: Record<ShapeType, ContextBarActionType[]> = {
-  'logseq-portal': [
-    'Swatch',
-    'LogseqPortalViewMode',
-    'ScaleLevel',
-    'AutoResizing',
-    'Links',
-  ],
+  'logseq-portal': ['Swatch', 'LogseqPortalViewMode', 'ScaleLevel', 'AutoResizing', 'Links'],
   youtube: ['YoutubeLink', 'Links'],
   youtube: ['YoutubeLink', 'Links'],
   tweet: ['TwitterLink', 'Links'],
   tweet: ['TwitterLink', 'Links'],
   iframe: ['IFrameSource', 'Links'],
   iframe: ['IFrameSource', 'Links'],
@@ -134,13 +129,24 @@ const LogseqPortalViewModeAction = observer(() => {
     return null
     return null
   }
   }
 
 
+  const tooltip = (
+    <div className="flex">
+      {collapsed ? 'Expand' : 'Collapse'}
+      <KeyboardShortcut
+        action={
+          collapsed ? 'editor/expand-block-children' : 'editor/collapse-block-children'
+        }
+      />
+    </div>
+  )
+
   return (
   return (
     <ToggleInput
     <ToggleInput
-      tooltip={collapsed ? 'Expand' : 'Collapse'}
+      tooltip={tooltip}
       toggle={shapes.every(s => s.props.type === 'logseq-portal')}
       toggle={shapes.every(s => s.props.type === 'logseq-portal')}
       className="tl-button"
       className="tl-button"
       pressed={collapsed}
       pressed={collapsed}
-      onPressedChange={() => app.api.setCollapsed(!collapsed) }
+      onPressedChange={() => app.api.setCollapsed(!collapsed)}
     >
     >
       <TablerIcon name={collapsed ? 'object-expanded' : 'object-compact'} />
       <TablerIcon name={collapsed ? 'object-expanded' : 'object-compact'} />
     </ToggleInput>
     </ToggleInput>
@@ -306,12 +312,7 @@ const GeometryAction = observer(() => {
     app.api.convertShapes(type)
     app.api.convertShapes(type)
   }, [])
   }, [])
 
 
-  return (
-    <GeometryTools
-      popoverSide="top"
-      chevron={false}
-      setGeometry={handleSetGeometry}/>
-  )
+  return <GeometryTools popoverSide="top" chevron={false} setGeometry={handleSetGeometry} />
 })
 })
 
 
 const StrokeTypeAction = observer(() => {
 const StrokeTypeAction = observer(() => {

+ 13 - 57
tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx

@@ -4,6 +4,7 @@ import { MOD_KEY, AlignType, DistributeType, isDev, EXPORT_PADDING } from '@tldr
 import { observer } from 'mobx-react-lite'
 import { observer } from 'mobx-react-lite'
 import { TablerIcon } from '../icons'
 import { TablerIcon } from '../icons'
 import { Button } from '../Button'
 import { Button } from '../Button'
+import { KeyboardShortcut } from '../KeyboardShortcut'
 import * as React from 'react'
 import * as React from 'react'
 
 
 import * as ReactContextMenu from '@radix-ui/react-context-menu'
 import * as ReactContextMenu from '@radix-ui/react-context-menu'
@@ -138,11 +139,7 @@ export const ContextMenu = observer(function ContextMenu({
                 onClick={() => runAndTransition(app.api.zoomToSelection)}
                 onClick={() => runAndTransition(app.api.zoomToSelection)}
               >
               >
                 Zoom to fit
                 Zoom to fit
-                <div className="tl-menu-right-slot">
-                  <span className="keyboard-shortcut">
-                    <code>⇧</code> <code>2</code>
-                  </span>
-                </div>
+                <KeyboardShortcut action="whiteboard/zoom-to-fit" />
               </ReactContextMenu.Item>
               </ReactContextMenu.Item>
               <ReactContextMenu.Separator className="menu-separator" />
               <ReactContextMenu.Separator className="menu-separator" />
             </>
             </>
@@ -159,11 +156,7 @@ export const ContextMenu = observer(function ContextMenu({
                   >
                   >
                     <TablerIcon className="tl-menu-icon" name="ungroup" />
                     <TablerIcon className="tl-menu-icon" name="ungroup" />
                     Ungroup
                     Ungroup
-                    <div className="tl-menu-right-slot">
-                      <span className="keyboard-shortcut">
-                        <code>{MOD_KEY}</code> <code>⇧</code> <code>G</code>
-                      </span>
-                    </div>
+                    <KeyboardShortcut action="whiteboard/ungroup" />
                   </ReactContextMenu.Item>
                   </ReactContextMenu.Item>
                 )}
                 )}
                 {app.selectedShapesArray.length > 1 &&
                 {app.selectedShapesArray.length > 1 &&
@@ -174,11 +167,7 @@ export const ContextMenu = observer(function ContextMenu({
                     >
                     >
                       <TablerIcon className="tl-menu-icon" name="group" />
                       <TablerIcon className="tl-menu-icon" name="group" />
                       Group
                       Group
-                      <div className="tl-menu-right-slot">
-                        <span className="keyboard-shortcut">
-                          <code>{MOD_KEY}</code> <code>G</code>
-                        </span>
-                      </div>
+                      <KeyboardShortcut action="whiteboard/group" />
                     </ReactContextMenu.Item>
                     </ReactContextMenu.Item>
                   )}
                   )}
                 <ReactContextMenu.Separator className="menu-separator" />
                 <ReactContextMenu.Separator className="menu-separator" />
@@ -193,11 +182,6 @@ export const ContextMenu = observer(function ContextMenu({
                 >
                 >
                   <TablerIcon className="tl-menu-icon" name="cut" />
                   <TablerIcon className="tl-menu-icon" name="cut" />
                   Cut
                   Cut
-                  <div className="tl-menu-right-slot">
-                    <span className="keyboard-shortcut">
-                      <code>{MOD_KEY}</code> <code>X</code>
-                    </span>
-                  </div>
                 </ReactContextMenu.Item>
                 </ReactContextMenu.Item>
               )}
               )}
               <ReactContextMenu.Item
               <ReactContextMenu.Item
@@ -206,11 +190,7 @@ export const ContextMenu = observer(function ContextMenu({
               >
               >
                 <TablerIcon className="tl-menu-icon" name="copy" />
                 <TablerIcon className="tl-menu-icon" name="copy" />
                 Copy
                 Copy
-                <div className="tl-menu-right-slot">
-                  <span className="keyboard-shortcut">
-                    <code>{MOD_KEY}</code> <code>C</code>
-                  </span>
-                </div>
+                <KeyboardShortcut action="editor/copy" />
               </ReactContextMenu.Item>
               </ReactContextMenu.Item>
             </>
             </>
           )}
           )}
@@ -223,7 +203,7 @@ export const ContextMenu = observer(function ContextMenu({
               Paste
               Paste
               <div className="tl-menu-right-slot">
               <div className="tl-menu-right-slot">
                 <span className="keyboard-shortcut">
                 <span className="keyboard-shortcut">
-                  <code>{MOD_KEY}</code> <code>V</code>
+                  <code>{MOD_KEY}</code> <code>v</code>
                 </span>
                 </span>
               </div>
               </div>
             </ReactContextMenu.Item>
             </ReactContextMenu.Item>
@@ -236,7 +216,7 @@ export const ContextMenu = observer(function ContextMenu({
               Paste as link
               Paste as link
               <div className="tl-menu-right-slot">
               <div className="tl-menu-right-slot">
                 <span className="keyboard-shortcut">
                 <span className="keyboard-shortcut">
-                  <code>{MOD_KEY}</code> <code>⇧</code> <code>V</code>
+                  <code>{MOD_KEY}</code> <code>⇧</code> <code>v</code>
                 </span>
                 </span>
               </div>
               </div>
             </ReactContextMenu.Item>
             </ReactContextMenu.Item>
@@ -272,11 +252,7 @@ export const ContextMenu = observer(function ContextMenu({
             onClick={() => runAndTransition(app.api.selectAll)}
             onClick={() => runAndTransition(app.api.selectAll)}
           >
           >
             Select all
             Select all
-            <div className="tl-menu-right-slot">
-              <span className="keyboard-shortcut">
-                <code>{MOD_KEY}</code> <code>A</code>
-              </span>
-            </div>
+            <KeyboardShortcut action="editor/select-parent" />
           </ReactContextMenu.Item>
           </ReactContextMenu.Item>
           {app.selectedShapes?.size > 1 && (
           {app.selectedShapes?.size > 1 && (
             <ReactContextMenu.Item
             <ReactContextMenu.Item
@@ -293,11 +269,7 @@ export const ContextMenu = observer(function ContextMenu({
             >
             >
               <TablerIcon className="tl-menu-icon" name="lock" />
               <TablerIcon className="tl-menu-icon" name="lock" />
               Lock
               Lock
-              <div className="tl-menu-right-slot">
-                <span className="keyboard-shortcut">
-                <code>{MOD_KEY}</code> <code>L</code>
-                </span>
-              </div>
+              <KeyboardShortcut action="whiteboard/lock" />
             </ReactContextMenu.Item>
             </ReactContextMenu.Item>
           )}
           )}
           {app.selectedShapes?.size > 0 && app.selectedShapesArray?.some(s => s.props.isLocked) && (
           {app.selectedShapes?.size > 0 && app.selectedShapesArray?.some(s => s.props.isLocked) && (
@@ -307,11 +279,7 @@ export const ContextMenu = observer(function ContextMenu({
             >
             >
               <TablerIcon className="tl-menu-icon" name="lock-open" />
               <TablerIcon className="tl-menu-icon" name="lock-open" />
               Unlock
               Unlock
-              <div className="tl-menu-right-slot">
-                <span className="keyboard-shortcut">
-                <code>{MOD_KEY}</code> <code>⇧</code> <code>L</code>
-                </span>
-              </div>
+              <KeyboardShortcut action="whiteboard/unlock" />
             </ReactContextMenu.Item>
             </ReactContextMenu.Item>
           )}
           )}
           {app.selectedShapes?.size > 0 &&
           {app.selectedShapes?.size > 0 &&
@@ -324,11 +292,7 @@ export const ContextMenu = observer(function ContextMenu({
                 >
                 >
                   <TablerIcon className="tl-menu-icon" name="backspace" />
                   <TablerIcon className="tl-menu-icon" name="backspace" />
                   Delete
                   Delete
-                  <div className="tl-menu-right-slot">
-                    <span className="keyboard-shortcut">
-                      <code>Del</code>
-                    </span>
-                  </div>
+                  <KeyboardShortcut action="editor/delete" />
                 </ReactContextMenu.Item>
                 </ReactContextMenu.Item>
                 {app.selectedShapes?.size > 1 && !app.readOnly && (
                 {app.selectedShapes?.size > 1 && !app.readOnly && (
                   <>
                   <>
@@ -357,22 +321,14 @@ export const ContextMenu = observer(function ContextMenu({
                       onClick={() => runAndTransition(app.bringToFront)}
                       onClick={() => runAndTransition(app.bringToFront)}
                     >
                     >
                       Move to front
                       Move to front
-                      <div className="tl-menu-right-slot">
-                        <span className="keyboard-shortcut">
-                          <code>⇧</code> <code>]</code>
-                        </span>
-                      </div>
+                      <KeyboardShortcut action="whiteboard/bring-to-front" />
                     </ReactContextMenu.Item>
                     </ReactContextMenu.Item>
                     <ReactContextMenu.Item
                     <ReactContextMenu.Item
                       className="tl-menu-item"
                       className="tl-menu-item"
                       onClick={() => runAndTransition(app.sendToBack)}
                       onClick={() => runAndTransition(app.sendToBack)}
                     >
                     >
                       Move to back
                       Move to back
-                      <div className="tl-menu-right-slot">
-                        <span className="keyboard-shortcut">
-                          <code>⇧</code> <code>[</code>
-                        </span>
-                      </div>
+                      <KeyboardShortcut action="whiteboard/send-to-back" />
                     </ReactContextMenu.Item>
                     </ReactContextMenu.Item>
                   </>
                   </>
                 )}
                 )}

+ 16 - 0
tldraw/apps/tldraw-logseq/src/components/KeyboardShortcut/KeyboardShortcut.tsx

@@ -0,0 +1,16 @@
+import { LogseqContext } from '../../lib/logseq-context'
+import * as React from 'react'
+
+export const KeyboardShortcut = ({
+  action,
+  ...props
+}: { action: string } & React.HTMLAttributes<HTMLElement>) => {
+  const { renderers } = React.useContext(LogseqContext)
+  const Shortcut = renderers?.KeyboardShortcut
+
+  return (
+    <div className="tl-menu-right-slot" {...props}>
+      <Shortcut action={action} />
+    </div>
+  )
+}

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

@@ -0,0 +1 @@
+export * from './KeyboardShortcut'

+ 52 - 19
tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx

@@ -15,16 +15,15 @@ export const PrimaryTools = observer(function PrimaryTools() {
     app.api.setColor(color)
     app.api.setColor(color)
   }, [])
   }, [])
 
 
-  const handleToolClick = React.useCallback(
-    (e: React.MouseEvent<HTMLButtonElement>) => {
-      const tool = e.currentTarget.dataset.tool
-      if (tool) app.selectTool(tool)
-    },
-    []
-  )
+  const handleToolClick = React.useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
+    const tool = e.currentTarget.dataset.tool
+    if (tool) app.selectTool(tool)
+  }, [])
 
 
   const [activeGeomId, setActiveGeomId] = React.useState(
   const [activeGeomId, setActiveGeomId] = React.useState(
-    () => (Object.values(Geometry).find((geo: string) => geo === app.selectedTool.id) ?? Object.values(Geometry)[0])
+    () =>
+      Object.values(Geometry).find((geo: string) => geo === app.selectedTool.id) ??
+      Object.values(Geometry)[0]
   )
   )
 
 
   React.useEffect(() => {
   React.useEffect(() => {
@@ -33,25 +32,59 @@ export const PrimaryTools = observer(function PrimaryTools() {
     })
     })
   }, [app.selectedTool.id])
   }, [app.selectedTool.id])
 
 
-
   return (
   return (
     <div className="tl-primary-tools" data-html2canvas-ignore="true">
     <div className="tl-primary-tools" data-html2canvas-ignore="true">
       <div className="tl-toolbar tl-tools-floating-panel">
       <div className="tl-toolbar tl-tools-floating-panel">
-        <ToolButton handleClick={() =>app.selectTool("select")} tooltip="Select" id="select" icon="select-cursor" />
         <ToolButton
         <ToolButton
-         handleClick={() =>app.selectTool("move")}
-          tooltip="Move"
+          handleClick={() => app.selectTool('select')}
+          tooltip="Select"
+          id="select"
+          icon="select-cursor"
+        />
+        <ToolButton
+          handleClick={() => app.selectTool('move')}
+          tooltip="Pan"
           id="move"
           id="move"
           icon={app.isIn('move.panning') ? 'hand-grab' : 'hand-stop'}
           icon={app.isIn('move.panning') ? 'hand-grab' : 'hand-stop'}
         />
         />
         <Separator.Root className="tl-toolbar-separator" orientation="horizontal" />
         <Separator.Root className="tl-toolbar-separator" orientation="horizontal" />
-        <ToolButton handleClick={() =>app.selectTool("logseq-portal")} tooltip="Add block or page" id="logseq-portal" icon="circle-plus" />
-        <ToolButton handleClick={() =>app.selectTool("pencil")} tooltip="Draw" id="pencil" icon="ballpen" />
-        <ToolButton handleClick={() =>app.selectTool("highlighter")} tooltip="Highlight" id="highlighter" icon="highlight" />
-        <ToolButton handleClick={() =>app.selectTool("erase")} tooltip="Eraser" id="erase" icon="eraser" />
-        <ToolButton handleClick={() =>app.selectTool("line")} tooltip="Connector" id="line" icon="connector" />
-        <ToolButton handleClick={() =>app.selectTool("text")} tooltip="Text" id="text" icon="text" />
-        <GeometryTools activeGeometry={activeGeomId} setGeometry={handleToolClick}/>
+        <ToolButton
+          handleClick={() => app.selectTool('logseq-portal')}
+          tooltip="Add block or page"
+          id="logseq-portal"
+          icon="circle-plus"
+        />
+        <ToolButton
+          handleClick={() => app.selectTool('pencil')}
+          tooltip="Draw"
+          id="pencil"
+          icon="ballpen"
+        />
+        <ToolButton
+          handleClick={() => app.selectTool('highlighter')}
+          tooltip="Highlight"
+          id="highlighter"
+          icon="highlight"
+        />
+        <ToolButton
+          handleClick={() => app.selectTool('erase')}
+          tooltip="Eraser"
+          id="erase"
+          icon="eraser"
+        />
+        <ToolButton
+          handleClick={() => app.selectTool('line')}
+          tooltip="Connector"
+          id="line"
+          icon="connector"
+        />
+        <ToolButton
+          handleClick={() => app.selectTool('text')}
+          tooltip="Text"
+          id="text"
+          icon="text"
+        />
+        <GeometryTools activeGeometry={activeGeomId} setGeometry={handleToolClick} />
         <Separator.Root
         <Separator.Root
           className="tl-toolbar-separator"
           className="tl-toolbar-separator"
           orientation="horizontal"
           orientation="horizontal"

+ 30 - 30
tldraw/apps/tldraw-logseq/src/components/ToolButton/ToolButton.tsx

@@ -5,6 +5,7 @@ import { observer } from 'mobx-react-lite'
 import type * as React from 'react'
 import type * as React from 'react'
 import { Button } from '../Button'
 import { Button } from '../Button'
 import { TablerIcon } from '../icons'
 import { TablerIcon } from '../icons'
+import { KeyboardShortcut } from '../KeyboardShortcut'
 
 
 export interface ToolButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
 export interface ToolButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
   id: string
   id: string
@@ -14,37 +15,36 @@ export interface ToolButtonProps extends React.ButtonHTMLAttributes<HTMLButtonEl
   handleClick: (e: React.MouseEvent<HTMLButtonElement>) => void
   handleClick: (e: React.MouseEvent<HTMLButtonElement>) => void
 }
 }
 
 
-export const ToolButton = observer(({ id, icon, tooltip, tooltipSide = "left", handleClick, ...props }: ToolButtonProps) => {
-  const app = useApp()
+export const ToolButton = observer(
+  ({ id, icon, tooltip, tooltipSide = 'left', handleClick, ...props }: ToolButtonProps) => {
+    const app = useApp()
 
 
-  // Tool must exist
-  const Tool = [...app.Tools, TLSelectTool, TLMoveTool]?.find(T => T.id === id)
+    // Tool must exist
+    const Tool = [...app.Tools, TLSelectTool, TLMoveTool]?.find(T => T.id === id)
 
 
-  const shortcuts = (Tool as any)?.['shortcut']
+    const shortcuts = (Tool as any)?.['shortcut']
 
 
-  const tooltipContent = shortcuts && tooltip ? (
-    <>
-      {tooltip}
-      <span className="ml-2 keyboard-shortcut">
-        {shortcuts
-          .map((shortcut: string, idx: number) => <code key={idx}>{shortcut.toUpperCase()}</code>)
-          .reduce((prev: React.ReactNode, curr: React.ReactNode) => [prev, ' | ', curr])}
-      </span>
-    </>
-  ) : (
-    tooltip
-  )
+    const tooltipContent =
+      shortcuts && tooltip ? (
+        <div className="flex">
+          {tooltip}
+          <KeyboardShortcut action={shortcuts} />
+        </div>
+      ) : (
+        tooltip
+      )
 
 
-  return (
-    <Button
-      {...props}
-      tooltipSide={tooltipSide}
-      tooltip={tooltipContent}
-      data-tool={id}
-      data-selected={id === app.selectedTool.id}
-      onClick={handleClick}
-    >
-      {typeof icon === 'string' ? <TablerIcon name={icon} /> : icon}
-    </Button>
-  )
-})
+    return (
+      <Button
+        {...props}
+        tooltipSide={tooltipSide}
+        tooltip={tooltipContent}
+        data-tool={id}
+        data-selected={id === app.selectedTool.id}
+        onClick={handleClick}
+      >
+        {typeof icon === 'string' ? <TablerIcon name={icon} /> : icon}
+      </Button>
+    )
+  }
+)

+ 7 - 25
tldraw/apps/tldraw-logseq/src/components/ZoomMenu/ZoomMenu.tsx

@@ -1,7 +1,9 @@
 import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
 import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
 import { useApp } from '@tldraw/react'
 import { useApp } from '@tldraw/react'
+import { KeyboardShortcut } from '../KeyboardShortcut'
 import { MOD_KEY } from '@tldraw/core'
 import { MOD_KEY } from '@tldraw/core'
 import { observer } from 'mobx-react-lite'
 import { observer } from 'mobx-react-lite'
+import * as React from 'react'
 
 
 export const ZoomMenu = observer(function ZoomMenu(): JSX.Element {
 export const ZoomMenu = observer(function ZoomMenu(): JSX.Element {
   const app = useApp()
   const app = useApp()
@@ -26,11 +28,7 @@ export const ZoomMenu = observer(function ZoomMenu(): JSX.Element {
           onClick={app.api.zoomToFit}
           onClick={app.api.zoomToFit}
         >
         >
           Zoom to drawing
           Zoom to drawing
-          <div className="tl-menu-right-slot">
-            <span className="keyboard-shortcut">
-              <code>⇧</code> <code>1</code>
-            </span>
-          </div>
+          <KeyboardShortcut action="whiteboard/zoom-to-fit" />
         </DropdownMenuPrimitive.Item>
         </DropdownMenuPrimitive.Item>
         <DropdownMenuPrimitive.Item
         <DropdownMenuPrimitive.Item
           className="tl-menu-item"
           className="tl-menu-item"
@@ -39,11 +37,7 @@ export const ZoomMenu = observer(function ZoomMenu(): JSX.Element {
           disabled={app.selectedShapesArray.length === 0}
           disabled={app.selectedShapesArray.length === 0}
         >
         >
           Zoom to fit selection
           Zoom to fit selection
-          <div className="tl-menu-right-slot">
-            <span className="keyboard-shortcut">
-              <code>⇧</code> <code>2</code>
-            </span>
-          </div>
+          <KeyboardShortcut action="whiteboard/zoom-to-selection" />
         </DropdownMenuPrimitive.Item>
         </DropdownMenuPrimitive.Item>
         <DropdownMenuPrimitive.Item
         <DropdownMenuPrimitive.Item
           className="tl-menu-item"
           className="tl-menu-item"
@@ -51,11 +45,7 @@ export const ZoomMenu = observer(function ZoomMenu(): JSX.Element {
           onClick={app.api.zoomIn}
           onClick={app.api.zoomIn}
         >
         >
           Zoom in
           Zoom in
-          <div className="tl-menu-right-slot">
-            <span className="keyboard-shortcut">
-              <code>{MOD_KEY}</code> <code>+</code>
-            </span>
-          </div>
+          <KeyboardShortcut action="whiteboard/zoom-in" />
         </DropdownMenuPrimitive.Item>
         </DropdownMenuPrimitive.Item>
         <DropdownMenuPrimitive.Item
         <DropdownMenuPrimitive.Item
           className="tl-menu-item"
           className="tl-menu-item"
@@ -63,11 +53,7 @@ export const ZoomMenu = observer(function ZoomMenu(): JSX.Element {
           onClick={app.api.zoomOut}
           onClick={app.api.zoomOut}
         >
         >
           Zoom out
           Zoom out
-          <div className="tl-menu-right-slot">
-            <span className="keyboard-shortcut">
-              <code>{MOD_KEY}</code> <code>-</code>
-            </span>
-          </div>
+          <KeyboardShortcut action="whiteboard/zoom-out" />
         </DropdownMenuPrimitive.Item>
         </DropdownMenuPrimitive.Item>
         <DropdownMenuPrimitive.Item
         <DropdownMenuPrimitive.Item
           className="tl-menu-item"
           className="tl-menu-item"
@@ -75,11 +61,7 @@ export const ZoomMenu = observer(function ZoomMenu(): JSX.Element {
           onClick={app.api.resetZoom}
           onClick={app.api.resetZoom}
         >
         >
           Reset zoom
           Reset zoom
-          <div className="tl-menu-right-slot">
-            <span className="keyboard-shortcut">
-              <code>⇧</code> <code>0</code>
-            </span>
-          </div>
+          <KeyboardShortcut action="whiteboard/reset-zoom" />
         </DropdownMenuPrimitive.Item>
         </DropdownMenuPrimitive.Item>
       </DropdownMenuPrimitive.Content>
       </DropdownMenuPrimitive.Content>
     </DropdownMenuPrimitive.Root>
     </DropdownMenuPrimitive.Root>

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

@@ -4,7 +4,7 @@ import * as Toggle from '@radix-ui/react-toggle'
 interface ToggleInputProps extends React.HTMLAttributes<HTMLElement> {
 interface ToggleInputProps extends React.HTMLAttributes<HTMLElement> {
   toggle?: boolean
   toggle?: boolean
   pressed: boolean
   pressed: boolean
-  tooltip?: string
+  tooltip?: React.ReactNode
   onPressedChange: (value: boolean) => void
   onPressedChange: (value: boolean) => void
 }
 }
 
 

+ 3 - 0
tldraw/apps/tldraw-logseq/src/lib/logseq-context.ts

@@ -37,6 +37,9 @@ export interface LogseqContextValue {
         renderFn?: (open?: boolean, count?: number) => React.ReactNode
         renderFn?: (open?: boolean, count?: number) => React.ReactNode
       }
       }
     }>
     }>
+    KeyboardShortcut: React.FC<{
+      action: string
+    }>
   }
   }
   handlers: {
   handlers: {
     search: (
     search: (

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

@@ -4,6 +4,6 @@ import { BoxShape, type Shape } from '../shapes'
 
 
 export class BoxTool extends TLBoxTool<BoxShape, Shape, TLReactEventMap> {
 export class BoxTool extends TLBoxTool<BoxShape, Shape, TLReactEventMap> {
   static id = 'box'
   static id = 'box'
-  static shortcut = ['9', 'r']
+  static shortcut = 'whiteboard/rectangle'
   Shape = BoxShape
   Shape = BoxShape
 }
 }

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

@@ -4,6 +4,6 @@ import { EllipseShape, type Shape } from '../shapes'
 
 
 export class EllipseTool extends TLBoxTool<EllipseShape, Shape, TLReactEventMap> {
 export class EllipseTool extends TLBoxTool<EllipseShape, Shape, TLReactEventMap> {
   static id = 'ellipse'
   static id = 'ellipse'
-  static shortcut = ['o']
+  static shortcut = 'whiteboard/ellipse'
   Shape = EllipseShape
   Shape = EllipseShape
 }
 }

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

@@ -4,5 +4,5 @@ import type { Shape } from '../shapes'
 
 
 export class NuEraseTool extends TLEraseTool<Shape, TLReactEventMap> {
 export class NuEraseTool extends TLEraseTool<Shape, TLReactEventMap> {
   static id = 'erase'
   static id = 'erase'
-  static shortcut = ['6', 'e']
+  static shortcut = 'whiteboard/eraser'
 }
 }

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

@@ -4,7 +4,7 @@ import { HighlighterShape, type Shape } from '../shapes'
 
 
 export class HighlighterTool extends TLDrawTool<HighlighterShape, Shape, TLReactEventMap> {
 export class HighlighterTool extends TLDrawTool<HighlighterShape, Shape, TLReactEventMap> {
   static id = 'highlighter'
   static id = 'highlighter'
-  static shortcut = ['5', 'h']
+  static shortcut = 'whiteboard/highlighter'
   Shape = HighlighterShape
   Shape = HighlighterShape
   simplify = true
   simplify = true
   simplifyTolerance = 0.618
   simplifyTolerance = 0.618

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

@@ -5,6 +5,6 @@ import { LineShape, type Shape } from '../shapes'
 // @ts-expect-error maybe later
 // @ts-expect-error maybe later
 export class LineTool extends TLLineTool<LineShape, Shape, TLReactEventMap> {
 export class LineTool extends TLLineTool<LineShape, Shape, TLReactEventMap> {
   static id = 'line'
   static id = 'line'
-  static shortcut = ['7', 'c']
+  static shortcut = 'whiteboard/connector'
   Shape = LineShape
   Shape = LineShape
 }
 }

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

@@ -9,7 +9,7 @@ export class LogseqPortalTool extends TLTool<
   TLApp<Shape, TLReactEventMap>
   TLApp<Shape, TLReactEventMap>
 > {
 > {
   static id = 'logseq-portal'
   static id = 'logseq-portal'
-  static shortcut = ['3']
+  static shortcut = 'whiteboard/portal'
   static states = [IdleState, CreatingState]
   static states = [IdleState, CreatingState]
   static initial = 'idle'
   static initial = 'idle'
 
 

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

@@ -4,7 +4,7 @@ import { PencilShape, type Shape } from '../shapes'
 
 
 export class PencilTool extends TLDrawTool<PencilShape, Shape, TLReactEventMap> {
 export class PencilTool extends TLDrawTool<PencilShape, Shape, TLReactEventMap> {
   static id = 'pencil'
   static id = 'pencil'
-  static shortcut = ['4', 'd']
+  static shortcut = 'whiteboard/pencil'
   Shape = PencilShape
   Shape = PencilShape
   simplify = false
   simplify = false
 }
 }

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

@@ -4,6 +4,6 @@ import { TextShape, type Shape } from '../shapes'
 
 
 export class TextTool extends TLTextTool<TextShape, Shape, TLReactEventMap> {
 export class TextTool extends TLTextTool<TextShape, Shape, TLReactEventMap> {
   static id = 'text'
   static id = 'text'
-  static shortcut = ['8', 't']
+  static shortcut = 'whiteboard/text'
   Shape = TextShape
   Shape = TextShape
 }
 }

+ 1 - 1
tldraw/apps/tldraw-logseq/src/styles.css

@@ -147,7 +147,7 @@ html[data-theme='light'] {
 
 
 .tl-menu-right-slot {
 .tl-menu-right-slot {
   margin-left: auto;
   margin-left: auto;
-  padding-left: 40px;
+  padding-left: 20px;
 
 
   .keyboard-shortcut > code {
   .keyboard-shortcut > code {
     padding: 4px !important;
     padding: 4px !important;

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

@@ -43,7 +43,9 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
    *
    *
    * @param shapes The serialized shape changes to apply.
    * @param shapes The serialized shape changes to apply.
    */
    */
-  updateShapes = <T extends S>(...shapes: ({ id: string, type: string } & Partial<T['props']>)[]): this => {
+  updateShapes = <T extends S>(
+    ...shapes: ({ id: string; type: string } & Partial<T['props']>)[]
+  ): this => {
     this.app.updateShapes(shapes)
     this.app.updateShapes(shapes)
     return this
     return this
   }
   }
@@ -196,16 +198,6 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
     return this
     return this
   }
   }
 
 
-  save = () => {
-    this.app.save()
-    return this
-  }
-
-  saveAs = () => {
-    this.app.save()
-    return this
-  }
-
   undo = () => {
   undo = () => {
     this.app.undo()
     this.app.undo()
     return this
     return this
@@ -445,8 +437,7 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
 
 
   setCollapsed = (collapsed: boolean, shapes: S[] = this.app.allSelectedShapesArray) => {
   setCollapsed = (collapsed: boolean, shapes: S[] = this.app.allSelectedShapesArray) => {
     shapes.forEach(shape => {
     shapes.forEach(shape => {
-      if (shape.props.type === 'logseq-portal')
-        shape.setCollapsed(collapsed)
+      if (shape.props.type === 'logseq-portal') shape.setCollapsed(collapsed)
     })
     })
     this.app.persist()
     this.app.persist()
   }
   }

+ 5 - 170
tldraw/packages/core/src/lib/TLApp/TLApp.ts

@@ -17,14 +17,7 @@ import type {
   TLSubscriptionEventName,
   TLSubscriptionEventName,
 } from '../../types'
 } from '../../types'
 import { AlignType, DistributeType } from '../../types'
 import { AlignType, DistributeType } from '../../types'
-import {
-  BoundsUtils,
-  createNewLineBinding,
-  dedupe,
-  isNonNullable,
-  KeyUtils,
-  uniqueId,
-} from '../../utils'
+import { BoundsUtils, createNewLineBinding, dedupe, isNonNullable, uniqueId } from '../../utils'
 import type { TLShape, TLShapeConstructor, TLShapeModel } from '../shapes'
 import type { TLShape, TLShapeConstructor, TLShapeModel } from '../shapes'
 import { TLApi } from '../TLApi'
 import { TLApi } from '../TLApi'
 import { TLCursors } from '../TLCursors'
 import { TLCursors } from '../TLCursors'
@@ -77,7 +70,6 @@ export class TLApp<
     this.notify('mount', null)
     this.notify('mount', null)
   }
   }
 
 
-  keybindingRegistered = false
   uuid = uniqueId()
   uuid = uniqueId()
 
 
   readOnly: boolean | undefined
   readOnly: boolean | undefined
@@ -93,159 +85,6 @@ export class TLApp<
 
 
   Tools: TLToolConstructor<S, K>[] = []
   Tools: TLToolConstructor<S, K>[] = []
 
 
-  dispose() {
-    super.dispose()
-    this.keybindingRegistered = false
-    return this
-  }
-
-  initKeyboardShortcuts() {
-    if (this.keybindingRegistered) {
-      return
-    }
-    const ownShortcuts: TLShortcut<S, K>[] = [
-      {
-        keys: 'shift+0',
-        fn: () => this.api.resetZoom(),
-      },
-      {
-        keys: 'shift+1',
-        fn: () => this.api.zoomToFit(),
-      },
-      {
-        keys: 'shift+2',
-        fn: () => this.api.zoomToSelection(),
-      },
-      {
-        keys: 'mod+up',
-        fn: () => this.api.setCollapsed(true),
-      },
-      {
-        keys: 'mod+down',
-        fn: () => this.api.setCollapsed(false),
-      },
-      {
-        keys: 'mod+-',
-        fn: () => this.api.zoomOut(),
-      },
-      {
-        keys: 'mod+=',
-        fn: () => this.api.zoomIn(),
-      },
-      {
-        keys: 'mod+x',
-        fn: () => this.cut(),
-      },
-      {
-        keys: '[',
-        fn: () => this.sendBackward(),
-      },
-      {
-        keys: 'shift+[',
-        fn: () => this.sendToBack(),
-      },
-      {
-        keys: ']',
-        fn: () => this.bringForward(),
-      },
-      {
-        keys: 'shift+]',
-        fn: () => this.bringToFront(),
-      },
-      {
-        keys: 'mod+a',
-        fn: () => {
-          const { selectedTool } = this
-          if (selectedTool.id !== 'select') {
-            this.selectTool('select')
-          }
-          this.api.selectAll()
-        },
-      },
-      {
-        keys: 'mod+shift+s',
-        fn: () => {
-          this.saveAs()
-          this.notify('saveAs', null)
-        },
-      },
-      {
-        keys: 'mod+shift+v',
-        fn: (_, __, e) => {
-          if (!this.editingShape) {
-            e.preventDefault()
-            this.paste(undefined, true)
-          }
-        },
-      },
-      {
-        keys: ['del', 'backspace'],
-        fn: () => {
-          if (!this.editingShape) {
-            this.api.deleteShapes()
-            this.selectedTool.transition('idle')
-          }
-        },
-      },
-      {
-        keys: 'mod+g',
-        fn: () => {
-          this.api.doGroup()
-        },
-      },
-      {
-        keys: 'mod+shift+g',
-        fn: () => {
-          this.api.unGroup()
-        },
-      },
-      {
-        keys: 'shift+g',
-        fn: () => {
-          this.api.toggleGrid()
-        },
-      },
-      {
-        keys: 'mod+l',
-        fn: () => {
-          this.setLocked(true)
-        },
-      },
-      {
-        keys: 'mod+shift+l',
-        fn: () => {
-          this.setLocked(false)
-        },
-      },
-    ]
-    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-    // @ts-ignore
-    const shortcuts = (this.constructor['shortcuts'] || []) as TLShortcut<S, K>[]
-    const childrenShortcuts = Array.from(this.children.values())
-      // @ts-expect-error ???
-      .filter(c => c.constructor['shortcut'])
-      .map(child => {
-        return {
-          // @ts-expect-error ???
-          keys: child.constructor['shortcut'] as string | string[],
-          fn: (_: any, __: any, e: KeyboardEvent) => {
-            this.selectTool(child.id)
-            // hack: allows logseq related shortcut combinations to work
-            // fixme?: unsure if it will cause unexpected issues
-            // e.stopPropagation()
-          },
-        }
-      })
-    this._disposables.push(
-      ...[...ownShortcuts, ...shortcuts, ...childrenShortcuts].map(({ keys, fn }) => {
-        return KeyUtils.registerShortcut(keys, e => {
-          fn(this, this, e)
-        })
-      })
-    )
-    this.keybindingRegistered = true
-  }
-
   /* --------------------- History -------------------- */
   /* --------------------- History -------------------- */
 
 
   history = new TLHistory<S, K>(this)
   history = new TLHistory<S, K>(this)
@@ -292,12 +131,6 @@ export class TLApp<
     return this
     return this
   }
   }
 
 
-  saveAs = (): this => {
-    // todo
-    this.notify('saveAs', null)
-    return this
-  }
-
   @computed get serialized(): TLDocumentModel<S> {
   @computed get serialized(): TLDocumentModel<S> {
     return {
     return {
       // currentPageId: this.currentPageId,
       // currentPageId: this.currentPageId,
@@ -355,14 +188,16 @@ export class TLApp<
     return this
     return this
   }
   }
 
 
-  @action updateShapes = <T extends S>(shapes: ({ id: string, type: string } & Partial<T['props']>)[]): this => {
+  @action updateShapes = <T extends S>(
+    shapes: ({ id: string; type: string } & Partial<T['props']>)[]
+  ): this => {
     if (this.readOnly) return this
     if (this.readOnly) return this
 
 
     shapes.forEach(shape => {
     shapes.forEach(shape => {
       const oldShape = this.getShapeById(shape.id)
       const oldShape = this.getShapeById(shape.id)
       oldShape?.update(shape)
       oldShape?.update(shape)
       if (shape.type !== oldShape?.type) {
       if (shape.type !== oldShape?.type) {
-        this.api.convertShapes(shape.type , [oldShape])
+        this.api.convertShapes(shape.type, [oldShape])
       }
       }
     })
     })
     this.persist()
     this.persist()

+ 0 - 21
tldraw/packages/core/src/lib/TLState.ts

@@ -9,7 +9,6 @@ import type {
   TLShortcut,
   TLShortcut,
   TLEvents,
   TLEvents,
 } from '../types'
 } from '../types'
-import { KeyUtils } from '../utils'
 import type { TLShape } from './shapes'
 import type { TLShape } from './shapes'
 
 
 export interface TLStateClass<
 export interface TLStateClass<
@@ -120,7 +119,6 @@ export abstract class TLRootState<S extends TLShape, K extends TLEventMap>
       if (this.currentState) {
       if (this.currentState) {
         prevState._events.onExit({ ...data, toId: id })
         prevState._events.onExit({ ...data, toId: id })
         prevState.dispose()
         prevState.dispose()
-        nextState.registerKeyboardShortcuts()
         this.setCurrentState(nextState)
         this.setCurrentState(nextState)
         this._events.onTransition({ ...data, fromId: prevState.id, toId: id })
         this._events.onTransition({ ...data, fromId: prevState.id, toId: id })
         nextState._events.onEnter({ ...data, fromId: prevState.id })
         nextState._events.onEnter({ ...data, fromId: prevState.id })
@@ -404,34 +402,15 @@ export abstract class TLState<
       }
       }
     }
     }
 
 
-    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-    // @ts-ignore
-    const shortcuts = this.constructor['shortcuts'] as TLShortcut<S, K>[]
-    this._shortcuts = shortcuts
-
     makeObservable(this)
     makeObservable(this)
   }
   }
 
 
   static cursor?: TLCursor
   static cursor?: TLCursor
 
 
-  registerKeyboardShortcuts = (): void => {
-    if (!this._shortcuts?.length) return
-
-    this._disposables.push(
-      ...this._shortcuts.map(({ keys, fn }) => {
-        return KeyUtils.registerShortcut(keys, event => {
-          if (!this.isActive) return
-          fn(this.root, this, event)
-        })
-      })
-    )
-  }
-
   /* --------------------- States --------------------- */
   /* --------------------- States --------------------- */
 
 
   protected _root: R
   protected _root: R
   protected _parent: P
   protected _parent: P
-  private _shortcuts: TLShortcut<S, K>[] = []
 
 
   get root() {
   get root() {
     return this._root
     return this._root

+ 1 - 1
tldraw/packages/core/src/lib/tools/TLMoveTool/TLMoveTool.ts

@@ -10,7 +10,7 @@ export class TLMoveTool<
   R extends TLApp<S, K> = TLApp<S, K>
   R extends TLApp<S, K> = TLApp<S, K>
 > extends TLTool<S, K, R> {
 > extends TLTool<S, K, R> {
   static id = 'move'
   static id = 'move'
-  static shortcut = ['2', 'm']
+  static shortcut = 'whiteboard/pan'
 
 
   static states = [IdleState, IdleHoldState, PanningState, PinchingState]
   static states = [IdleState, IdleHoldState, PanningState, PinchingState]
 
 

+ 1 - 1
tldraw/packages/core/src/lib/tools/TLSelectTool/TLSelectTool.tsx

@@ -33,7 +33,7 @@ export class TLSelectTool<
 
 
   static initial = 'idle'
   static initial = 'idle'
 
 
-  static shortcut = ['1', 's']
+  static shortcut = 'whiteboard/select'
 
 
   static states = [
   static states = [
     IdleState,
     IdleState,

+ 3 - 13
tldraw/packages/core/src/types/types.ts

@@ -15,9 +15,9 @@ export enum Color {
 }
 }
 
 
 export enum Geometry {
 export enum Geometry {
-    Box = 'box',
-    Ellipse = 'ellipse',
-    Polygon = 'polygon',
+  Box = 'box',
+  Ellipse = 'ellipse',
+  Polygon = 'polygon',
 }
 }
 
 
 export enum AlignType {
 export enum AlignType {
@@ -163,14 +163,6 @@ export type TLSubscriptionEvent =
       event: 'persist'
       event: 'persist'
       info: { replace: boolean }
       info: { replace: boolean }
     }
     }
-  | {
-      event: 'save'
-      info: null
-    }
-  | {
-      event: 'saveAs'
-      info: null
-    }
   | {
   | {
       event: 'undo'
       event: 'undo'
       info: null
       info: null
@@ -260,8 +252,6 @@ export interface TLCallbacks<
 > {
 > {
   onMount: TLCallback<S, K, R, 'mount'>
   onMount: TLCallback<S, K, R, 'mount'>
   onPersist: TLCallback<S, K, R, 'persist'>
   onPersist: TLCallback<S, K, R, 'persist'>
-  onSave: TLCallback<S, K, R, 'save'>
-  onSaveAs: TLCallback<S, K, R, 'saveAs'>
   onError: TLCallback<S, K, R, 'error'>
   onError: TLCallback<S, K, R, 'error'>
 }
 }
 
 

+ 0 - 32
tldraw/packages/core/src/utils/KeyUtils.ts

@@ -1,32 +0,0 @@
-import Mousetrap from 'mousetrap'
-
-type AvailableTags = 'INPUT' | 'TEXTAREA' | 'SELECT'
-
-const tagFilter = ({ target }: KeyboardEvent, enableOnTags?: AvailableTags[]) => {
-  const targetTagName = target && (target as HTMLElement).tagName
-  return Boolean(
-    targetTagName && enableOnTags && enableOnTags.includes(targetTagName as AvailableTags)
-  )
-}
-
-export class KeyUtils {
-  static registerShortcut(
-    keys: string | string[],
-    callback: (keyboardEvent: Mousetrap.ExtendedKeyboardEvent, combo: string) => void
-  ) {
-    const fn = (keyboardEvent: Mousetrap.ExtendedKeyboardEvent, combo: string): void => {
-      keyboardEvent.preventDefault()
-      if (
-        tagFilter(keyboardEvent, ['INPUT', 'TEXTAREA', 'SELECT']) ||
-        (keyboardEvent.target as HTMLElement)?.isContentEditable
-      ) {
-        return
-      }
-      callback(keyboardEvent, combo)
-    }
-    Mousetrap.bind(keys, fn, 'keydown')
-    return () => {
-      Mousetrap.unbind(keys, 'keydown')
-    }
-  }
-}

+ 7 - 7
tldraw/packages/core/src/utils/index.ts

@@ -1,7 +1,6 @@
 import * as uuid from 'uuid'
 import * as uuid from 'uuid'
 export * from './BoundsUtils'
 export * from './BoundsUtils'
 export * from './PointUtils'
 export * from './PointUtils'
-export * from './KeyUtils'
 export * from './GeomUtils'
 export * from './GeomUtils'
 export * from './PolygonUtils'
 export * from './PolygonUtils'
 export * from './SvgPathUtils'
 export * from './SvgPathUtils'
@@ -81,13 +80,14 @@ export function isDarwin(): boolean {
   return /Mac|iPod|iPhone|iPad/.test(window.navigator.platform)
   return /Mac|iPod|iPhone|iPad/.test(window.navigator.platform)
 }
 }
 
 
-export function isDev():boolean {
-  return window?.logseq?.api?.get_state_from_store?.('ui/developer-mode?') || process.env.NODE_ENV === 'development'
+export function isDev(): boolean {
+  return (
+    window?.logseq?.api?.get_state_from_store?.('ui/developer-mode?') ||
+    process.env.NODE_ENV === 'development'
+  )
 }
 }
 
 
-/**
- * Migrated from frontend.util/safari?
- */
+/** Migrated from frontend.util/safari? */
 export function isSafari(): boolean {
 export function isSafari(): boolean {
   const ua = window.navigator.userAgent.toLowerCase()
   const ua = window.navigator.userAgent.toLowerCase()
   return ua.includes('webkit') && !ua.includes('chrome')
   return ua.includes('webkit') && !ua.includes('chrome')
@@ -102,7 +102,7 @@ export function modKey(e: any): boolean {
   return isDarwin() ? e.metaKey : e.ctrlKey
   return isDarwin() ? e.metaKey : e.ctrlKey
 }
 }
 
 
-export const MOD_KEY = isDarwin() ? '⌘' : 'Ctrl'
+export const MOD_KEY = isDarwin() ? '⌘' : 'ctrl'
 
 
 export function isNonNullable<TValue>(value: TValue): value is NonNullable<TValue> {
 export function isNonNullable<TValue>(value: TValue): value is NonNullable<TValue> {
   return Boolean(value)
   return Boolean(value)

+ 0 - 1
tldraw/packages/react/src/hooks/useAppSetup.ts

@@ -11,7 +11,6 @@ export function useAppSetup<S extends TLReactShape, R extends TLReactApp<S> = TL
   )
   )
 
 
   React.useLayoutEffect(() => {
   React.useLayoutEffect(() => {
-    app.initKeyboardShortcuts()
     return () => {
     return () => {
       app.dispose()
       app.dispose()
     }
     }

+ 1 - 5
tldraw/packages/react/src/hooks/useSetup.ts

@@ -10,8 +10,6 @@ export function useSetup<
 >(app: R, props: TLAppPropsWithApp<S, R> | TLAppPropsWithoutApp<S, R>) {
 >(app: R, props: TLAppPropsWithApp<S, R> | TLAppPropsWithoutApp<S, R>) {
   const {
   const {
     onPersist,
     onPersist,
-    onSave,
-    onSaveAs,
     onError,
     onError,
     onMount,
     onMount,
     onCreateAssets,
     onCreateAssets,
@@ -43,8 +41,6 @@ export function useSetup<
   React.useLayoutEffect(() => {
   React.useLayoutEffect(() => {
     const unsubs: (() => void)[] = []
     const unsubs: (() => void)[] = []
     if (onPersist) unsubs.push(app.subscribe('persist', onPersist))
     if (onPersist) unsubs.push(app.subscribe('persist', onPersist))
-    if (onSave) unsubs.push(app.subscribe('save', onSave))
-    if (onSaveAs) unsubs.push(app.subscribe('saveAs', onSaveAs))
     if (onError) unsubs.push(app.subscribe('error', onError))
     if (onError) unsubs.push(app.subscribe('error', onError))
     if (onCreateShapes) unsubs.push(app.subscribe('create-shapes', onCreateShapes))
     if (onCreateShapes) unsubs.push(app.subscribe('create-shapes', onCreateShapes))
     if (onCreateAssets) unsubs.push(app.subscribe('create-assets', onCreateAssets))
     if (onCreateAssets) unsubs.push(app.subscribe('create-assets', onCreateAssets))
@@ -56,5 +52,5 @@ export function useSetup<
     if (onCanvasDBClick) unsubs.push(app.subscribe('canvas-dbclick', onCanvasDBClick))
     if (onCanvasDBClick) unsubs.push(app.subscribe('canvas-dbclick', onCanvasDBClick))
     // Kind of unusual, is this the right pattern?
     // Kind of unusual, is this the right pattern?
     return () => unsubs.forEach(unsub => unsub())
     return () => unsubs.forEach(unsub => unsub())
-  }, [app, onPersist, onSave, onSaveAs, onError])
+  }, [app, onPersist, onError])
 }
 }

+ 0 - 2
tldraw/packages/react/src/types/TLReactSubscriptions.tsx

@@ -31,8 +31,6 @@ export interface TLReactCallbacks<
 > {
 > {
   onMount: TLReactCallback<S, R, 'mount'>
   onMount: TLReactCallback<S, R, 'mount'>
   onPersist: TLReactCallback<S, R, 'persist'>
   onPersist: TLReactCallback<S, R, 'persist'>
-  onSave: TLReactCallback<S, R, 'save'>
-  onSaveAs: TLReactCallback<S, R, 'saveAs'>
   onError: TLReactCallback<S, R, 'error'>
   onError: TLReactCallback<S, R, 'error'>
   onCreateShapes: TLReactCallback<S, R, 'create-shapes'>
   onCreateShapes: TLReactCallback<S, R, 'create-shapes'>
   onCreateAssets: TLReactCallback<S, R, 'create-assets'>
   onCreateAssets: TLReactCallback<S, R, 'create-assets'>