瀏覽代碼

Merge branch 'master' into chore/update-non-en-dicts

Bad3r 2 年之前
父節點
當前提交
68a339c227
共有 27 個文件被更改,包括 360 次插入172 次删除
  1. 29 17
      e2e-tests/whiteboards.spec.ts
  2. 9 0
      scripts/src/logseq/tasks/lang.clj
  3. 14 13
      src/main/frontend/commands.cljs
  4. 7 2
      src/main/frontend/components/settings.cljs
  5. 3 1
      src/main/frontend/extensions/tldraw.cljs
  6. 1 1
      src/main/frontend/handler/common/config_edn.cljs
  7. 8 8
      src/main/frontend/handler/editor.cljs
  8. 0 9
      src/main/frontend/state.cljs
  9. 84 0
      src/resources/dicts/en.edn
  10. 1 1
      src/resources/dicts/nl.edn
  11. 1 1
      src/test/frontend/handler/common/config_edn_test.cljs
  12. 8 4
      tldraw/apps/tldraw-logseq/src/components/ActionBar/ActionBar.tsx
  13. 45 14
      tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx
  14. 52 46
      tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx
  15. 29 11
      tldraw/apps/tldraw-logseq/src/components/GeometryTools/GeometryTools.tsx
  16. 12 8
      tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx
  17. 2 1
      tldraw/apps/tldraw-logseq/src/components/QuickLinks/QuickLinks.tsx
  18. 8 10
      tldraw/apps/tldraw-logseq/src/components/QuickSearch/QuickSearch.tsx
  19. 9 4
      tldraw/apps/tldraw-logseq/src/components/inputs/ColorInput.tsx
  20. 12 7
      tldraw/apps/tldraw-logseq/src/components/inputs/ScaleInput.tsx
  21. 16 7
      tldraw/apps/tldraw-logseq/src/components/inputs/ShapeLinksInput.tsx
  22. 1 0
      tldraw/apps/tldraw-logseq/src/lib/logseq-context.ts
  23. 6 3
      tldraw/packages/core/src/lib/TLViewport.ts
  24. 1 1
      tldraw/packages/core/src/lib/tools/TLSelectTool/states/BrushingState.ts
  25. 1 1
      tldraw/packages/core/src/lib/tools/TLSelectTool/states/ResizingState.ts
  26. 1 1
      tldraw/packages/core/src/lib/tools/TLSelectTool/states/TranslatingState.ts
  27. 0 1
      tldraw/packages/react/src/components/ui/SelectionForeground/handles/RotateHandle.tsx

+ 29 - 17
e2e-tests/whiteboards.spec.ts

@@ -85,10 +85,10 @@ test('draw a rectangle', async ({ page }) => {
 
 
   await page.keyboard.type('wr')
   await page.keyboard.type('wr')
 
 
-  await page.mouse.move(bounds.x + 5, bounds.y + 5)
+  await page.mouse.move(bounds.x + 105, bounds.y + 105)
   await page.mouse.down()
   await page.mouse.down()
 
 
-  await page.mouse.move(bounds.x + 50, bounds.y + 50 )
+  await page.mouse.move(bounds.x + 150, bounds.y + 150 )
   await page.mouse.up()
   await page.mouse.up()
   await page.keyboard.press('Escape')
   await page.keyboard.press('Escape')
 
 
@@ -114,12 +114,14 @@ test('clone the rectangle', async ({ page }) => {
   const canvas = await page.waitForSelector('.logseq-tldraw')
   const canvas = await page.waitForSelector('.logseq-tldraw')
   const bounds = (await canvas.boundingBox())!
   const bounds = (await canvas.boundingBox())!
 
 
-  await page.mouse.move(bounds.x + 20, bounds.y + 20, {steps: 5})
+  await page.mouse.move(bounds.x + 400, bounds.y + 400)
+
+  await page.mouse.move(bounds.x + 120, bounds.y + 120, {steps: 5})
 
 
   await page.keyboard.down('Alt')
   await page.keyboard.down('Alt')
   await page.mouse.down()
   await page.mouse.down()
 
 
-  await page.mouse.move(bounds.x + 100, bounds.y + 100, {steps: 5})
+  await page.mouse.move(bounds.x + 200, bounds.y + 200, {steps: 5})
   await page.mouse.up()
   await page.mouse.up()
   await page.keyboard.up('Alt')
   await page.keyboard.up('Alt')
 
 
@@ -163,10 +165,10 @@ test('connect rectangles with an arrow', async ({ page }) => {
 
 
   await page.keyboard.type('wc')
   await page.keyboard.type('wc')
 
 
-  await page.mouse.move(bounds.x + 20, bounds.y + 20)
+  await page.mouse.move(bounds.x + 120, bounds.y + 120)
   await page.mouse.down()
   await page.mouse.down()
 
 
-  await page.mouse.move(bounds.x + 100, bounds.y + 100, {steps: 5}) // will fail without steps
+  await page.mouse.move(bounds.x + 200, bounds.y + 200, {steps: 5}) // will fail without steps
   await page.mouse.up()
   await page.mouse.up()
   await page.keyboard.press('Escape')
   await page.keyboard.press('Escape')
 
 
@@ -191,10 +193,15 @@ test('undo the delete action', async ({ page }) => {
 })
 })
 
 
 test('convert the first rectangle to ellipse', async ({ page }) => {
 test('convert the first rectangle to ellipse', async ({ page }) => {
+  const canvas = await page.waitForSelector('.logseq-tldraw')
+  const bounds = (await canvas.boundingBox())!
+
   await page.keyboard.press('Escape')
   await page.keyboard.press('Escape')
-  await page.waitForTimeout(1000)
-  await page.click('.logseq-tldraw .tl-box-container:first-of-type')
-  await page.mouse.move(0, 0)  // move mouse to trigger a rerender of the context bar
+  await page.mouse.move(bounds.x + 220, bounds.y + 220)
+  await page.mouse.down()
+  await page.mouse.up()
+  await page.mouse.move(bounds.x + 520, bounds.y + 520)
+
   await page.click('.tl-context-bar .tl-geometry-tools-pane-anchor')
   await page.click('.tl-context-bar .tl-geometry-tools-pane-anchor')
   await page.click('.tl-context-bar .tl-geometry-toolbar [data-tool=ellipse]')
   await page.click('.tl-context-bar .tl-geometry-toolbar [data-tool=ellipse]')
 
 
@@ -223,9 +230,14 @@ test('undo the shape conversion', async ({ page }) => {
 })
 })
 
 
 test('locked elements should not be removed', async ({ page }) => {
 test('locked elements should not be removed', async ({ page }) => {
+  const canvas = await page.waitForSelector('.logseq-tldraw')
+  const bounds = (await canvas.boundingBox())!
+
   await page.keyboard.press('Escape')
   await page.keyboard.press('Escape')
-  await page.waitForTimeout(1000)
-  await page.click('.logseq-tldraw .tl-box-container:first-of-type')
+  await page.mouse.move(bounds.x + 220, bounds.y + 220)
+  await page.mouse.down()
+  await page.mouse.up()
+  await page.mouse.move(bounds.x + 520, bounds.y + 520)
   await page.keyboard.press(`${modKey}+l`)
   await page.keyboard.press(`${modKey}+l`)
   await page.keyboard.press('Delete')
   await page.keyboard.press('Delete')
   await page.keyboard.press(`${modKey}+Shift+l`)
   await page.keyboard.press(`${modKey}+Shift+l`)
@@ -269,7 +281,7 @@ test('create a block', async ({ page }) => {
   const bounds = (await canvas.boundingBox())!
   const bounds = (await canvas.boundingBox())!
 
 
   await page.keyboard.type('ws')
   await page.keyboard.type('ws')
-  await page.mouse.dblclick(bounds.x + 5, bounds.y + 5)
+  await page.mouse.dblclick(bounds.x + 105, bounds.y + 105)
   await page.waitForTimeout(100)
   await page.waitForTimeout(100)
 
 
   await page.keyboard.type('a')
   await page.keyboard.type('a')
@@ -304,7 +316,7 @@ test('copy/paste url to create an iFrame shape', async ({ page }) => {
   const bounds = (await canvas.boundingBox())!
   const bounds = (await canvas.boundingBox())!
 
 
   await page.keyboard.type('wt')
   await page.keyboard.type('wt')
-  await page.mouse.move(bounds.x + 5, bounds.y + 5)
+  await page.mouse.move(bounds.x + 105, bounds.y + 105)
   await page.mouse.down()
   await page.mouse.down()
   await page.waitForTimeout(100)
   await page.waitForTimeout(100)
 
 
@@ -323,7 +335,7 @@ test('copy/paste twitter status url to create a Tweet shape', async ({ page }) =
   const bounds = (await canvas.boundingBox())!
   const bounds = (await canvas.boundingBox())!
 
 
   await page.keyboard.type('wt')
   await page.keyboard.type('wt')
-  await page.mouse.move(bounds.x + 5, bounds.y + 5)
+  await page.mouse.move(bounds.x + 105, bounds.y + 105)
   await page.mouse.down()
   await page.mouse.down()
   await page.waitForTimeout(100)
   await page.waitForTimeout(100)
 
 
@@ -342,7 +354,7 @@ test('copy/paste youtube video url to create a Youtube shape', async ({ page })
   const bounds = (await canvas.boundingBox())!
   const bounds = (await canvas.boundingBox())!
 
 
   await page.keyboard.type('wt')
   await page.keyboard.type('wt')
-  await page.mouse.move(bounds.x + 5, bounds.y + 5)
+  await page.mouse.move(bounds.x + 105, bounds.y + 105)
   await page.mouse.down()
   await page.mouse.down()
   await page.waitForTimeout(100)
   await page.waitForTimeout(100)
 
 
@@ -394,8 +406,8 @@ test('quick add another whiteboard', async ({ page }) => {
   const canvas = await page.waitForSelector('.logseq-tldraw')
   const canvas = await page.waitForSelector('.logseq-tldraw')
   await canvas.dblclick({
   await canvas.dblclick({
     position: {
     position: {
-      x: 100,
-      y: 100,
+      x: 200,
+      y: 200,
     },
     },
   })
   })
 
 

+ 9 - 0
scripts/src/logseq/tasks/lang.clj

@@ -115,6 +115,14 @@
    "(t title" []
    "(t title" []
    "(t subtitle" [:asset/physical-delete]})
    "(t subtitle" [:asset/physical-delete]})
 
 
+(defn- whiteboard-dicts
+  []
+  (->> (shell {:out :string}
+              "grep -E -oh" "\\bt\\('[^ ']+" "-r" "tldraw/apps/tldraw-logseq/src/components")
+       :out
+       string/split-lines
+       (map #(keyword (subs % 3)))))
+
 (defn- validate-ui-translations-are-used
 (defn- validate-ui-translations-are-used
   "This validation checks to see that translations done by (t ...) are equal to
   "This validation checks to see that translations done by (t ...) are equal to
   the ones defined for the default :en lang. This catches translations that have
   the ones defined for the default :en lang. This catches translations that have
@@ -129,6 +137,7 @@
                           string/split-lines
                           string/split-lines
                           (map #(keyword (subs % 4)))
                           (map #(keyword (subs % 4)))
                           (concat (mapcat val manual-ui-dicts))
                           (concat (mapcat val manual-ui-dicts))
+                          (concat (whiteboard-dicts))
                           set)
                           set)
         expected-dicts (set (remove #(re-find #"^(command|shortcut)\." (str (namespace %)))
         expected-dicts (set (remove #(re-find #"^(command|shortcut)\." (str (namespace %)))
                                     (keys (:en (get-dicts)))))
                                     (keys (:en (get-dicts)))))

+ 14 - 13
src/main/frontend/commands.cljs

@@ -30,6 +30,7 @@
 (defonce angle-bracket "<")
 (defonce angle-bracket "<")
 (defonce hashtag "#")
 (defonce hashtag "#")
 (defonce colon ":")
 (defonce colon ":")
+(defonce command-trigger "/")
 (defonce *current-command (atom nil))
 (defonce *current-command (atom nil))
 
 
 (def query-doc
 (def query-doc
@@ -52,7 +53,7 @@
     "."]])
     "."]])
 
 
 (defn link-steps []
 (defn link-steps []
-  [[:editor/input (str (state/get-editor-command-trigger) "link")]
+  [[:editor/input (str command-trigger "link")]
    [:editor/show-input [{:command :link
    [:editor/show-input [{:command :link
                          :id :link
                          :id :link
                          :placeholder "Link"
                          :placeholder "Link"
@@ -62,7 +63,7 @@
                          :placeholder "Label"}]]])
                          :placeholder "Label"}]]])
 
 
 (defn image-link-steps []
 (defn image-link-steps []
-  [[:editor/input (str (state/get-editor-command-trigger) "link")]
+  [[:editor/input (str command-trigger "link")]
    [:editor/show-input [{:command :image-link
    [:editor/show-input [{:command :image-link
                          :id :link
                          :id :link
                          :placeholder "Link"
                          :placeholder "Link"
@@ -72,7 +73,7 @@
                          :placeholder "Label"}]]])
                          :placeholder "Label"}]]])
 
 
 (defn zotero-steps []
 (defn zotero-steps []
-  [[:editor/input (str (state/get-editor-command-trigger) "zotero")]
+  [[:editor/input (str command-trigger "zotero")]
    [:editor/show-zotero]])
    [:editor/show-zotero]])
 
 
 (def *extend-slash-commands (atom []))
 (def *extend-slash-commands (atom []))
@@ -96,19 +97,19 @@
   [type]
   [type]
   (let [template (util/format "@@%s: @@"
   (let [template (util/format "@@%s: @@"
                               type)]
                               type)]
-    [[:editor/input template {:last-pattern (state/get-editor-command-trigger)
+    [[:editor/input template {:last-pattern command-trigger
                               :backward-pos 2}]]))
                               :backward-pos 2}]]))
 
 
 (defn embed-page
 (defn embed-page
   []
   []
   (conj
   (conj
-   [[:editor/input "{{embed [[]]}}" {:last-pattern (state/get-editor-command-trigger)
+   [[:editor/input "{{embed [[]]}}" {:last-pattern command-trigger
                                      :backward-pos 4}]]
                                      :backward-pos 4}]]
    [:editor/search-page :embed]))
    [:editor/search-page :embed]))
 
 
 (defn embed-block
 (defn embed-block
   []
   []
-  [[:editor/input "{{embed (())}}" {:last-pattern (state/get-editor-command-trigger)
+  [[:editor/input "{{embed (())}}" {:last-pattern command-trigger
                                     :backward-pos 4}]
                                     :backward-pos 4}]
    [:editor/search-block :embed]])
    [:editor/search-block :embed]])
 
 
@@ -229,9 +230,9 @@
      ["Image link" (image-link-steps) "Create a HTTP link to a image"]
      ["Image link" (image-link-steps) "Create a HTTP link to a image"]
      (when (state/markdown?)
      (when (state/markdown?)
        ["Underline" [[:editor/input "<ins></ins>"
        ["Underline" [[:editor/input "<ins></ins>"
-                      {:last-pattern (state/get-editor-command-trigger)
+                      {:last-pattern command-trigger
                        :backward-pos 6}]] "Create a underline text decoration"])
                        :backward-pos 6}]] "Create a underline text decoration"])
-     ["Template" [[:editor/input (state/get-editor-command-trigger) nil]
+     ["Template" [[:editor/input command-trigger nil]
                   [:editor/search-template]] "Insert a created template here"]
                   [:editor/search-template]] "Insert a created template here"]
      (cond
      (cond
        (and (util/electron?) (config/local-db? (state/get-current-repo)))
        (and (util/electron?) (config/local-db? (state/get-current-repo)))
@@ -239,7 +240,7 @@
        ["Upload an asset" [[:editor/click-hidden-file-input :id]] "Upload file types like image, pdf, docx, etc.)"])]
        ["Upload an asset" [[:editor/click-hidden-file-input :id]] "Upload file types like image, pdf, docx, etc.)"])]
 
 
        ;; ["Upload an image" [[:editor/click-hidden-file-input :id]]]
        ;; ["Upload an image" [[:editor/click-hidden-file-input :id]]]
-       
+
 
 
     (headings)
     (headings)
 
 
@@ -290,12 +291,12 @@
                  text)) "Draw a graph with Excalidraw"]
                  text)) "Draw a graph with Excalidraw"]
      ["Embed HTML " (->inline "html")]
      ["Embed HTML " (->inline "html")]
 
 
-     ["Embed Video URL" [[:editor/input "{{video }}" {:last-pattern (state/get-editor-command-trigger)
+     ["Embed Video URL" [[:editor/input "{{video }}" {:last-pattern command-trigger
                                                       :backward-pos 2}]]]
                                                       :backward-pos 2}]]]
 
 
      ["Embed Youtube timestamp" [[:youtube/insert-timestamp]]]
      ["Embed Youtube timestamp" [[:youtube/insert-timestamp]]]
 
 
-     ["Embed Twitter tweet" [[:editor/input "{{tweet }}" {:last-pattern (state/get-editor-command-trigger)
+     ["Embed Twitter tweet" [[:editor/input "{{tweet }}" {:last-pattern command-trigger
                                                           :backward-pos 2}]]]]
                                                           :backward-pos 2}]]]]
 
 
     @*extend-slash-commands
     @*extend-slash-commands
@@ -335,7 +336,7 @@
   (when-let [input (gdom/getElement id)]
   (when-let [input (gdom/getElement id)]
     (let [last-pattern (when-not (= last-pattern :skip-check)
     (let [last-pattern (when-not (= last-pattern :skip-check)
                          (when-not backward-truncate-number
                          (when-not backward-truncate-number
-                          (or last-pattern (state/get-editor-command-trigger))))
+                           (or last-pattern command-trigger)))
           edit-content (gobj/get input "value")
           edit-content (gobj/get input "value")
           current-pos (cursor/pos input)
           current-pos (cursor/pos input)
           current-pos (or
           current-pos (or
@@ -527,7 +528,7 @@
       (let [edit-content (gobj/get current-input "value")
       (let [edit-content (gobj/get current-input "value")
             current-pos (cursor/pos current-input)
             current-pos (cursor/pos current-input)
             prefix (subs edit-content 0 current-pos)
             prefix (subs edit-content 0 current-pos)
-            prefix (util/replace-last (state/get-editor-command-trigger) prefix "" (boolean space?))
+            prefix (util/replace-last command-trigger prefix "" (boolean space?))
             new-value (str prefix
             new-value (str prefix
                            (subs edit-content current-pos))]
                            (subs edit-content current-pos))]
         (state/set-block-content-and-last-pos! input-id
         (state/set-block-content-and-last-pos! input-id

+ 7 - 2
src/main/frontend/components/settings.cljs

@@ -385,8 +385,13 @@
 
 
 (defn preferred-pasting-file [t preferred-pasting-file?]
 (defn preferred-pasting-file [t preferred-pasting-file?]
   (toggle "preferred_pasting_file"
   (toggle "preferred_pasting_file"
-          (t :settings-page/preferred-pasting-file)
-          preferred-pasting-file?
+          [(t :settings-page/preferred-pasting-file)
+           (ui/tippy {:html        (t :settings-page/preferred-pasting-file-hint)
+                      :class       "tippy-hover ml-2"
+                      :interactive true
+                      :disabled    false}
+                     (svg/info))]
+          preferred-pasting-file? 
           config-handler/toggle-preferred-pasting-file!))
           config-handler/toggle-preferred-pasting-file!))
 
 
 (defn auto-expand-row [t auto-expand-block-refs?]
 (defn auto-expand-row [t auto-expand-block-refs?]

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

@@ -5,6 +5,7 @@
             [frontend.components.export :as export]
             [frontend.components.export :as export]
             [frontend.components.page :as page]
             [frontend.components.page :as page]
             [frontend.config :as config]
             [frontend.config :as config]
+            [frontend.context.i18n :refer [t]]
             [frontend.db.model :as model]
             [frontend.db.model :as model]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
@@ -93,7 +94,8 @@
 (def undo (fn [] (history/undo! nil)))
 (def undo (fn [] (history/undo! nil)))
 (def redo (fn [] (history/redo! nil)))
 (def redo (fn [] (history/redo! nil)))
 (defn get-tldraw-handlers [current-whiteboard-name]
 (defn get-tldraw-handlers [current-whiteboard-name]
-  {:search search-handler
+  {:t (fn [key] (t (keyword key)))
+   :search search-handler
    :queryBlockByUUID (fn [block-uuid]
    :queryBlockByUUID (fn [block-uuid]
                        (clj->js
                        (clj->js
                         (model/query-block-by-uuid (parse-uuid block-uuid))))
                         (model/query-block-by-uuid (parse-uuid block-uuid))))

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

@@ -92,7 +92,7 @@ nested keys or positional errors e.g. tuples"
   (let [body (try (edn/read-string content)
   (let [body (try (edn/read-string content)
                (catch :default _ ::failed-to-detect))
                (catch :default _ ::failed-to-detect))
         warnings {:editor/command-trigger
         warnings {:editor/command-trigger
-                  "Will no longer be supported soon. Please use '/' and report bugs on it."}]
+                  "is no longer supported. Please use '/' and report bugs on it."}]
     (cond
     (cond
       (= body ::failed-to-detect)
       (= body ::failed-to-detect)
       (log/info :msg "Skip deprecation check since config is not valid edn")
       (log/info :msg "Skip deprecation check since config is not valid edn")

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

@@ -1536,7 +1536,7 @@
                                        (if file-obj (.-name file-obj) (if image? "image" "asset"))
                                        (if file-obj (.-name file-obj) (if image? "image" "asset"))
                                        image?)
                                        image?)
                   format
                   format
-                  {:last-pattern (if drop-or-paste? "" (state/get-editor-command-trigger))
+                  {:last-pattern (if drop-or-paste? "" commands/command-trigger)
                    :restore?     true
                    :restore?     true
                    :command      :insert-asset})))))
                    :command      :insert-asset})))))
           (p/finally
           (p/finally
@@ -1675,7 +1675,7 @@
           last-command (and last-slash-caret-pos (subs edit-content last-slash-caret-pos pos))]
           last-command (and last-slash-caret-pos (subs edit-content last-slash-caret-pos pos))]
       (when (> pos 0)
       (when (> pos 0)
         (or
         (or
-         (and (= (state/get-editor-command-trigger) (util/nth-safe edit-content (dec pos)))
+         (and (= commands/command-trigger (util/nth-safe edit-content (dec pos)))
               @commands/*initial-commands)
               @commands/*initial-commands)
          (and last-command
          (and last-command
               (commands/get-matched-commands last-command)))))
               (commands/get-matched-commands last-command)))))
@@ -1793,7 +1793,7 @@
                id
                id
                (get-link format link label)
                (get-link format link label)
                format
                format
-               {:last-pattern (str (state/get-editor-command-trigger) "link")
+               {:last-pattern (str commands/command-trigger "link")
                 :command :link})))
                 :command :link})))
 
 
     :image-link (let [{:keys [link label]} m]
     :image-link (let [{:keys [link label]} m]
@@ -1802,7 +1802,7 @@
                      id
                      id
                      (get-image-link format link label)
                      (get-image-link format link label)
                      format
                      format
-                     {:last-pattern (str (state/get-editor-command-trigger) "link")
+                     {:last-pattern (str commands/command-trigger "link")
                       :command :image-link})))
                       :command :image-link})))
 
 
     nil)
     nil)
@@ -1879,7 +1879,7 @@
         (-> (p/delay 10)
         (-> (p/delay 10)
             (p/then #(state/pub-event! [:editor/toggle-own-number-list edit-block]))))
             (p/then #(state/pub-event! [:editor/toggle-own-number-list edit-block]))))
 
 
-      (and (= last-input-char (state/get-editor-command-trigger))
+      (and (= last-input-char commands/command-trigger)
            (or (re-find #"(?m)^/" (str (.-value input))) (start-of-new-word? input pos)))
            (or (re-find #"(?m)^/" (str (.-value input))) (start-of-new-word? input pos)))
       (do
       (do
         (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
         (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
@@ -2730,7 +2730,7 @@
             (delete-block! repo false))))
             (delete-block! repo false))))
 
 
       (and (> current-pos 1)
       (and (> current-pos 1)
-           (= (util/nth-safe value (dec current-pos)) (state/get-editor-command-trigger)))
+           (= (util/nth-safe value (dec current-pos)) commands/command-trigger))
       (do
       (do
         (util/stop e)
         (util/stop e)
         (commands/restore-state)
         (commands/restore-state)
@@ -3014,8 +3014,8 @@
                (util/event-is-composing? e true)])]
                (util/event-is-composing? e true)])]
         (cond
         (cond
           ;; When you type something after /
           ;; When you type something after /
-          (and (= :commands (state/get-editor-action)) (not= k (state/get-editor-command-trigger)))
-          (if (= (state/get-editor-command-trigger) (second (re-find #"(\S+)\s+$" value)))
+          (and (= :commands (state/get-editor-action)) (not= k commands/command-trigger))
+          (if (= commands/command-trigger (second (re-find #"(\S+)\s+$" value)))
             (state/clear-editor-action!)
             (state/clear-editor-action!)
             (let [matched-commands (get-matched-commands input)]
             (let [matched-commands (get-matched-commands input)]
               (if (seq matched-commands)
               (if (seq matched-commands)

+ 0 - 9
src/main/frontend/state.cljs

@@ -426,15 +426,6 @@ should be done through this fn in order to get global config and config defaults
 
 
        (get-in @state [:me :preferred_format] "markdown")))))
        (get-in @state [:me :preferred_format] "markdown")))))
 
 
-;; TODO: consider adding a pane in Settings to set this through the GUI (rather
-;; than having to go through the config.edn file)
-(defn get-editor-command-trigger
-  ([] (get-editor-command-trigger (get-current-repo)))
-  ([repo-url]
-   (or
-     (:editor/command-trigger (get-config repo-url))        ;; Get from user config
-     "/")))                                                 ;; Set the default
-
 (defn markdown?
 (defn markdown?
   []
   []
   (= (keyword (get-preferred-format))
   (= (keyword (get-preferred-format))

+ 84 - 0
src/resources/dicts/en.edn

@@ -192,6 +192,7 @@
  :settings-page/auto-expand-block-refs "Expand block references automatically when zoom-in"
  :settings-page/auto-expand-block-refs "Expand block references automatically when zoom-in"
  :settings-page/custom-date-format "Preferred date format"
  :settings-page/custom-date-format "Preferred date format"
  :settings-page/custom-date-format-warning "Re-index required! Existing journal references would be broken!"
  :settings-page/custom-date-format-warning "Re-index required! Existing journal references would be broken!"
+ :settings-page/preferred-pasting-file-hint "When enabled, pasting an image from the internet will download and insert the image. When disabled, it will paste the link to the image."
  :settings-page/preferred-file-format "Preferred file format"
  :settings-page/preferred-file-format "Preferred file format"
  :settings-page/preferred-workflow "Preferred workflow"
  :settings-page/preferred-workflow "Preferred workflow"
  :settings-page/preferred-pasting-file "Prefer pasting file"
  :settings-page/preferred-pasting-file "Prefer pasting file"
@@ -249,6 +250,89 @@
  :search/publishing "Search"
  :search/publishing "Search"
  :search "Search or create page"
  :search "Search or create page"
  :whiteboard/link-whiteboard-or-block "Link whiteboard/page/block"
  :whiteboard/link-whiteboard-or-block "Link whiteboard/page/block"
+ :whiteboard/align-left "Align left"
+ :whiteboard/align-center-horizontally "Align center horizontally"
+ :whiteboard/align-right "Align right"
+ :whiteboard/distribute-horizontally "Distribute horizontally"
+ :whiteboard/align-top "Align top"
+ :whiteboard/align-center-vertically "Align center vertically"
+ :whiteboard/align-bottom "Align bottom"
+ :whiteboard/distribute-vertically "Distribute vertically"
+ :whiteboard/pack-into-rectangle "Pack into rectangle"
+ :whiteboard/zoom-to-fit "Zoom to fit"
+ :whiteboard/ungroup "Ungroup"
+ :whiteboard/group "Group"
+ :whiteboard/cut "Cut"
+ :whiteboard/copy "Copy"
+ :whiteboard/paste "Paste"
+ :whiteboard/paste-as-link "Paste as link"
+ :whiteboard/export "Export"
+ :whiteboard/select-all "Select all"
+ :whiteboard/deselect-all "Deselect all"
+ :whiteboard/lock "Lock"
+ :whiteboard/unlock "Unlock"
+ :whiteboard/delete "Delete"
+ :whiteboard/flip-horizontally "Flip horizontally"
+ :whiteboard/flip-vertically "Flip vertically"
+ :whiteboard/move-to-front "Move to front"
+ :whiteboard/move-to-back "Move to back"
+ :whiteboard/dev-print-shape-props "(Dev) Print shape props"
+ :whiteboard/auto-resize "Auto resize"
+ :whiteboard/expand "Expand"
+ :whiteboard/collapse "Collapse"
+ :whiteboard/website-url "Website url"
+ :whiteboard/reload "Reload"
+ :whiteboard/open-website-url "Open website url"
+ :whiteboard/youtube-url "YouTube url"
+ :whiteboard/open-youtube-url "Open YouTube url"
+ :whiteboard/twitter-url "Twitter url"
+ :whiteboard/open-twitter-url "Open Twitter url"
+ :whiteboard/fill "Fill"
+ :whiteboard/stroke-type "Stroke type"
+ :whiteboard/arrow-head "Arrow head"
+ :whiteboard/bold "Bold"
+ :whiteboard/italic "Italic"
+ :whiteboard/undo "Undo"
+ :whiteboard/redo "Redo"
+ :whiteboard/zoom-in "Zoom in"
+ :whiteboard/zoom-out "Zoom out"
+ :whiteboard/select "Select"
+ :whiteboard/pan "Pan"
+ :whiteboard/add-block-or-page "Add block or page"
+ :whiteboard/draw "Draw"
+ :whiteboard/highlight "Highlight"
+ :whiteboard/eraser "Eraser"
+ :whiteboard/connector "Connector"
+ :whiteboard/text "Text"
+ :whiteboard/color "Color"
+ :whiteboard/select-custom-color "Select custom color"
+ :whiteboard/opacity "Opacity"
+ :whiteboard/extra-small "Extra Small"
+ :whiteboard/small "Small"
+ :whiteboard/medium "Medium"
+ :whiteboard/large "Large"
+ :whiteboard/extra-large "Extra Large"
+ :whiteboard/huge "Huge"
+ :whiteboard/scale-level "Scale level"
+ :whiteboard/rectangle "Rectangle"
+ :whiteboard/circle "Circle"
+ :whiteboard/triangle "Triangle"
+ :whiteboard/shape "Shape"
+ :whiteboard/open-page "Open page" 
+ :whiteboard/open-page-in-sidebar "Open page in sidebar"
+ :whiteboard/remove-link "Remove link"
+ :whiteboard/link "Link"
+ :whiteboard/references "References"
+ :whiteboard/link-to-any-page-or-block "Link to any page or block"
+ :whiteboard/start-typing-to-search "Start typing to search..."
+ :whiteboard/new-block-no-colon "New block"
+ :whiteboard/new-block "New block:"
+ :whiteboard/new-page "New page:"
+ :whiteboard/new-whiteboard "New whiteboard"
+ :whiteboard/search-only-blocks "Search only blocks"
+ :whiteboard/search-only-pages "Search only pages"
+ :whiteboard/cache-outdated "Cache is outdated. Please click the 'Re-index' button in the graph's dropdown menu."
+ :whiteboard/shape-quick-links "Shape Quick Links"
  :page-search "Search in the current page"
  :page-search "Search in the current page"
  :graph-search "Search graph"
  :graph-search "Search graph"
  :home "Home"
  :home "Home"

+ 1 - 1
src/resources/dicts/nl.edn

@@ -153,7 +153,7 @@
  :plugin/custom-js-alert "custom.js bestand gevonden, mag deze uitgevoerd worden? (Als je niet begrijpt wat dit bestand inhoud is het aangeraden om het uivoeren hiervan niet toe te staan, dit is in verband met beveiligings risico's.)" ;; TODO
  :plugin/custom-js-alert "custom.js bestand gevonden, mag deze uitgevoerd worden? (Als je niet begrijpt wat dit bestand inhoud is het aangeraden om het uivoeren hiervan niet toe te staan, dit is in verband met beveiligings risico's.)" ;; TODO
  :plugin/delete-alert "Weet je zeker dat je plugin [{1}] wilt verwijderen?"
  :plugin/delete-alert "Weet je zeker dat je plugin [{1}] wilt verwijderen?"
  :plugin/disabled "Uitgeschakeld"
  :plugin/disabled "Uitgeschakeld"
-\ :plugin/enabled "Ingeschakeld"
+ :plugin/enabled "Ingeschakeld"
  :plugin/install "Installeren"
  :plugin/install "Installeren"
  :plugin/installed "Geïnstalleerd"
  :plugin/installed "Geïnstalleerd"
  :plugin/installing "Installeren"
  :plugin/installing "Installeren"

+ 1 - 1
src/test/frontend/handler/common/config_edn_test.cljs

@@ -60,7 +60,7 @@
 
 
 (deftest detect-deprecations
 (deftest detect-deprecations
   (is (re-find
   (is (re-find
-       #":editor/command-trigger.*Will"
+       #":editor/command-trigger.*is"
        (deprecation-warnings-for "{:preferred-workflow :todo :editor/command-trigger \",\"}"))
        (deprecation-warnings-for "{:preferred-workflow :todo :editor/command-trigger \",\"}"))
       "Warning when there is a deprecation")
       "Warning when there is a deprecation")
 
 

+ 8 - 4
tldraw/apps/tldraw-logseq/src/components/ActionBar/ActionBar.tsx

@@ -8,9 +8,13 @@ import { TablerIcon } from '../icons'
 import { Button } from '../Button'
 import { Button } from '../Button'
 import { ZoomMenu } from '../ZoomMenu'
 import { ZoomMenu } from '../ZoomMenu'
 import * as Separator from '@radix-ui/react-separator'
 import * as Separator from '@radix-ui/react-separator'
+import { LogseqContext } from '../../lib/logseq-context'
 
 
 export const ActionBar = observer(function ActionBar(): JSX.Element {
 export const ActionBar = observer(function ActionBar(): JSX.Element {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
 
 
   const undo = React.useCallback(() => {
   const undo = React.useCallback(() => {
     app.api.undo()
     app.api.undo()
@@ -32,20 +36,20 @@ export const ActionBar = observer(function ActionBar(): JSX.Element {
     <div className="tl-action-bar" data-html2canvas-ignore="true">
     <div className="tl-action-bar" data-html2canvas-ignore="true">
       {!app.readOnly && (
       {!app.readOnly && (
         <div className="tl-toolbar tl-history-bar">
         <div className="tl-toolbar tl-history-bar">
-          <Button tooltip="Undo" onClick={undo}>
+          <Button tooltip={t('whiteboard/undo')} onClick={undo}>
             <TablerIcon name="arrow-back-up" />
             <TablerIcon name="arrow-back-up" />
           </Button>
           </Button>
-          <Button tooltip="Redo" onClick={redo}>
+          <Button tooltip={t('whiteboard/redo')} onClick={redo}>
             <TablerIcon name="arrow-forward-up" />
             <TablerIcon name="arrow-forward-up" />
           </Button>
           </Button>
         </div>
         </div>
       )}
       )}
 
 
       <div className={`tl-toolbar tl-zoom-bar ${app.readOnly ? '' : 'ml-4'}`}>
       <div className={`tl-toolbar tl-zoom-bar ${app.readOnly ? '' : 'ml-4'}`}>
-        <Button tooltip="Zoom in" onClick={zoomIn} id="tl-zoom-in">
+        <Button tooltip={t('whiteboard/zoom-in')} onClick={zoomIn} id="tl-zoom-in">
           <TablerIcon name="plus" />
           <TablerIcon name="plus" />
         </Button>
         </Button>
-        <Button tooltip="Zoom out" onClick={zoomOut} id="tl-zoom-out">
+        <Button tooltip={t('whiteboard/zoom-out')} onClick={zoomOut} id="tl-zoom-out">
           <TablerIcon name="minus" />
           <TablerIcon name="minus" />
         </Button>
         </Button>
         <Separator.Root className="tl-toolbar-separator" orientation="vertical" />
         <Separator.Root className="tl-toolbar-separator" orientation="vertical" />

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

@@ -92,13 +92,16 @@ function filterShapeByAction<S extends Shape>(type: ContextBarActionType) {
 
 
 const AutoResizingAction = observer(() => {
 const AutoResizingAction = observer(() => {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<LogseqPortalShape | TextShape | HTMLShape>('AutoResizing')
   const shapes = filterShapeByAction<LogseqPortalShape | TextShape | HTMLShape>('AutoResizing')
 
 
   const pressed = shapes.every(s => s.props.isAutoResizing)
   const pressed = shapes.every(s => s.props.isAutoResizing)
 
 
   return (
   return (
     <ToggleInput
     <ToggleInput
-      tooltip="Auto Resize"
+      tooltip={t('whiteboard/auto-resize')}
       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={pressed}
       pressed={pressed}
@@ -122,6 +125,9 @@ const AutoResizingAction = observer(() => {
 
 
 const LogseqPortalViewModeAction = observer(() => {
 const LogseqPortalViewModeAction = observer(() => {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<LogseqPortalShape>('LogseqPortalViewMode')
   const shapes = filterShapeByAction<LogseqPortalShape>('LogseqPortalViewMode')
 
 
   const collapsed = shapes.every(s => s.collapsed)
   const collapsed = shapes.every(s => s.collapsed)
@@ -131,7 +137,7 @@ const LogseqPortalViewModeAction = observer(() => {
 
 
   const tooltip = (
   const tooltip = (
     <div className="flex">
     <div className="flex">
-      {collapsed ? 'Expand' : 'Collapse'}
+      {collapsed ? t('whiteboard/expand') : t('whiteboard/collapse')}
       <KeyboardShortcut
       <KeyboardShortcut
         action={collapsed ? 'editor/expand-block-children' : 'editor/collapse-block-children'}
         action={collapsed ? 'editor/expand-block-children' : 'editor/collapse-block-children'}
       />
       />
@@ -164,6 +170,9 @@ const ScaleLevelAction = observer(() => {
 
 
 const IFrameSourceAction = observer(() => {
 const IFrameSourceAction = observer(() => {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shape = filterShapeByAction<IFrameShape>('IFrameSource')[0]
   const shape = filterShapeByAction<IFrameShape>('IFrameSource')[0]
 
 
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
@@ -177,16 +186,20 @@ const IFrameSourceAction = observer(() => {
 
 
   return (
   return (
     <span className="flex gap-3">
     <span className="flex gap-3">
-      <Button tooltip="Reload" type="button" onClick={handleReload}>
+      <Button tooltip={t('whiteboard/reload')} type="button" onClick={handleReload}>
         <TablerIcon name="refresh" />
         <TablerIcon name="refresh" />
       </Button>
       </Button>
       <TextInput
       <TextInput
-        title="Website Url"
+        title={t('whiteboard/website-url')}
         className="tl-iframe-src"
         className="tl-iframe-src"
         value={`${shape.props.url}`}
         value={`${shape.props.url}`}
         onChange={handleChange}
         onChange={handleChange}
       />
       />
-      <Button tooltip="Open website url" type="button" onClick={() => window.open(shape.props.url)}>
+      <Button
+        tooltip={t('whiteboard/open-website-url')}
+        type="button"
+        onClick={() => window.open(shape.props.url)}
+      >
         <TablerIcon name="external-link" />
         <TablerIcon name="external-link" />
       </Button>
       </Button>
     </span>
     </span>
@@ -195,6 +208,9 @@ const IFrameSourceAction = observer(() => {
 
 
 const YoutubeLinkAction = observer(() => {
 const YoutubeLinkAction = observer(() => {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shape = filterShapeByAction<YouTubeShape>('YoutubeLink')[0]
   const shape = filterShapeByAction<YouTubeShape>('YoutubeLink')[0]
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
     shape.onYoutubeLinkChange(e.target.value)
     shape.onYoutubeLinkChange(e.target.value)
@@ -204,13 +220,13 @@ const YoutubeLinkAction = observer(() => {
   return (
   return (
     <span className="flex gap-3">
     <span className="flex gap-3">
       <TextInput
       <TextInput
-        title="YouTube Link"
+        title={t('whiteboard/youtube-url')}
         className="tl-youtube-link"
         className="tl-youtube-link"
         value={`${shape.props.url}`}
         value={`${shape.props.url}`}
         onChange={handleChange}
         onChange={handleChange}
       />
       />
       <Button
       <Button
-        tooltip="Open YouTube Link"
+        tooltip={t('whiteboard/open-youtube-url')}
         type="button"
         type="button"
         onClick={() => window.logseq?.api?.open_external_link?.(shape.props.url)}
         onClick={() => window.logseq?.api?.open_external_link?.(shape.props.url)}
       >
       >
@@ -222,6 +238,9 @@ const YoutubeLinkAction = observer(() => {
 
 
 const TwitterLinkAction = observer(() => {
 const TwitterLinkAction = observer(() => {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shape = filterShapeByAction<TweetShape>('TwitterLink')[0]
   const shape = filterShapeByAction<TweetShape>('TwitterLink')[0]
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
     shape.onTwitterLinkChange(e.target.value)
     shape.onTwitterLinkChange(e.target.value)
@@ -231,13 +250,13 @@ const TwitterLinkAction = observer(() => {
   return (
   return (
     <span className="flex gap-3">
     <span className="flex gap-3">
       <TextInput
       <TextInput
-        title="Twitter Link"
+        title={t('whiteboard/twitter-url')}
         className="tl-twitter-link"
         className="tl-twitter-link"
         value={`${shape.props.url}`}
         value={`${shape.props.url}`}
         onChange={handleChange}
         onChange={handleChange}
       />
       />
       <Button
       <Button
-        tooltip="Open Twitter Link"
+        tooltip={t('whiteboard/open-twitter-url')}
         type="button"
         type="button"
         onClick={() => window.logseq?.api?.open_external_link?.(shape.props.url)}
         onClick={() => window.logseq?.api?.open_external_link?.(shape.props.url)}
       >
       >
@@ -249,6 +268,9 @@ const TwitterLinkAction = observer(() => {
 
 
 const NoFillAction = observer(() => {
 const NoFillAction = observer(() => {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<BoxShape | PolygonShape | EllipseShape>('NoFill')
   const shapes = filterShapeByAction<BoxShape | PolygonShape | EllipseShape>('NoFill')
   const handleChange = React.useCallback((v: boolean) => {
   const handleChange = React.useCallback((v: boolean) => {
     app.selectedShapesArray.forEach(s => s.update({ noFill: v }))
     app.selectedShapesArray.forEach(s => s.update({ noFill: v }))
@@ -259,7 +281,7 @@ const NoFillAction = observer(() => {
 
 
   return (
   return (
     <ToggleInput
     <ToggleInput
-      tooltip="Fill"
+      tooltip={t('whiteboard/fill')}
       className="tl-button"
       className="tl-button"
       pressed={noFill}
       pressed={noFill}
       onPressedChange={handleChange}
       onPressedChange={handleChange}
@@ -315,6 +337,9 @@ const GeometryAction = observer(() => {
 
 
 const StrokeTypeAction = observer(() => {
 const StrokeTypeAction = observer(() => {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<
   const shapes = filterShapeByAction<
     BoxShape | PolygonShape | EllipseShape | LineShape | PencilShape
     BoxShape | PolygonShape | EllipseShape | LineShape | PencilShape
   >('StrokeType')
   >('StrokeType')
@@ -340,7 +365,7 @@ const StrokeTypeAction = observer(() => {
 
 
   return (
   return (
     <ToggleGroupInput
     <ToggleGroupInput
-      title="Stroke Type"
+      title={t('whiteboard/stroke-type')}
       options={StrokeTypeOptions}
       options={StrokeTypeOptions}
       value={value}
       value={value}
       onValueChange={v => {
       onValueChange={v => {
@@ -357,6 +382,9 @@ const StrokeTypeAction = observer(() => {
 
 
 const ArrowModeAction = observer(() => {
 const ArrowModeAction = observer(() => {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<LineShape>('ArrowMode')
   const shapes = filterShapeByAction<LineShape>('ArrowMode')
 
 
   const StrokeTypeOptions: ToggleGroupInputOption[] = [
   const StrokeTypeOptions: ToggleGroupInputOption[] = [
@@ -384,7 +412,7 @@ const ArrowModeAction = observer(() => {
 
 
   return (
   return (
     <ToggleGroupMultipleInput
     <ToggleGroupMultipleInput
-      title="Arrow Head"
+      title={t('whiteboard/arrow-head')}
       options={StrokeTypeOptions}
       options={StrokeTypeOptions}
       value={value}
       value={value}
       onValueChange={v => {
       onValueChange={v => {
@@ -401,6 +429,9 @@ const ArrowModeAction = observer(() => {
 
 
 const TextStyleAction = observer(() => {
 const TextStyleAction = observer(() => {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<TextShape>('TextStyle')
   const shapes = filterShapeByAction<TextShape>('TextStyle')
 
 
   const bold = shapes.every(s => s.props.fontWeight > 500)
   const bold = shapes.every(s => s.props.fontWeight > 500)
@@ -409,7 +440,7 @@ const TextStyleAction = observer(() => {
   return (
   return (
     <span className="flex gap-1">
     <span className="flex gap-1">
       <ToggleInput
       <ToggleInput
-        tooltip="Bold"
+        tooltip={t('whiteboard/bold')}
         className="tl-button"
         className="tl-button"
         pressed={bold}
         pressed={bold}
         onPressedChange={v => {
         onPressedChange={v => {
@@ -425,7 +456,7 @@ const TextStyleAction = observer(() => {
         <TablerIcon name="bold" />
         <TablerIcon name="bold" />
       </ToggleInput>
       </ToggleInput>
       <ToggleInput
       <ToggleInput
-        tooltip="Italic"
+        tooltip={t('whiteboard/italic')}
         className="tl-button"
         className="tl-button"
         pressed={italic}
         pressed={italic}
         onPressedChange={v => {
         onPressedChange={v => {

+ 52 - 46
tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx

@@ -21,7 +21,9 @@ export const ContextMenu = observer(function ContextMenu({
   collisionRef,
   collisionRef,
 }: ContextMenuProps) {
 }: ContextMenuProps) {
   const app = useApp()
   const app = useApp()
-  const { handlers } = React.useContext(LogseqContext)
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const rContent = React.useRef<HTMLDivElement>(null)
   const rContent = React.useRef<HTMLDivElement>(null)
 
 
   const runAndTransition = (f: Function) => {
   const runAndTransition = (f: Function) => {
@@ -64,26 +66,26 @@ export const ContextMenu = observer(function ContextMenu({
                 <ReactContextMenu.Item>
                 <ReactContextMenu.Item>
                   <div className="tl-menu-button-row pb-0">
                   <div className="tl-menu-button-row pb-0">
                     <Button
                     <Button
-                      tooltip="Align left"
+                      tooltip={t('whiteboard/align-left')}
                       onClick={() => runAndTransition(() => app.align(AlignType.Left))}
                       onClick={() => runAndTransition(() => app.align(AlignType.Left))}
                     >
                     >
                       <TablerIcon name="layout-align-left" />
                       <TablerIcon name="layout-align-left" />
                     </Button>
                     </Button>
                     <Button
                     <Button
-                      tooltip="Align center horizontally"
+                      tooltip={t('whiteboard/align-center-horizontally')}
                       onClick={() => runAndTransition(() => app.align(AlignType.CenterHorizontal))}
                       onClick={() => runAndTransition(() => app.align(AlignType.CenterHorizontal))}
                     >
                     >
                       <TablerIcon name="layout-align-center" />
                       <TablerIcon name="layout-align-center" />
                     </Button>
                     </Button>
                     <Button
                     <Button
-                      tooltip="Align right"
+                      tooltip={t('whiteboard/align-right')}
                       onClick={() => runAndTransition(() => app.align(AlignType.Right))}
                       onClick={() => runAndTransition(() => app.align(AlignType.Right))}
                     >
                     >
                       <TablerIcon name="layout-align-right" />
                       <TablerIcon name="layout-align-right" />
                     </Button>
                     </Button>
                     <Separator.Root className="tl-toolbar-separator" orientation="vertical" />
                     <Separator.Root className="tl-toolbar-separator" orientation="vertical" />
                     <Button
                     <Button
-                      tooltip="Distribute horizontally"
+                      tooltip={t('whiteboard/distribute-horizontally')}
                       onClick={() =>
                       onClick={() =>
                         runAndTransition(() => app.distribute(DistributeType.Horizontal))
                         runAndTransition(() => app.distribute(DistributeType.Horizontal))
                       }
                       }
@@ -93,26 +95,26 @@ export const ContextMenu = observer(function ContextMenu({
                   </div>
                   </div>
                   <div className="tl-menu-button-row pt-0">
                   <div className="tl-menu-button-row pt-0">
                     <Button
                     <Button
-                      tooltip="Align top"
+                      tooltip={t('whiteboard/align-top')}
                       onClick={() => runAndTransition(() => app.align(AlignType.Top))}
                       onClick={() => runAndTransition(() => app.align(AlignType.Top))}
                     >
                     >
                       <TablerIcon name="layout-align-top" />
                       <TablerIcon name="layout-align-top" />
                     </Button>
                     </Button>
                     <Button
                     <Button
-                      tooltip="Align center vertically"
+                      tooltip={t('whiteboard/align-center-vertically')}
                       onClick={() => runAndTransition(() => app.align(AlignType.CenterVertical))}
                       onClick={() => runAndTransition(() => app.align(AlignType.CenterVertical))}
                     >
                     >
                       <TablerIcon name="layout-align-middle" />
                       <TablerIcon name="layout-align-middle" />
                     </Button>
                     </Button>
                     <Button
                     <Button
-                      tooltip="Align bottom"
+                      tooltip={t('whiteboard/align-bottom')}
                       onClick={() => runAndTransition(() => app.align(AlignType.Bottom))}
                       onClick={() => runAndTransition(() => app.align(AlignType.Bottom))}
                     >
                     >
                       <TablerIcon name="layout-align-bottom" />
                       <TablerIcon name="layout-align-bottom" />
                     </Button>
                     </Button>
                     <Separator.Root className="tl-toolbar-separator" orientation="vertical" />
                     <Separator.Root className="tl-toolbar-separator" orientation="vertical" />
                     <Button
                     <Button
-                      tooltip="Distribute vertically"
+                      tooltip={t('whiteboard/distribute-vertically')}
                       onClick={() =>
                       onClick={() =>
                         runAndTransition(() => app.distribute(DistributeType.Vertical))
                         runAndTransition(() => app.distribute(DistributeType.Vertical))
                       }
                       }
@@ -127,7 +129,7 @@ export const ContextMenu = observer(function ContextMenu({
                   onClick={() => runAndTransition(app.packIntoRectangle)}
                   onClick={() => runAndTransition(app.packIntoRectangle)}
                 >
                 >
                   <TablerIcon className="tl-menu-icon" name="layout-grid" />
                   <TablerIcon className="tl-menu-icon" name="layout-grid" />
-                  Pack into rectangle
+                  {t('whiteboard/pack-into-rectangle')}
                 </ReactContextMenu.Item>
                 </ReactContextMenu.Item>
                 <ReactContextMenu.Separator className="menu-separator" />
                 <ReactContextMenu.Separator className="menu-separator" />
               </>
               </>
@@ -138,7 +140,7 @@ export const ContextMenu = observer(function ContextMenu({
                 className="tl-menu-item"
                 className="tl-menu-item"
                 onClick={() => runAndTransition(app.api.zoomToSelection)}
                 onClick={() => runAndTransition(app.api.zoomToSelection)}
               >
               >
-                Zoom to fit
+                {t('whiteboard/zoom-to-fit')}
                 <KeyboardShortcut action="whiteboard/zoom-to-fit" />
                 <KeyboardShortcut action="whiteboard/zoom-to-fit" />
               </ReactContextMenu.Item>
               </ReactContextMenu.Item>
               <ReactContextMenu.Separator className="menu-separator" />
               <ReactContextMenu.Separator className="menu-separator" />
@@ -155,7 +157,7 @@ export const ContextMenu = observer(function ContextMenu({
                     onClick={() => runAndTransition(app.api.unGroup)}
                     onClick={() => runAndTransition(app.api.unGroup)}
                   >
                   >
                     <TablerIcon className="tl-menu-icon" name="ungroup" />
                     <TablerIcon className="tl-menu-icon" name="ungroup" />
-                    Ungroup
+                    {t('whiteboard/ungroup')}
                     <KeyboardShortcut action="whiteboard/ungroup" />
                     <KeyboardShortcut action="whiteboard/ungroup" />
                   </ReactContextMenu.Item>
                   </ReactContextMenu.Item>
                 )}
                 )}
@@ -166,7 +168,7 @@ export const ContextMenu = observer(function ContextMenu({
                       onClick={() => runAndTransition(app.api.doGroup)}
                       onClick={() => runAndTransition(app.api.doGroup)}
                     >
                     >
                       <TablerIcon className="tl-menu-icon" name="group" />
                       <TablerIcon className="tl-menu-icon" name="group" />
-                      Group
+                      {t('whiteboard/group')}
                       <KeyboardShortcut action="whiteboard/group" />
                       <KeyboardShortcut action="whiteboard/group" />
                     </ReactContextMenu.Item>
                     </ReactContextMenu.Item>
                   )}
                   )}
@@ -181,7 +183,7 @@ export const ContextMenu = observer(function ContextMenu({
                   onClick={() => runAndTransition(app.cut)}
                   onClick={() => runAndTransition(app.cut)}
                 >
                 >
                   <TablerIcon className="tl-menu-icon" name="cut" />
                   <TablerIcon className="tl-menu-icon" name="cut" />
-                  Cut
+                  {t('whiteboard/cut')}
                 </ReactContextMenu.Item>
                 </ReactContextMenu.Item>
               )}
               )}
               <ReactContextMenu.Item
               <ReactContextMenu.Item
@@ -189,7 +191,7 @@ export const ContextMenu = observer(function ContextMenu({
                 onClick={() => runAndTransition(app.copy)}
                 onClick={() => runAndTransition(app.copy)}
               >
               >
                 <TablerIcon className="tl-menu-icon" name="copy" />
                 <TablerIcon className="tl-menu-icon" name="copy" />
-                Copy
+                {t('whiteboard/copy')}
                 <KeyboardShortcut action="editor/copy" />
                 <KeyboardShortcut action="editor/copy" />
               </ReactContextMenu.Item>
               </ReactContextMenu.Item>
             </>
             </>
@@ -200,7 +202,7 @@ export const ContextMenu = observer(function ContextMenu({
               onClick={() => runAndTransition(app.paste)}
               onClick={() => runAndTransition(app.paste)}
             >
             >
               <TablerIcon className="tl-menu-icon" name="clipboard" />
               <TablerIcon className="tl-menu-icon" name="clipboard" />
-              Paste
+              {t('whiteboard/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>
@@ -213,7 +215,7 @@ export const ContextMenu = observer(function ContextMenu({
               className="tl-menu-item"
               className="tl-menu-item"
               onClick={() => runAndTransition(() => app.paste(undefined, true))}
               onClick={() => runAndTransition(() => app.paste(undefined, true))}
             >
             >
-              Paste as link
+              {t('whiteboard/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>
@@ -239,7 +241,7 @@ export const ContextMenu = observer(function ContextMenu({
                 }
                 }
               >
               >
                 <TablerIcon className="tl-menu-icon" name="file-export" />
                 <TablerIcon className="tl-menu-icon" name="file-export" />
-                Export
+                {t('whiteboard/export')}
                 <div className="tl-menu-right-slot">
                 <div className="tl-menu-right-slot">
                   <span className="keyboard-shortcut"></span>
                   <span className="keyboard-shortcut"></span>
                 </div>
                 </div>
@@ -251,7 +253,7 @@ export const ContextMenu = observer(function ContextMenu({
             className="tl-menu-item"
             className="tl-menu-item"
             onClick={() => runAndTransition(app.api.selectAll)}
             onClick={() => runAndTransition(app.api.selectAll)}
           >
           >
-            Select all
+            {t('whiteboard/select-all')}
             <KeyboardShortcut action="editor/select-parent" />
             <KeyboardShortcut action="editor/select-parent" />
           </ReactContextMenu.Item>
           </ReactContextMenu.Item>
           {app.selectedShapes?.size > 1 && (
           {app.selectedShapes?.size > 1 && (
@@ -259,29 +261,33 @@ export const ContextMenu = observer(function ContextMenu({
               className="tl-menu-item"
               className="tl-menu-item"
               onClick={() => runAndTransition(app.api.deselectAll)}
               onClick={() => runAndTransition(app.api.deselectAll)}
             >
             >
-              Deselect all
-            </ReactContextMenu.Item>
-          )}
-          {!app.readOnly && app.selectedShapes?.size > 0 && app.selectedShapesArray?.some(s => !s.props.isLocked) && (
-            <ReactContextMenu.Item
-              className="tl-menu-item"
-              onClick={() => runAndTransition(() => app.setLocked(true))}
-            >
-              <TablerIcon className="tl-menu-icon" name="lock" />
-              Lock
-              <KeyboardShortcut action="whiteboard/lock" />
-            </ReactContextMenu.Item>
-          )}
-          {!app.readOnly && app.selectedShapes?.size > 0 && app.selectedShapesArray?.some(s => s.props.isLocked) && (
-            <ReactContextMenu.Item
-              className="tl-menu-item"
-              onClick={() => runAndTransition(() => app.setLocked(false))}
-            >
-              <TablerIcon className="tl-menu-icon" name="lock-open" />
-              Unlock
-              <KeyboardShortcut action="whiteboard/unlock" />
+              {t('whiteboard/deselect-all')}
             </ReactContextMenu.Item>
             </ReactContextMenu.Item>
           )}
           )}
+          {!app.readOnly &&
+            app.selectedShapes?.size > 0 &&
+            app.selectedShapesArray?.some(s => !s.props.isLocked) && (
+              <ReactContextMenu.Item
+                className="tl-menu-item"
+                onClick={() => runAndTransition(() => app.setLocked(true))}
+              >
+                <TablerIcon className="tl-menu-icon" name="lock" />
+                {t('whiteboard/lock')}
+                <KeyboardShortcut action="whiteboard/lock" />
+              </ReactContextMenu.Item>
+            )}
+          {!app.readOnly &&
+            app.selectedShapes?.size > 0 &&
+            app.selectedShapesArray?.some(s => s.props.isLocked) && (
+              <ReactContextMenu.Item
+                className="tl-menu-item"
+                onClick={() => runAndTransition(() => app.setLocked(false))}
+              >
+                <TablerIcon className="tl-menu-icon" name="lock-open" />
+                {t('whiteboard/unlock')}
+                <KeyboardShortcut action="whiteboard/unlock" />
+              </ReactContextMenu.Item>
+            )}
           {app.selectedShapes?.size > 0 &&
           {app.selectedShapes?.size > 0 &&
             !app.readOnly &&
             !app.readOnly &&
             app.selectedShapesArray?.some(s => !s.props.isLocked) && (
             app.selectedShapesArray?.some(s => !s.props.isLocked) && (
@@ -291,7 +297,7 @@ export const ContextMenu = observer(function ContextMenu({
                   onClick={() => runAndTransition(app.api.deleteShapes)}
                   onClick={() => runAndTransition(app.api.deleteShapes)}
                 >
                 >
                   <TablerIcon className="tl-menu-icon" name="backspace" />
                   <TablerIcon className="tl-menu-icon" name="backspace" />
-                  Delete
+                  {t('whiteboard/delete')}
                   <KeyboardShortcut action="editor/delete" />
                   <KeyboardShortcut action="editor/delete" />
                 </ReactContextMenu.Item>
                 </ReactContextMenu.Item>
                 {app.selectedShapes?.size > 1 && !app.readOnly && (
                 {app.selectedShapes?.size > 1 && !app.readOnly && (
@@ -302,14 +308,14 @@ export const ContextMenu = observer(function ContextMenu({
                       onClick={() => runAndTransition(app.flipHorizontal)}
                       onClick={() => runAndTransition(app.flipHorizontal)}
                     >
                     >
                       <TablerIcon className="tl-menu-icon" name="flip-horizontal" />
                       <TablerIcon className="tl-menu-icon" name="flip-horizontal" />
-                      Flip horizontally
+                      {t('whiteboard/flip-horizontally')}
                     </ReactContextMenu.Item>
                     </ReactContextMenu.Item>
                     <ReactContextMenu.Item
                     <ReactContextMenu.Item
                       className="tl-menu-item"
                       className="tl-menu-item"
                       onClick={() => runAndTransition(app.flipVertical)}
                       onClick={() => runAndTransition(app.flipVertical)}
                     >
                     >
                       <TablerIcon className="tl-menu-icon" name="flip-vertical" />
                       <TablerIcon className="tl-menu-icon" name="flip-vertical" />
-                      Flip vertically
+                      {t('whiteboard/flip-vertically')}
                     </ReactContextMenu.Item>
                     </ReactContextMenu.Item>
                   </>
                   </>
                 )}
                 )}
@@ -320,14 +326,14 @@ export const ContextMenu = observer(function ContextMenu({
                       className="tl-menu-item"
                       className="tl-menu-item"
                       onClick={() => runAndTransition(app.bringToFront)}
                       onClick={() => runAndTransition(app.bringToFront)}
                     >
                     >
-                      Move to front
+                      {t('whiteboard/move-to-front')}
                       <KeyboardShortcut action="whiteboard/bring-to-front" />
                       <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
+                      {t('whiteboard/move-to-back')}
                       <KeyboardShortcut action="whiteboard/send-to-back" />
                       <KeyboardShortcut action="whiteboard/send-to-back" />
                     </ReactContextMenu.Item>
                     </ReactContextMenu.Item>
                   </>
                   </>
@@ -344,7 +350,7 @@ export const ContextMenu = observer(function ContextMenu({
                       }
                       }
                     }}
                     }}
                   >
                   >
-                    (Dev) Print shape props
+                    {t('whiteboard/dev-print-shape-props')}
                   </ReactContextMenu.Item>
                   </ReactContextMenu.Item>
                 )}
                 )}
               </>
               </>

+ 29 - 11
tldraw/apps/tldraw-logseq/src/components/GeometryTools/GeometryTools.tsx

@@ -3,6 +3,8 @@ import type { Side } from '@radix-ui/react-popper'
 import { ToolButton } from '../ToolButton'
 import { ToolButton } from '../ToolButton'
 import * as Popover from '@radix-ui/react-popover'
 import * as Popover from '@radix-ui/react-popover'
 import { TablerIcon } from '../icons'
 import { TablerIcon } from '../icons'
+import React from 'react'
+import { LogseqContext } from '../../lib/logseq-context'
 
 
 interface GeometryToolsProps extends React.HTMLAttributes<HTMLElement> {
 interface GeometryToolsProps extends React.HTMLAttributes<HTMLElement> {
   popoverSide?: Side
   popoverSide?: Side
@@ -12,56 +14,72 @@ interface GeometryToolsProps extends React.HTMLAttributes<HTMLElement> {
 }
 }
 
 
 export const GeometryTools = observer(function GeometryTools({
 export const GeometryTools = observer(function GeometryTools({
-  popoverSide = "left",
+  popoverSide = 'left',
   setGeometry,
   setGeometry,
   activeGeometry,
   activeGeometry,
   chevron = true,
   chevron = true,
-  ...rest}: GeometryToolsProps) {
+  ...rest
+}: GeometryToolsProps) {
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
+
   const geometries = [
   const geometries = [
     {
     {
       id: 'box',
       id: 'box',
       icon: 'square',
       icon: 'square',
-      tooltip: 'Rectangle',
+      tooltip: t('whiteboard/rectangle'),
     },
     },
     {
     {
       id: 'ellipse',
       id: 'ellipse',
       icon: 'circle',
       icon: 'circle',
-      tooltip: 'Circle',
+      tooltip: t('whiteboard/circle'),
     },
     },
     {
     {
       id: 'polygon',
       id: 'polygon',
       icon: 'triangle',
       icon: 'triangle',
-      tooltip: 'Triangle',
+      tooltip: t('whiteboard/triangle'),
     },
     },
   ]
   ]
 
 
   const shapes = {
   const shapes = {
     id: 'shapes',
     id: 'shapes',
     icon: 'triangle-square-circle',
     icon: 'triangle-square-circle',
-    tooltip: 'Shape',
+    tooltip: t('whiteboard/shape'),
   }
   }
 
 
   const activeTool = activeGeometry ? geometries.find(geo => geo.id === activeGeometry) : shapes
   const activeTool = activeGeometry ? geometries.find(geo => geo.id === activeGeometry) : shapes
 
 
   return (
   return (
     <Popover.Root>
     <Popover.Root>
-      <Popover.Trigger asChild >
+      <Popover.Trigger asChild>
         <div {...rest} className="tl-geometry-tools-pane-anchor">
         <div {...rest} className="tl-geometry-tools-pane-anchor">
           <ToolButton {...activeTool} tooltipSide={popoverSide} />
           <ToolButton {...activeTool} tooltipSide={popoverSide} />
-          {chevron &&
+          {chevron && (
             <TablerIcon
             <TablerIcon
               data-selected={activeGeometry}
               data-selected={activeGeometry}
               className="tl-popover-indicator"
               className="tl-popover-indicator"
               name="chevron-down-left"
               name="chevron-down-left"
             />
             />
-          }
+          )}
         </div>
         </div>
       </Popover.Trigger>
       </Popover.Trigger>
 
 
       <Popover.Content className="tl-popover-content" side={popoverSide} sideOffset={15}>
       <Popover.Content className="tl-popover-content" side={popoverSide} sideOffset={15}>
-        <div className={`tl-toolbar tl-geometry-toolbar ${["left", "right"].includes(popoverSide) ? "flex-col" : "flex-row" }`}>
+        <div
+          className={`tl-toolbar tl-geometry-toolbar ${
+            ['left', 'right'].includes(popoverSide) ? 'flex-col' : 'flex-row'
+          }`}
+        >
           {geometries.map(props => (
           {geometries.map(props => (
-            <ToolButton key={props.id} id={props.id} icon={props.icon} tooltip={activeGeometry ? props.tooltip : ''} handleClick={setGeometry} tooltipSide={popoverSide} />
+            <ToolButton
+              key={props.id}
+              id={props.id}
+              icon={props.icon}
+              tooltip={activeGeometry ? props.tooltip : ''}
+              handleClick={setGeometry}
+              tooltipSide={popoverSide}
+            />
           ))}
           ))}
         </div>
         </div>
 
 

+ 12 - 8
tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx

@@ -7,9 +7,13 @@ import { GeometryTools } from '../GeometryTools'
 import { ColorInput } from '../inputs/ColorInput'
 import { ColorInput } from '../inputs/ColorInput'
 import { ScaleInput } from '../inputs/ScaleInput'
 import { ScaleInput } from '../inputs/ScaleInput'
 import * as Separator from '@radix-ui/react-separator'
 import * as Separator from '@radix-ui/react-separator'
+import { LogseqContext } from '../../lib/logseq-context'
 
 
 export const PrimaryTools = observer(function PrimaryTools() {
 export const PrimaryTools = observer(function PrimaryTools() {
   const app = useApp()
   const app = useApp()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
 
 
   const handleSetColor = React.useCallback((color: string) => {
   const handleSetColor = React.useCallback((color: string) => {
     app.api.setColor(color)
     app.api.setColor(color)
@@ -37,50 +41,50 @@ export const PrimaryTools = observer(function PrimaryTools() {
       <div className="tl-toolbar tl-tools-floating-panel">
       <div className="tl-toolbar tl-tools-floating-panel">
         <ToolButton
         <ToolButton
           handleClick={() => app.selectTool('select')}
           handleClick={() => app.selectTool('select')}
-          tooltip="Select"
+          tooltip={t('whiteboard/select')}
           id="select"
           id="select"
           icon="select-cursor"
           icon="select-cursor"
         />
         />
         <ToolButton
         <ToolButton
           handleClick={() => app.selectTool('move')}
           handleClick={() => app.selectTool('move')}
-          tooltip="Pan"
+          tooltip={t('whiteboard/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
         <ToolButton
           handleClick={() => app.selectTool('logseq-portal')}
           handleClick={() => app.selectTool('logseq-portal')}
-          tooltip="Add block or page"
+          tooltip={t('whiteboard/add-block-or-page')}
           id="logseq-portal"
           id="logseq-portal"
           icon="circle-plus"
           icon="circle-plus"
         />
         />
         <ToolButton
         <ToolButton
           handleClick={() => app.selectTool('pencil')}
           handleClick={() => app.selectTool('pencil')}
-          tooltip="Draw"
+          tooltip={t('whiteboard/draw')}
           id="pencil"
           id="pencil"
           icon="ballpen"
           icon="ballpen"
         />
         />
         <ToolButton
         <ToolButton
           handleClick={() => app.selectTool('highlighter')}
           handleClick={() => app.selectTool('highlighter')}
-          tooltip="Highlight"
+          tooltip={t('whiteboard/highlight')}
           id="highlighter"
           id="highlighter"
           icon="highlight"
           icon="highlight"
         />
         />
         <ToolButton
         <ToolButton
           handleClick={() => app.selectTool('erase')}
           handleClick={() => app.selectTool('erase')}
-          tooltip="Eraser"
+          tooltip={t('whiteboard/eraser')}
           id="erase"
           id="erase"
           icon="eraser"
           icon="eraser"
         />
         />
         <ToolButton
         <ToolButton
           handleClick={() => app.selectTool('line')}
           handleClick={() => app.selectTool('line')}
-          tooltip="Connector"
+          tooltip={t('whiteboard/connector')}
           id="line"
           id="line"
           icon="connector"
           icon="connector"
         />
         />
         <ToolButton
         <ToolButton
           handleClick={() => app.selectTool('text')}
           handleClick={() => app.selectTool('text')}
-          tooltip="Text"
+          tooltip={t('whiteboard/text')}
           id="text"
           id="text"
           icon="text"
           icon="text"
         />
         />

+ 2 - 1
tldraw/apps/tldraw-logseq/src/components/QuickLinks/QuickLinks.tsx

@@ -8,6 +8,7 @@ import { BlockLink } from '../BlockLink'
 export const QuickLinks: TLQuickLinksComponent<Shape> = observer(({ shape }) => {
 export const QuickLinks: TLQuickLinksComponent<Shape> = observer(({ shape }) => {
   const app = useApp()
   const app = useApp()
   const { handlers } = React.useContext(LogseqContext)
   const { handlers } = React.useContext(LogseqContext)
+  const t = handlers.t
   const links = React.useMemo(() => {
   const links = React.useMemo(() => {
     const links = [...(shape.props.refs ?? [])].map<[ref: string, showReferenceContent: boolean]>(
     const links = [...(shape.props.refs ?? [])].map<[ref: string, showReferenceContent: boolean]>(
       // user added links should show the referenced block content
       // user added links should show the referenced block content
@@ -30,7 +31,7 @@ export const QuickLinks: TLQuickLinksComponent<Shape> = observer(({ shape }) =>
   if (links.length === 0) return null
   if (links.length === 0) return null
 
 
   return (
   return (
-    <div className="tl-quick-links" title="Shape Quick Links">
+    <div className="tl-quick-links" title={t('whiteboard/shape-quick-links')}>
       {links.map(([ref, showReferenceContent]) => {
       {links.map(([ref, showReferenceContent]) => {
         return (
         return (
           <div key={ref} className="tl-quick-links-row">
           <div key={ref} className="tl-quick-links-row">

+ 8 - 10
tldraw/apps/tldraw-logseq/src/components/QuickSearch/QuickSearch.tsx

@@ -101,6 +101,7 @@ export const LogseqQuickSearch = observer(
     )
     )
     const rInput = React.useRef<HTMLInputElement>(null)
     const rInput = React.useRef<HTMLInputElement>(null)
     const { handlers, renderers } = React.useContext(LogseqContext)
     const { handlers, renderers } = React.useContext(LogseqContext)
+    const t = handlers.t
 
 
     const finishSearching = React.useCallback((id: string) => {
     const finishSearching = React.useCallback((id: string) => {
       onChange(id)
       onChange(id)
@@ -172,11 +173,11 @@ export const LogseqQuickSearch = observer(
               <LogseqTypeTag active type="BA" />
               <LogseqTypeTag active type="BA" />
               {q.length > 0 ? (
               {q.length > 0 ? (
                 <>
                 <>
-                  <strong>New block:</strong>
+                  <strong>{t('whiteboard/new-block')}</strong>
                   {q}
                   {q}
                 </>
                 </>
               ) : (
               ) : (
-                <strong>New block</strong>
+                <strong>{t('whiteboard/new-block-no-colon')}</strong>
               )}
               )}
             </div>
             </div>
           ),
           ),
@@ -195,7 +196,7 @@ export const LogseqQuickSearch = observer(
             element: (
             element: (
               <div className="tl-quick-search-option-row">
               <div className="tl-quick-search-option-row">
                 <LogseqTypeTag active type="PA" />
                 <LogseqTypeTag active type="PA" />
-                <strong>New page:</strong>
+                <strong>{t('whiteboard/new-page')}</strong>
                 {q}
                 {q}
               </div>
               </div>
             ),
             ),
@@ -210,7 +211,7 @@ export const LogseqQuickSearch = observer(
             element: (
             element: (
               <div className="tl-quick-search-option-row">
               <div className="tl-quick-search-option-row">
                 <LogseqTypeTag active type="WA" />
                 <LogseqTypeTag active type="WA" />
-                <strong>New whiteboard:</strong>
+                <strong>{t('whiteboard/new-whiteboard')}</strong>
                 {q}
                 {q}
               </div>
               </div>
             ),
             ),
@@ -230,7 +231,7 @@ export const LogseqQuickSearch = observer(
             element: (
             element: (
               <div className="tl-quick-search-option-row">
               <div className="tl-quick-search-option-row">
                 <LogseqTypeTag type="BS" />
                 <LogseqTypeTag type="BS" />
-                Search only blocks
+                {t('whiteboard/search-only-blocks')}
               </div>
               </div>
             ),
             ),
           },
           },
@@ -243,7 +244,7 @@ export const LogseqQuickSearch = observer(
             element: (
             element: (
               <div className="tl-quick-search-option-row">
               <div className="tl-quick-search-option-row">
                 <LogseqTypeTag type="PS" />
                 <LogseqTypeTag type="PS" />
-                Search only pages
+                {t('whiteboard/search-only-pages')}
               </div>
               </div>
             ),
             ),
           }
           }
@@ -302,10 +303,7 @@ export const LogseqQuickSearch = observer(
                     </div>
                     </div>
                   </>
                   </>
                 ) : (
                 ) : (
-                  <div className="tl-quick-search-option-row">
-                    Cache is outdated. Please click the 'Re-index' button in the graph's dropdown
-                    menu.
-                  </div>
+                  <div className="tl-quick-search-option-row">{t('whiteboard/cache-outdated')}</div>
                 ),
                 ),
               }
               }
             })
             })

+ 9 - 4
tldraw/apps/tldraw-logseq/src/components/inputs/ColorInput.tsx

@@ -5,6 +5,7 @@ import { TablerIcon } from '../icons'
 import { PopoverButton } from '../PopoverButton'
 import { PopoverButton } from '../PopoverButton'
 import { Tooltip } from '../Tooltip'
 import { Tooltip } from '../Tooltip'
 import React from 'react'
 import React from 'react'
+import { LogseqContext } from '../../lib/logseq-context'
 
 
 interface ColorInputProps extends React.HTMLAttributes<HTMLButtonElement> {
 interface ColorInputProps extends React.HTMLAttributes<HTMLButtonElement> {
   color?: string
   color?: string
@@ -22,6 +23,10 @@ export function ColorInput({
   setOpacity,
   setOpacity,
   ...rest
   ...rest
 }: ColorInputProps) {
 }: ColorInputProps) {
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
+
   function renderColor(color?: string) {
   function renderColor(color?: string) {
     return color ? (
     return color ? (
       <div className="tl-color-bg" style={{ backgroundColor: color }}>
       <div className="tl-color-bg" style={{ backgroundColor: color }}>
@@ -57,7 +62,7 @@ export function ColorInput({
       arrow
       arrow
       side={popoverSide}
       side={popoverSide}
       label={
       label={
-        <Tooltip content={'Color'} side={popoverSide} sideOffset={14}>
+        <Tooltip content={t('whiteboard/color')} side={popoverSide} sideOffset={14}>
           {renderColor(color)}
           {renderColor(color)}
         </Tooltip>
         </Tooltip>
       }
       }
@@ -82,7 +87,7 @@ export function ColorInput({
                 className="color-input cursor-pointer"
                 className="color-input cursor-pointer"
                 id="tl-custom-color-input"
                 id="tl-custom-color-input"
                 type="color"
                 type="color"
-                value={isHexColor(color) ? color : "#000000"}
+                value={isHexColor(color) ? color : '#000000'}
                 onChange={handleChangeDebounced}
                 onChange={handleChangeDebounced}
                 style={{ opacity: isBuiltInColor(color) ? 0 : 1 }}
                 style={{ opacity: isBuiltInColor(color) ? 0 : 1 }}
                 {...rest}
                 {...rest}
@@ -90,7 +95,7 @@ export function ColorInput({
             </div>
             </div>
           </div>
           </div>
           <label htmlFor="tl-custom-color-input" className="cursor-pointer">
           <label htmlFor="tl-custom-color-input" className="cursor-pointer">
-            Select custom color
+            {t('whiteboard/select-custom-color')}
           </label>
           </label>
         </div>
         </div>
 
 
@@ -101,7 +106,7 @@ export function ColorInput({
               onValueCommit={value => setOpacity(value[0])}
               onValueCommit={value => setOpacity(value[0])}
               max={1}
               max={1}
               step={0.1}
               step={0.1}
-              aria-label="Opacity"
+              aria-label={t('whiteboard/opacity')}
               className="tl-slider-root"
               className="tl-slider-root"
             >
             >
               <Slider.Track className="tl-slider-track">
               <Slider.Track className="tl-slider-track">

+ 12 - 7
tldraw/apps/tldraw-logseq/src/components/inputs/ScaleInput.tsx

@@ -2,6 +2,8 @@ import { SelectInput, type SelectOption } from '../inputs/SelectInput'
 import type { Side } from '@radix-ui/react-popper'
 import type { Side } from '@radix-ui/react-popper'
 import type { SizeLevel } from '../../lib'
 import type { SizeLevel } from '../../lib'
 import { useApp } from '@tldraw/react'
 import { useApp } from '@tldraw/react'
+import React from 'react'
+import { LogseqContext } from '../../lib/logseq-context'
 
 
 interface ScaleInputProps extends React.HTMLAttributes<HTMLButtonElement> {
 interface ScaleInputProps extends React.HTMLAttributes<HTMLButtonElement> {
   scaleLevel?: SizeLevel
   scaleLevel?: SizeLevel
@@ -11,37 +13,40 @@ interface ScaleInputProps extends React.HTMLAttributes<HTMLButtonElement> {
 
 
 export function ScaleInput({ scaleLevel, compact, popoverSide, ...rest }: ScaleInputProps) {
 export function ScaleInput({ scaleLevel, compact, popoverSide, ...rest }: ScaleInputProps) {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
 
 
   const sizeOptions: SelectOption[] = [
   const sizeOptions: SelectOption[] = [
     {
     {
-      label: compact ? 'XS' : 'Extra Small',
+      label: compact ? 'XS' : t('whiteboard/extra-small'),
       value: 'xs',
       value: 'xs',
     },
     },
     {
     {
-      label: compact ? 'SM' : 'Small',
+      label: compact ? 'SM' : t('whiteboard/small'),
       value: 'sm',
       value: 'sm',
     },
     },
     {
     {
-      label: compact ? 'MD' : 'Medium',
+      label: compact ? 'MD' : t('whiteboard/medium'),
       value: 'md',
       value: 'md',
     },
     },
     {
     {
-      label: compact ? 'LG' : 'Large',
+      label: compact ? 'LG' : t('whiteboard/large'),
       value: 'lg',
       value: 'lg',
     },
     },
     {
     {
-      label: compact ? 'XL' : 'Extra Large',
+      label: compact ? 'XL' : t('whiteboard/extra-large'),
       value: 'xl',
       value: 'xl',
     },
     },
     {
     {
-      label: compact ? 'XXL' : 'Huge',
+      label: compact ? 'XXL' : t('whiteboard/huge'),
       value: 'xxl',
       value: 'xxl',
     },
     },
   ]
   ]
 
 
   return (
   return (
     <SelectInput
     <SelectInput
-      tooltip="Scale level"
+      tooltip={t('whiteboard/scale-level')}
       options={sizeOptions}
       options={sizeOptions}
       value={scaleLevel}
       value={scaleLevel}
       popoverSide={popoverSide}
       popoverSide={popoverSide}

+ 16 - 7
tldraw/apps/tldraw-logseq/src/components/inputs/ShapeLinksInput.tsx

@@ -34,6 +34,7 @@ function ShapeLinkItem({
 }) {
 }) {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
   const { handlers } = React.useContext(LogseqContext)
   const { handlers } = React.useContext(LogseqContext)
+  const t = handlers.t
 
 
   return (
   return (
     <div className="tl-shape-links-panel-item color-level relative">
     <div className="tl-shape-links-panel-item color-level relative">
@@ -42,12 +43,16 @@ function ShapeLinkItem({
       </div>
       </div>
       <div className="flex-1" />
       <div className="flex-1" />
       {handlers.getBlockPageName(id) !== app.currentPage.name && (
       {handlers.getBlockPageName(id) !== app.currentPage.name && (
-        <Button tooltip="Open Page" type="button" onClick={() => handlers?.redirectToPage(id)}>
+        <Button
+          tooltip={t('whiteboard/open-page')}
+          type="button"
+          onClick={() => handlers?.redirectToPage(id)}
+        >
           <TablerIcon name="open-as-page" />
           <TablerIcon name="open-as-page" />
         </Button>
         </Button>
       )}
       )}
       <Button
       <Button
-        tooltip="Open Page in Right Sidebar"
+        tooltip={t('whiteboard/open-page-in-sidebar')}
         type="button"
         type="button"
         onClick={() => handlers?.sidebarAddBlock(id, type === 'B' ? 'block' : 'page')}
         onClick={() => handlers?.sidebarAddBlock(id, type === 'B' ? 'block' : 'page')}
       >
       >
@@ -56,7 +61,7 @@ function ShapeLinkItem({
       {onRemove && (
       {onRemove && (
         <Button
         <Button
           className="tl-shape-links-panel-item-remove-button"
           className="tl-shape-links-panel-item-remove-button"
-          tooltip="Remove link"
+          tooltip={t('whiteboard/remove-link')}
           type="button"
           type="button"
           onClick={onRemove}
           onClick={onRemove}
         >
         >
@@ -76,6 +81,10 @@ export const ShapeLinksInput = observer(function ShapeLinksInput({
   onRefsChange,
   onRefsChange,
   ...rest
   ...rest
 }: ShapeLinksInputProps) {
 }: ShapeLinksInputProps) {
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
+
   const noOfLinks = refs.length + (pageId ? 1 : 0)
   const noOfLinks = refs.length + (pageId ? 1 : 0)
   const canAddLink = refs.length === 0
   const canAddLink = refs.length === 0
 
 
@@ -94,7 +103,7 @@ export const ShapeLinksInput = observer(function ShapeLinksInput({
       align="start"
       align="start"
       alignOffset={-6}
       alignOffset={-6}
       label={
       label={
-        <Tooltip content={'Link'} sideOffset={14}>
+        <Tooltip content={t('whiteboard/link')} sideOffset={14}>
           <div className="flex gap-1 relative items-center justify-center px-1">
           <div className="flex gap-1 relative items-center justify-center px-1">
             <TablerIcon name={noOfLinks > 0 ? 'link' : 'add-link'} />
             <TablerIcon name={noOfLinks > 0 ? 'link' : 'add-link'} />
             {noOfLinks > 0 && <div className="tl-shape-links-count">{noOfLinks}</div>}
             {noOfLinks > 0 && <div className="tl-shape-links-count">{noOfLinks}</div>}
@@ -107,7 +116,7 @@ export const ShapeLinksInput = observer(function ShapeLinksInput({
           <div className="tl-shape-links-reference-panel">
           <div className="tl-shape-links-reference-panel">
             <div className="text-base inline-flex gap-1 items-center">
             <div className="text-base inline-flex gap-1 items-center">
               <TablerIcon className="opacity-50" name="internal-link" />
               <TablerIcon className="opacity-50" name="internal-link" />
-              References
+              {t('whiteboard/references')}
             </div>
             </div>
             <ShapeLinkItem type={portalType} id={pageId} />
             <ShapeLinkItem type={portalType} id={pageId} />
           </div>
           </div>
@@ -115,7 +124,7 @@ export const ShapeLinksInput = observer(function ShapeLinksInput({
         <div className="tl-shape-links-panel color-level">
         <div className="tl-shape-links-panel color-level">
           <div className="text-base inline-flex gap-1 items-center">
           <div className="text-base inline-flex gap-1 items-center">
             <TablerIcon className="opacity-50" name="add-link" />
             <TablerIcon className="opacity-50" name="add-link" />
-            Link to any page or block
+            {t('whiteboard/link-to-any-page-or-block')}
           </div>
           </div>
 
 
           {canAddLink && (
           {canAddLink && (
@@ -124,7 +133,7 @@ export const ShapeLinksInput = observer(function ShapeLinksInput({
                 width: 'calc(100% - 46px)',
                 width: 'calc(100% - 46px)',
                 marginLeft: '46px',
                 marginLeft: '46px',
               }}
               }}
-              placeholder="Start typing to search..."
+              placeholder={t('whiteboard/start-typing-to-search')}
               onChange={addNewRef}
               onChange={addNewRef}
             />
             />
           )}
           )}

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

@@ -42,6 +42,7 @@ export interface LogseqContextValue {
     }>
     }>
   }
   }
   handlers: {
   handlers: {
+    t: (key: string) => any
     search: (
     search: (
       query: string,
       query: string,
       filters: { 'pages?': boolean; 'blocks?': boolean; 'files?': boolean }
       filters: { 'pages?': boolean; 'blocks?': boolean; 'files?': boolean }

+ 6 - 3
tldraw/packages/core/src/lib/TLViewport.ts

@@ -19,6 +19,7 @@ export class TLViewport {
   static readonly minZoom = 0.1
   static readonly minZoom = 0.1
   static readonly maxZoom = 4
   static readonly maxZoom = 4
   static readonly panMultiplier = 0.05
   static readonly panMultiplier = 0.05
+  static readonly panThreshold = 100
 
 
   /* ------------------- Properties ------------------- */
   /* ------------------- Properties ------------------- */
 
 
@@ -49,9 +50,11 @@ export class TLViewport {
     })
     })
   }
   }
 
 
-  panToPointWhenOutOfBounds = (point: number[]) => {
-    const deltaMax = Vec.sub([this.currentView.maxX, this.currentView.maxY], point)
-    const deltaMin = Vec.sub([this.currentView.minX, this.currentView.minY], point)
+  panToPointWhenNearBounds = (point: number[]) => {
+    const threshold = [TLViewport.panThreshold, TLViewport.panThreshold]
+
+    const deltaMax = Vec.sub([this.currentView.maxX, this.currentView.maxY], Vec.add(point, threshold))
+    const deltaMin = Vec.sub([this.currentView.minX, this.currentView.minY], Vec.sub(point, threshold))
 
 
     const deltaX = deltaMax[0] < 0 ? deltaMax[0] : deltaMin[0] > 0 ? deltaMin[0] : 0
     const deltaX = deltaMax[0] < 0 ? deltaMax[0] : deltaMin[0] > 0 ? deltaMin[0] : 0
     const deltaY = deltaMax[1] < 0 ? deltaMax[1] : deltaMin[1] > 0 ? deltaMin[1] : 0
     const deltaY = deltaMax[1] < 0 ? deltaMax[1] : deltaMin[1] > 0 ? deltaMin[1] : 0

+ 1 - 1
tldraw/packages/core/src/lib/tools/TLSelectTool/states/BrushingState.ts

@@ -69,7 +69,7 @@ export class BrushingState<
       // Select hit shapes
       // Select hit shapes
       this.app.setSelectedShapes(hits)
       this.app.setSelectedShapes(hits)
     }
     }
-    this.app.viewport.panToPointWhenOutOfBounds(currentPoint)
+    this.app.viewport.panToPointWhenNearBounds(currentPoint)
   }
   }
 
 
   onPointerUp: TLEvents<S>['pointer'] = () => {
   onPointerUp: TLEvents<S>['pointer'] = () => {

+ 1 - 1
tldraw/packages/core/src/lib/tools/TLSelectTool/states/ResizingState.ts

@@ -227,7 +227,7 @@ export class ResizingState<
       })
       })
     })
     })
     this.updateCursor(scaleX, scaleY)
     this.updateCursor(scaleX, scaleY)
-    this.app.viewport.panToPointWhenOutOfBounds(currentPoint)
+    this.app.viewport.panToPointWhenNearBounds(currentPoint)
   }
   }
 
 
   onPointerUp: TLEvents<S>['pointer'] = () => {
   onPointerUp: TLEvents<S>['pointer'] = () => {

+ 1 - 1
tldraw/packages/core/src/lib/tools/TLSelectTool/states/TranslatingState.ts

@@ -141,7 +141,7 @@ export class TranslatingState<
     } = this.app
     } = this.app
 
 
     this.moveSelectedShapesToPointer()
     this.moveSelectedShapesToPointer()
-    this.app.viewport.panToPointWhenOutOfBounds(currentPoint)
+    this.app.viewport.panToPointWhenNearBounds(currentPoint)
   }
   }
 
 
   onPointerDown: TLEvents<S>['pointer'] = () => {
   onPointerDown: TLEvents<S>['pointer'] = () => {

+ 0 - 1
tldraw/packages/react/src/components/ui/SelectionForeground/handles/RotateHandle.tsx

@@ -1,5 +1,4 @@
 import { observer } from 'mobx-react-lite'
 import { observer } from 'mobx-react-lite'
-import * as React from 'react'
 import { useBoundsEvents } from '../../../../hooks'
 import { useBoundsEvents } from '../../../../hooks'
 
 
 interface RotateHandleProps {
 interface RotateHandleProps {