Răsfoiți Sursa

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

Bad3r 2 ani în urmă
părinte
comite
68a339c227
27 a modificat fișierele cu 360 adăugiri și 172 ștergeri
  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.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.move(bounds.x + 50, bounds.y + 50 )
+  await page.mouse.move(bounds.x + 150, bounds.y + 150 )
   await page.mouse.up()
   await page.keyboard.press('Escape')
 
@@ -114,12 +114,14 @@ test('clone the rectangle', async ({ page }) => {
   const canvas = await page.waitForSelector('.logseq-tldraw')
   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.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.keyboard.up('Alt')
 
@@ -163,10 +165,10 @@ test('connect rectangles with an arrow', async ({ page }) => {
 
   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.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.keyboard.press('Escape')
 
@@ -191,10 +193,15 @@ test('undo the delete action', 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.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-toolbar [data-tool=ellipse]')
 
@@ -223,9 +230,14 @@ test('undo the shape conversion', 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.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('Delete')
   await page.keyboard.press(`${modKey}+Shift+l`)
@@ -269,7 +281,7 @@ test('create a block', async ({ page }) => {
   const bounds = (await canvas.boundingBox())!
 
   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.keyboard.type('a')
@@ -304,7 +316,7 @@ test('copy/paste url to create an iFrame shape', async ({ page }) => {
   const bounds = (await canvas.boundingBox())!
 
   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.waitForTimeout(100)
 
@@ -323,7 +335,7 @@ test('copy/paste twitter status url to create a Tweet shape', async ({ page }) =
   const bounds = (await canvas.boundingBox())!
 
   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.waitForTimeout(100)
 
@@ -342,7 +354,7 @@ test('copy/paste youtube video url to create a Youtube shape', async ({ page })
   const bounds = (await canvas.boundingBox())!
 
   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.waitForTimeout(100)
 
@@ -394,8 +406,8 @@ test('quick add another whiteboard', async ({ page }) => {
   const canvas = await page.waitForSelector('.logseq-tldraw')
   await canvas.dblclick({
     position: {
-      x: 100,
-      y: 100,
+      x: 200,
+      y: 200,
     },
   })
 

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

@@ -115,6 +115,14 @@
    "(t title" []
    "(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
   "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
@@ -129,6 +137,7 @@
                           string/split-lines
                           (map #(keyword (subs % 4)))
                           (concat (mapcat val manual-ui-dicts))
+                          (concat (whiteboard-dicts))
                           set)
         expected-dicts (set (remove #(re-find #"^(command|shortcut)\." (str (namespace %)))
                                     (keys (:en (get-dicts)))))

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

@@ -30,6 +30,7 @@
 (defonce angle-bracket "<")
 (defonce hashtag "#")
 (defonce colon ":")
+(defonce command-trigger "/")
 (defonce *current-command (atom nil))
 
 (def query-doc
@@ -52,7 +53,7 @@
     "."]])
 
 (defn link-steps []
-  [[:editor/input (str (state/get-editor-command-trigger) "link")]
+  [[:editor/input (str command-trigger "link")]
    [:editor/show-input [{:command :link
                          :id :link
                          :placeholder "Link"
@@ -62,7 +63,7 @@
                          :placeholder "Label"}]]])
 
 (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
                          :id :link
                          :placeholder "Link"
@@ -72,7 +73,7 @@
                          :placeholder "Label"}]]])
 
 (defn zotero-steps []
-  [[:editor/input (str (state/get-editor-command-trigger) "zotero")]
+  [[:editor/input (str command-trigger "zotero")]
    [:editor/show-zotero]])
 
 (def *extend-slash-commands (atom []))
@@ -96,19 +97,19 @@
   [type]
   (let [template (util/format "@@%s: @@"
                               type)]
-    [[:editor/input template {:last-pattern (state/get-editor-command-trigger)
+    [[:editor/input template {:last-pattern command-trigger
                               :backward-pos 2}]]))
 
 (defn embed-page
   []
   (conj
-   [[:editor/input "{{embed [[]]}}" {:last-pattern (state/get-editor-command-trigger)
+   [[:editor/input "{{embed [[]]}}" {:last-pattern command-trigger
                                      :backward-pos 4}]]
    [:editor/search-page :embed]))
 
 (defn embed-block
   []
-  [[:editor/input "{{embed (())}}" {:last-pattern (state/get-editor-command-trigger)
+  [[:editor/input "{{embed (())}}" {:last-pattern command-trigger
                                     :backward-pos 4}]
    [:editor/search-block :embed]])
 
@@ -229,9 +230,9 @@
      ["Image link" (image-link-steps) "Create a HTTP link to a image"]
      (when (state/markdown?)
        ["Underline" [[:editor/input "<ins></ins>"
-                      {:last-pattern (state/get-editor-command-trigger)
+                      {:last-pattern command-trigger
                        :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"]
      (cond
        (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 image" [[:editor/click-hidden-file-input :id]]]
-       
+
 
     (headings)
 
@@ -290,12 +291,12 @@
                  text)) "Draw a graph with Excalidraw"]
      ["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}]]]
 
      ["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}]]]]
 
     @*extend-slash-commands
@@ -335,7 +336,7 @@
   (when-let [input (gdom/getElement id)]
     (let [last-pattern (when-not (= last-pattern :skip-check)
                          (when-not backward-truncate-number
-                          (or last-pattern (state/get-editor-command-trigger))))
+                           (or last-pattern command-trigger)))
           edit-content (gobj/get input "value")
           current-pos (cursor/pos input)
           current-pos (or
@@ -527,7 +528,7 @@
       (let [edit-content (gobj/get current-input "value")
             current-pos (cursor/pos current-input)
             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
                            (subs edit-content current-pos))]
         (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?]
   (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!))
 
 (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.page :as page]
             [frontend.config :as config]
+            [frontend.context.i18n :refer [t]]
             [frontend.db.model :as model]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.route :as route-handler]
@@ -93,7 +94,8 @@
 (def undo (fn [] (history/undo! nil)))
 (def redo (fn [] (history/redo! nil)))
 (defn get-tldraw-handlers [current-whiteboard-name]
-  {:search search-handler
+  {:t (fn [key] (t (keyword key)))
+   :search search-handler
    :queryBlockByUUID (fn [block-uuid]
                        (clj->js
                         (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)
                (catch :default _ ::failed-to-detect))
         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
       (= body ::failed-to-detect)
       (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"))
                                        image?)
                   format
-                  {:last-pattern (if drop-or-paste? "" (state/get-editor-command-trigger))
+                  {:last-pattern (if drop-or-paste? "" commands/command-trigger)
                    :restore?     true
                    :command      :insert-asset})))))
           (p/finally
@@ -1675,7 +1675,7 @@
           last-command (and last-slash-caret-pos (subs edit-content last-slash-caret-pos pos))]
       (when (> pos 0)
         (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)
          (and last-command
               (commands/get-matched-commands last-command)))))
@@ -1793,7 +1793,7 @@
                id
                (get-link format link label)
                format
-               {:last-pattern (str (state/get-editor-command-trigger) "link")
+               {:last-pattern (str commands/command-trigger "link")
                 :command :link})))
 
     :image-link (let [{:keys [link label]} m]
@@ -1802,7 +1802,7 @@
                      id
                      (get-image-link format link label)
                      format
-                     {:last-pattern (str (state/get-editor-command-trigger) "link")
+                     {:last-pattern (str commands/command-trigger "link")
                       :command :image-link})))
 
     nil)
@@ -1879,7 +1879,7 @@
         (-> (p/delay 10)
             (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)))
       (do
         (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
@@ -2730,7 +2730,7 @@
             (delete-block! repo false))))
 
       (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
         (util/stop e)
         (commands/restore-state)
@@ -3014,8 +3014,8 @@
                (util/event-is-composing? e true)])]
         (cond
           ;; 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!)
             (let [matched-commands (get-matched-commands input)]
               (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")))))
 
-;; 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?
   []
   (= (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/custom-date-format "Preferred date format"
  :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-workflow "Preferred workflow"
  :settings-page/preferred-pasting-file "Prefer pasting file"
@@ -249,6 +250,89 @@
  :search/publishing "Search"
  :search "Search or create page"
  :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"
  :graph-search "Search graph"
  :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/delete-alert "Weet je zeker dat je plugin [{1}] wilt verwijderen?"
  :plugin/disabled "Uitgeschakeld"
-\ :plugin/enabled "Ingeschakeld"
+ :plugin/enabled "Ingeschakeld"
  :plugin/install "Installeren"
  :plugin/installed "Geïnstalleerd"
  :plugin/installing "Installeren"

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

@@ -60,7 +60,7 @@
 
 (deftest detect-deprecations
   (is (re-find
-       #":editor/command-trigger.*Will"
+       #":editor/command-trigger.*is"
        (deprecation-warnings-for "{:preferred-workflow :todo :editor/command-trigger \",\"}"))
       "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 { ZoomMenu } from '../ZoomMenu'
 import * as Separator from '@radix-ui/react-separator'
+import { LogseqContext } from '../../lib/logseq-context'
 
 export const ActionBar = observer(function ActionBar(): JSX.Element {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
 
   const undo = React.useCallback(() => {
     app.api.undo()
@@ -32,20 +36,20 @@ export const ActionBar = observer(function ActionBar(): JSX.Element {
     <div className="tl-action-bar" data-html2canvas-ignore="true">
       {!app.readOnly && (
         <div className="tl-toolbar tl-history-bar">
-          <Button tooltip="Undo" onClick={undo}>
+          <Button tooltip={t('whiteboard/undo')} onClick={undo}>
             <TablerIcon name="arrow-back-up" />
           </Button>
-          <Button tooltip="Redo" onClick={redo}>
+          <Button tooltip={t('whiteboard/redo')} onClick={redo}>
             <TablerIcon name="arrow-forward-up" />
           </Button>
         </div>
       )}
 
       <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" />
         </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" />
         </Button>
         <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 app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<LogseqPortalShape | TextShape | HTMLShape>('AutoResizing')
 
   const pressed = shapes.every(s => s.props.isAutoResizing)
 
   return (
     <ToggleInput
-      tooltip="Auto Resize"
+      tooltip={t('whiteboard/auto-resize')}
       toggle={shapes.every(s => s.props.type === 'logseq-portal')}
       className="tl-button"
       pressed={pressed}
@@ -122,6 +125,9 @@ const AutoResizingAction = observer(() => {
 
 const LogseqPortalViewModeAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<LogseqPortalShape>('LogseqPortalViewMode')
 
   const collapsed = shapes.every(s => s.collapsed)
@@ -131,7 +137,7 @@ const LogseqPortalViewModeAction = observer(() => {
 
   const tooltip = (
     <div className="flex">
-      {collapsed ? 'Expand' : 'Collapse'}
+      {collapsed ? t('whiteboard/expand') : t('whiteboard/collapse')}
       <KeyboardShortcut
         action={collapsed ? 'editor/expand-block-children' : 'editor/collapse-block-children'}
       />
@@ -164,6 +170,9 @@ const ScaleLevelAction = observer(() => {
 
 const IFrameSourceAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shape = filterShapeByAction<IFrameShape>('IFrameSource')[0]
 
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
@@ -177,16 +186,20 @@ const IFrameSourceAction = observer(() => {
 
   return (
     <span className="flex gap-3">
-      <Button tooltip="Reload" type="button" onClick={handleReload}>
+      <Button tooltip={t('whiteboard/reload')} type="button" onClick={handleReload}>
         <TablerIcon name="refresh" />
       </Button>
       <TextInput
-        title="Website Url"
+        title={t('whiteboard/website-url')}
         className="tl-iframe-src"
         value={`${shape.props.url}`}
         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" />
       </Button>
     </span>
@@ -195,6 +208,9 @@ const IFrameSourceAction = observer(() => {
 
 const YoutubeLinkAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shape = filterShapeByAction<YouTubeShape>('YoutubeLink')[0]
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
     shape.onYoutubeLinkChange(e.target.value)
@@ -204,13 +220,13 @@ const YoutubeLinkAction = observer(() => {
   return (
     <span className="flex gap-3">
       <TextInput
-        title="YouTube Link"
+        title={t('whiteboard/youtube-url')}
         className="tl-youtube-link"
         value={`${shape.props.url}`}
         onChange={handleChange}
       />
       <Button
-        tooltip="Open YouTube Link"
+        tooltip={t('whiteboard/open-youtube-url')}
         type="button"
         onClick={() => window.logseq?.api?.open_external_link?.(shape.props.url)}
       >
@@ -222,6 +238,9 @@ const YoutubeLinkAction = observer(() => {
 
 const TwitterLinkAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shape = filterShapeByAction<TweetShape>('TwitterLink')[0]
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
     shape.onTwitterLinkChange(e.target.value)
@@ -231,13 +250,13 @@ const TwitterLinkAction = observer(() => {
   return (
     <span className="flex gap-3">
       <TextInput
-        title="Twitter Link"
+        title={t('whiteboard/twitter-url')}
         className="tl-twitter-link"
         value={`${shape.props.url}`}
         onChange={handleChange}
       />
       <Button
-        tooltip="Open Twitter Link"
+        tooltip={t('whiteboard/open-twitter-url')}
         type="button"
         onClick={() => window.logseq?.api?.open_external_link?.(shape.props.url)}
       >
@@ -249,6 +268,9 @@ const TwitterLinkAction = observer(() => {
 
 const NoFillAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<BoxShape | PolygonShape | EllipseShape>('NoFill')
   const handleChange = React.useCallback((v: boolean) => {
     app.selectedShapesArray.forEach(s => s.update({ noFill: v }))
@@ -259,7 +281,7 @@ const NoFillAction = observer(() => {
 
   return (
     <ToggleInput
-      tooltip="Fill"
+      tooltip={t('whiteboard/fill')}
       className="tl-button"
       pressed={noFill}
       onPressedChange={handleChange}
@@ -315,6 +337,9 @@ const GeometryAction = observer(() => {
 
 const StrokeTypeAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<
     BoxShape | PolygonShape | EllipseShape | LineShape | PencilShape
   >('StrokeType')
@@ -340,7 +365,7 @@ const StrokeTypeAction = observer(() => {
 
   return (
     <ToggleGroupInput
-      title="Stroke Type"
+      title={t('whiteboard/stroke-type')}
       options={StrokeTypeOptions}
       value={value}
       onValueChange={v => {
@@ -357,6 +382,9 @@ const StrokeTypeAction = observer(() => {
 
 const ArrowModeAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<LineShape>('ArrowMode')
 
   const StrokeTypeOptions: ToggleGroupInputOption[] = [
@@ -384,7 +412,7 @@ const ArrowModeAction = observer(() => {
 
   return (
     <ToggleGroupMultipleInput
-      title="Arrow Head"
+      title={t('whiteboard/arrow-head')}
       options={StrokeTypeOptions}
       value={value}
       onValueChange={v => {
@@ -401,6 +429,9 @@ const ArrowModeAction = observer(() => {
 
 const TextStyleAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<TextShape>('TextStyle')
 
   const bold = shapes.every(s => s.props.fontWeight > 500)
@@ -409,7 +440,7 @@ const TextStyleAction = observer(() => {
   return (
     <span className="flex gap-1">
       <ToggleInput
-        tooltip="Bold"
+        tooltip={t('whiteboard/bold')}
         className="tl-button"
         pressed={bold}
         onPressedChange={v => {
@@ -425,7 +456,7 @@ const TextStyleAction = observer(() => {
         <TablerIcon name="bold" />
       </ToggleInput>
       <ToggleInput
-        tooltip="Italic"
+        tooltip={t('whiteboard/italic')}
         className="tl-button"
         pressed={italic}
         onPressedChange={v => {

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

@@ -21,7 +21,9 @@ export const ContextMenu = observer(function ContextMenu({
   collisionRef,
 }: ContextMenuProps) {
   const app = useApp()
-  const { handlers } = React.useContext(LogseqContext)
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const rContent = React.useRef<HTMLDivElement>(null)
 
   const runAndTransition = (f: Function) => {
@@ -64,26 +66,26 @@ export const ContextMenu = observer(function ContextMenu({
                 <ReactContextMenu.Item>
                   <div className="tl-menu-button-row pb-0">
                     <Button
-                      tooltip="Align left"
+                      tooltip={t('whiteboard/align-left')}
                       onClick={() => runAndTransition(() => app.align(AlignType.Left))}
                     >
                       <TablerIcon name="layout-align-left" />
                     </Button>
                     <Button
-                      tooltip="Align center horizontally"
+                      tooltip={t('whiteboard/align-center-horizontally')}
                       onClick={() => runAndTransition(() => app.align(AlignType.CenterHorizontal))}
                     >
                       <TablerIcon name="layout-align-center" />
                     </Button>
                     <Button
-                      tooltip="Align right"
+                      tooltip={t('whiteboard/align-right')}
                       onClick={() => runAndTransition(() => app.align(AlignType.Right))}
                     >
                       <TablerIcon name="layout-align-right" />
                     </Button>
                     <Separator.Root className="tl-toolbar-separator" orientation="vertical" />
                     <Button
-                      tooltip="Distribute horizontally"
+                      tooltip={t('whiteboard/distribute-horizontally')}
                       onClick={() =>
                         runAndTransition(() => app.distribute(DistributeType.Horizontal))
                       }
@@ -93,26 +95,26 @@ export const ContextMenu = observer(function ContextMenu({
                   </div>
                   <div className="tl-menu-button-row pt-0">
                     <Button
-                      tooltip="Align top"
+                      tooltip={t('whiteboard/align-top')}
                       onClick={() => runAndTransition(() => app.align(AlignType.Top))}
                     >
                       <TablerIcon name="layout-align-top" />
                     </Button>
                     <Button
-                      tooltip="Align center vertically"
+                      tooltip={t('whiteboard/align-center-vertically')}
                       onClick={() => runAndTransition(() => app.align(AlignType.CenterVertical))}
                     >
                       <TablerIcon name="layout-align-middle" />
                     </Button>
                     <Button
-                      tooltip="Align bottom"
+                      tooltip={t('whiteboard/align-bottom')}
                       onClick={() => runAndTransition(() => app.align(AlignType.Bottom))}
                     >
                       <TablerIcon name="layout-align-bottom" />
                     </Button>
                     <Separator.Root className="tl-toolbar-separator" orientation="vertical" />
                     <Button
-                      tooltip="Distribute vertically"
+                      tooltip={t('whiteboard/distribute-vertically')}
                       onClick={() =>
                         runAndTransition(() => app.distribute(DistributeType.Vertical))
                       }
@@ -127,7 +129,7 @@ export const ContextMenu = observer(function ContextMenu({
                   onClick={() => runAndTransition(app.packIntoRectangle)}
                 >
                   <TablerIcon className="tl-menu-icon" name="layout-grid" />
-                  Pack into rectangle
+                  {t('whiteboard/pack-into-rectangle')}
                 </ReactContextMenu.Item>
                 <ReactContextMenu.Separator className="menu-separator" />
               </>
@@ -138,7 +140,7 @@ export const ContextMenu = observer(function ContextMenu({
                 className="tl-menu-item"
                 onClick={() => runAndTransition(app.api.zoomToSelection)}
               >
-                Zoom to fit
+                {t('whiteboard/zoom-to-fit')}
                 <KeyboardShortcut action="whiteboard/zoom-to-fit" />
               </ReactContextMenu.Item>
               <ReactContextMenu.Separator className="menu-separator" />
@@ -155,7 +157,7 @@ export const ContextMenu = observer(function ContextMenu({
                     onClick={() => runAndTransition(app.api.unGroup)}
                   >
                     <TablerIcon className="tl-menu-icon" name="ungroup" />
-                    Ungroup
+                    {t('whiteboard/ungroup')}
                     <KeyboardShortcut action="whiteboard/ungroup" />
                   </ReactContextMenu.Item>
                 )}
@@ -166,7 +168,7 @@ export const ContextMenu = observer(function ContextMenu({
                       onClick={() => runAndTransition(app.api.doGroup)}
                     >
                       <TablerIcon className="tl-menu-icon" name="group" />
-                      Group
+                      {t('whiteboard/group')}
                       <KeyboardShortcut action="whiteboard/group" />
                     </ReactContextMenu.Item>
                   )}
@@ -181,7 +183,7 @@ export const ContextMenu = observer(function ContextMenu({
                   onClick={() => runAndTransition(app.cut)}
                 >
                   <TablerIcon className="tl-menu-icon" name="cut" />
-                  Cut
+                  {t('whiteboard/cut')}
                 </ReactContextMenu.Item>
               )}
               <ReactContextMenu.Item
@@ -189,7 +191,7 @@ export const ContextMenu = observer(function ContextMenu({
                 onClick={() => runAndTransition(app.copy)}
               >
                 <TablerIcon className="tl-menu-icon" name="copy" />
-                Copy
+                {t('whiteboard/copy')}
                 <KeyboardShortcut action="editor/copy" />
               </ReactContextMenu.Item>
             </>
@@ -200,7 +202,7 @@ export const ContextMenu = observer(function ContextMenu({
               onClick={() => runAndTransition(app.paste)}
             >
               <TablerIcon className="tl-menu-icon" name="clipboard" />
-              Paste
+              {t('whiteboard/paste')}
               <div className="tl-menu-right-slot">
                 <span className="keyboard-shortcut">
                   <code>{MOD_KEY}</code> <code>v</code>
@@ -213,7 +215,7 @@ export const ContextMenu = observer(function ContextMenu({
               className="tl-menu-item"
               onClick={() => runAndTransition(() => app.paste(undefined, true))}
             >
-              Paste as link
+              {t('whiteboard/paste-as-link')}
               <div className="tl-menu-right-slot">
                 <span className="keyboard-shortcut">
                   <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" />
-                Export
+                {t('whiteboard/export')}
                 <div className="tl-menu-right-slot">
                   <span className="keyboard-shortcut"></span>
                 </div>
@@ -251,7 +253,7 @@ export const ContextMenu = observer(function ContextMenu({
             className="tl-menu-item"
             onClick={() => runAndTransition(app.api.selectAll)}
           >
-            Select all
+            {t('whiteboard/select-all')}
             <KeyboardShortcut action="editor/select-parent" />
           </ReactContextMenu.Item>
           {app.selectedShapes?.size > 1 && (
@@ -259,29 +261,33 @@ export const ContextMenu = observer(function ContextMenu({
               className="tl-menu-item"
               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>
           )}
+          {!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.readOnly &&
             app.selectedShapesArray?.some(s => !s.props.isLocked) && (
@@ -291,7 +297,7 @@ export const ContextMenu = observer(function ContextMenu({
                   onClick={() => runAndTransition(app.api.deleteShapes)}
                 >
                   <TablerIcon className="tl-menu-icon" name="backspace" />
-                  Delete
+                  {t('whiteboard/delete')}
                   <KeyboardShortcut action="editor/delete" />
                 </ReactContextMenu.Item>
                 {app.selectedShapes?.size > 1 && !app.readOnly && (
@@ -302,14 +308,14 @@ export const ContextMenu = observer(function ContextMenu({
                       onClick={() => runAndTransition(app.flipHorizontal)}
                     >
                       <TablerIcon className="tl-menu-icon" name="flip-horizontal" />
-                      Flip horizontally
+                      {t('whiteboard/flip-horizontally')}
                     </ReactContextMenu.Item>
                     <ReactContextMenu.Item
                       className="tl-menu-item"
                       onClick={() => runAndTransition(app.flipVertical)}
                     >
                       <TablerIcon className="tl-menu-icon" name="flip-vertical" />
-                      Flip vertically
+                      {t('whiteboard/flip-vertically')}
                     </ReactContextMenu.Item>
                   </>
                 )}
@@ -320,14 +326,14 @@ export const ContextMenu = observer(function ContextMenu({
                       className="tl-menu-item"
                       onClick={() => runAndTransition(app.bringToFront)}
                     >
-                      Move to front
+                      {t('whiteboard/move-to-front')}
                       <KeyboardShortcut action="whiteboard/bring-to-front" />
                     </ReactContextMenu.Item>
                     <ReactContextMenu.Item
                       className="tl-menu-item"
                       onClick={() => runAndTransition(app.sendToBack)}
                     >
-                      Move to back
+                      {t('whiteboard/move-to-back')}
                       <KeyboardShortcut action="whiteboard/send-to-back" />
                     </ReactContextMenu.Item>
                   </>
@@ -344,7 +350,7 @@ export const ContextMenu = observer(function ContextMenu({
                       }
                     }}
                   >
-                    (Dev) Print shape props
+                    {t('whiteboard/dev-print-shape-props')}
                   </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 * as Popover from '@radix-ui/react-popover'
 import { TablerIcon } from '../icons'
+import React from 'react'
+import { LogseqContext } from '../../lib/logseq-context'
 
 interface GeometryToolsProps extends React.HTMLAttributes<HTMLElement> {
   popoverSide?: Side
@@ -12,56 +14,72 @@ interface GeometryToolsProps extends React.HTMLAttributes<HTMLElement> {
 }
 
 export const GeometryTools = observer(function GeometryTools({
-  popoverSide = "left",
+  popoverSide = 'left',
   setGeometry,
   activeGeometry,
   chevron = true,
-  ...rest}: GeometryToolsProps) {
+  ...rest
+}: GeometryToolsProps) {
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
+
   const geometries = [
     {
       id: 'box',
       icon: 'square',
-      tooltip: 'Rectangle',
+      tooltip: t('whiteboard/rectangle'),
     },
     {
       id: 'ellipse',
       icon: 'circle',
-      tooltip: 'Circle',
+      tooltip: t('whiteboard/circle'),
     },
     {
       id: 'polygon',
       icon: 'triangle',
-      tooltip: 'Triangle',
+      tooltip: t('whiteboard/triangle'),
     },
   ]
 
   const shapes = {
     id: 'shapes',
     icon: 'triangle-square-circle',
-    tooltip: 'Shape',
+    tooltip: t('whiteboard/shape'),
   }
 
   const activeTool = activeGeometry ? geometries.find(geo => geo.id === activeGeometry) : shapes
 
   return (
     <Popover.Root>
-      <Popover.Trigger asChild >
+      <Popover.Trigger asChild>
         <div {...rest} className="tl-geometry-tools-pane-anchor">
           <ToolButton {...activeTool} tooltipSide={popoverSide} />
-          {chevron &&
+          {chevron && (
             <TablerIcon
               data-selected={activeGeometry}
               className="tl-popover-indicator"
               name="chevron-down-left"
             />
-          }
+          )}
         </div>
       </Popover.Trigger>
 
       <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 => (
-            <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>
 

+ 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 { ScaleInput } from '../inputs/ScaleInput'
 import * as Separator from '@radix-ui/react-separator'
+import { LogseqContext } from '../../lib/logseq-context'
 
 export const PrimaryTools = observer(function PrimaryTools() {
   const app = useApp()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
 
   const handleSetColor = React.useCallback((color: string) => {
     app.api.setColor(color)
@@ -37,50 +41,50 @@ export const PrimaryTools = observer(function PrimaryTools() {
       <div className="tl-toolbar tl-tools-floating-panel">
         <ToolButton
           handleClick={() => app.selectTool('select')}
-          tooltip="Select"
+          tooltip={t('whiteboard/select')}
           id="select"
           icon="select-cursor"
         />
         <ToolButton
           handleClick={() => app.selectTool('move')}
-          tooltip="Pan"
+          tooltip={t('whiteboard/pan')}
           id="move"
           icon={app.isIn('move.panning') ? 'hand-grab' : 'hand-stop'}
         />
         <Separator.Root className="tl-toolbar-separator" orientation="horizontal" />
         <ToolButton
           handleClick={() => app.selectTool('logseq-portal')}
-          tooltip="Add block or page"
+          tooltip={t('whiteboard/add-block-or-page')}
           id="logseq-portal"
           icon="circle-plus"
         />
         <ToolButton
           handleClick={() => app.selectTool('pencil')}
-          tooltip="Draw"
+          tooltip={t('whiteboard/draw')}
           id="pencil"
           icon="ballpen"
         />
         <ToolButton
           handleClick={() => app.selectTool('highlighter')}
-          tooltip="Highlight"
+          tooltip={t('whiteboard/highlight')}
           id="highlighter"
           icon="highlight"
         />
         <ToolButton
           handleClick={() => app.selectTool('erase')}
-          tooltip="Eraser"
+          tooltip={t('whiteboard/eraser')}
           id="erase"
           icon="eraser"
         />
         <ToolButton
           handleClick={() => app.selectTool('line')}
-          tooltip="Connector"
+          tooltip={t('whiteboard/connector')}
           id="line"
           icon="connector"
         />
         <ToolButton
           handleClick={() => app.selectTool('text')}
-          tooltip="Text"
+          tooltip={t('whiteboard/text')}
           id="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 }) => {
   const app = useApp()
   const { handlers } = React.useContext(LogseqContext)
+  const t = handlers.t
   const links = React.useMemo(() => {
     const links = [...(shape.props.refs ?? [])].map<[ref: string, showReferenceContent: boolean]>(
       // 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
 
   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]) => {
         return (
           <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 { handlers, renderers } = React.useContext(LogseqContext)
+    const t = handlers.t
 
     const finishSearching = React.useCallback((id: string) => {
       onChange(id)
@@ -172,11 +173,11 @@ export const LogseqQuickSearch = observer(
               <LogseqTypeTag active type="BA" />
               {q.length > 0 ? (
                 <>
-                  <strong>New block:</strong>
+                  <strong>{t('whiteboard/new-block')}</strong>
                   {q}
                 </>
               ) : (
-                <strong>New block</strong>
+                <strong>{t('whiteboard/new-block-no-colon')}</strong>
               )}
             </div>
           ),
@@ -195,7 +196,7 @@ export const LogseqQuickSearch = observer(
             element: (
               <div className="tl-quick-search-option-row">
                 <LogseqTypeTag active type="PA" />
-                <strong>New page:</strong>
+                <strong>{t('whiteboard/new-page')}</strong>
                 {q}
               </div>
             ),
@@ -210,7 +211,7 @@ export const LogseqQuickSearch = observer(
             element: (
               <div className="tl-quick-search-option-row">
                 <LogseqTypeTag active type="WA" />
-                <strong>New whiteboard:</strong>
+                <strong>{t('whiteboard/new-whiteboard')}</strong>
                 {q}
               </div>
             ),
@@ -230,7 +231,7 @@ export const LogseqQuickSearch = observer(
             element: (
               <div className="tl-quick-search-option-row">
                 <LogseqTypeTag type="BS" />
-                Search only blocks
+                {t('whiteboard/search-only-blocks')}
               </div>
             ),
           },
@@ -243,7 +244,7 @@ export const LogseqQuickSearch = observer(
             element: (
               <div className="tl-quick-search-option-row">
                 <LogseqTypeTag type="PS" />
-                Search only pages
+                {t('whiteboard/search-only-pages')}
               </div>
             ),
           }
@@ -302,10 +303,7 @@ export const LogseqQuickSearch = observer(
                     </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 { Tooltip } from '../Tooltip'
 import React from 'react'
+import { LogseqContext } from '../../lib/logseq-context'
 
 interface ColorInputProps extends React.HTMLAttributes<HTMLButtonElement> {
   color?: string
@@ -22,6 +23,10 @@ export function ColorInput({
   setOpacity,
   ...rest
 }: ColorInputProps) {
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
+
   function renderColor(color?: string) {
     return color ? (
       <div className="tl-color-bg" style={{ backgroundColor: color }}>
@@ -57,7 +62,7 @@ export function ColorInput({
       arrow
       side={popoverSide}
       label={
-        <Tooltip content={'Color'} side={popoverSide} sideOffset={14}>
+        <Tooltip content={t('whiteboard/color')} side={popoverSide} sideOffset={14}>
           {renderColor(color)}
         </Tooltip>
       }
@@ -82,7 +87,7 @@ export function ColorInput({
                 className="color-input cursor-pointer"
                 id="tl-custom-color-input"
                 type="color"
-                value={isHexColor(color) ? color : "#000000"}
+                value={isHexColor(color) ? color : '#000000'}
                 onChange={handleChangeDebounced}
                 style={{ opacity: isBuiltInColor(color) ? 0 : 1 }}
                 {...rest}
@@ -90,7 +95,7 @@ export function ColorInput({
             </div>
           </div>
           <label htmlFor="tl-custom-color-input" className="cursor-pointer">
-            Select custom color
+            {t('whiteboard/select-custom-color')}
           </label>
         </div>
 
@@ -101,7 +106,7 @@ export function ColorInput({
               onValueCommit={value => setOpacity(value[0])}
               max={1}
               step={0.1}
-              aria-label="Opacity"
+              aria-label={t('whiteboard/opacity')}
               className="tl-slider-root"
             >
               <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 { SizeLevel } from '../../lib'
 import { useApp } from '@tldraw/react'
+import React from 'react'
+import { LogseqContext } from '../../lib/logseq-context'
 
 interface ScaleInputProps extends React.HTMLAttributes<HTMLButtonElement> {
   scaleLevel?: SizeLevel
@@ -11,37 +13,40 @@ interface ScaleInputProps extends React.HTMLAttributes<HTMLButtonElement> {
 
 export function ScaleInput({ scaleLevel, compact, popoverSide, ...rest }: ScaleInputProps) {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
 
   const sizeOptions: SelectOption[] = [
     {
-      label: compact ? 'XS' : 'Extra Small',
+      label: compact ? 'XS' : t('whiteboard/extra-small'),
       value: 'xs',
     },
     {
-      label: compact ? 'SM' : 'Small',
+      label: compact ? 'SM' : t('whiteboard/small'),
       value: 'sm',
     },
     {
-      label: compact ? 'MD' : 'Medium',
+      label: compact ? 'MD' : t('whiteboard/medium'),
       value: 'md',
     },
     {
-      label: compact ? 'LG' : 'Large',
+      label: compact ? 'LG' : t('whiteboard/large'),
       value: 'lg',
     },
     {
-      label: compact ? 'XL' : 'Extra Large',
+      label: compact ? 'XL' : t('whiteboard/extra-large'),
       value: 'xl',
     },
     {
-      label: compact ? 'XXL' : 'Huge',
+      label: compact ? 'XXL' : t('whiteboard/huge'),
       value: 'xxl',
     },
   ]
 
   return (
     <SelectInput
-      tooltip="Scale level"
+      tooltip={t('whiteboard/scale-level')}
       options={sizeOptions}
       value={scaleLevel}
       popoverSide={popoverSide}

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

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

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

@@ -42,6 +42,7 @@ export interface LogseqContextValue {
     }>
   }
   handlers: {
+    t: (key: string) => any
     search: (
       query: string,
       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 maxZoom = 4
   static readonly panMultiplier = 0.05
+  static readonly panThreshold = 100
 
   /* ------------------- 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 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
       this.app.setSelectedShapes(hits)
     }
-    this.app.viewport.panToPointWhenOutOfBounds(currentPoint)
+    this.app.viewport.panToPointWhenNearBounds(currentPoint)
   }
 
   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.app.viewport.panToPointWhenOutOfBounds(currentPoint)
+    this.app.viewport.panToPointWhenNearBounds(currentPoint)
   }
 
   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.moveSelectedShapesToPointer()
-    this.app.viewport.panToPointWhenOutOfBounds(currentPoint)
+    this.app.viewport.panToPointWhenNearBounds(currentPoint)
   }
 
   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 * as React from 'react'
 import { useBoundsEvents } from '../../../../hooks'
 
 interface RotateHandleProps {