Browse Source

Merge branch 'master' into feat/slash-command-for-code-block

charlie 2 years ago
parent
commit
f326a504e5
28 changed files with 359 additions and 173 deletions
  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/test/frontend/handler/common/config_edn_test.cljs
  11. 8 4
      tldraw/apps/tldraw-logseq/src/components/ActionBar/ActionBar.tsx
  12. 45 14
      tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx
  13. 52 46
      tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx
  14. 29 11
      tldraw/apps/tldraw-logseq/src/components/GeometryTools/GeometryTools.tsx
  15. 12 8
      tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx
  16. 2 1
      tldraw/apps/tldraw-logseq/src/components/QuickLinks/QuickLinks.tsx
  17. 8 10
      tldraw/apps/tldraw-logseq/src/components/QuickSearch/QuickSearch.tsx
  18. 9 4
      tldraw/apps/tldraw-logseq/src/components/inputs/ColorInput.tsx
  19. 12 7
      tldraw/apps/tldraw-logseq/src/components/inputs/ScaleInput.tsx
  20. 16 7
      tldraw/apps/tldraw-logseq/src/components/inputs/ShapeLinksInput.tsx
  21. 1 0
      tldraw/apps/tldraw-logseq/src/lib/logseq-context.ts
  22. 0 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/TextShape.tsx
  23. 0 1
      tldraw/packages/core/src/lib/TLApp/TLApp.ts
  24. 6 3
      tldraw/packages/core/src/lib/TLViewport.ts
  25. 1 1
      tldraw/packages/core/src/lib/tools/TLSelectTool/states/BrushingState.ts
  26. 1 1
      tldraw/packages/core/src/lib/tools/TLSelectTool/states/ResizingState.ts
  27. 1 1
      tldraw/packages/core/src/lib/tools/TLSelectTool/states/TranslatingState.ts
  28. 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}]]]
 
      ["Code block" [[:editor/input "```\n```\n" {:type         "block"
@@ -339,7 +340,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
@@ -531,7 +532,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/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 }

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

@@ -132,7 +132,6 @@ export class TextShape extends TLTextShape<TextShapeProps> {
     const handleBlur = React.useCallback(
       (e: React.FocusEvent<HTMLTextAreaElement>) => {
         if (!isEditing) return
-        e.currentTarget.setSelectionRange(0, 0)
         onEditingEnd?.()
       },
       [onEditingEnd]

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

@@ -399,7 +399,6 @@ export class TLApp<
 
   @action addAssets<T extends TLAsset>(assets: T[]): this {
     assets.forEach(asset => (this.assets[asset.id] = asset))
-    this.persist()
     return this
   }
 

+ 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 {