Ver Fonte

Fix Notes overflow + add link support (#155)

* wrap note content

* url
Bernt Christian Egeland há 5 meses atrás
pai
commit
edb0c280d8

+ 14 - 189
package-lock.json

@@ -20,6 +20,7 @@
         "@react-pdf/renderer": "^4.3.2",
         "@sendgrid/mail": "^8.1.6",
         "@tanstack/react-query": "^5.90.21",
+        "@tiptap/extension-link": "^3.22.4",
         "@tiptap/extension-placeholder": "^3.19.0",
         "@tiptap/extension-underline": "^3.19.0",
         "@tiptap/pm": "^3.19.0",
@@ -5938,12 +5939,6 @@
         "url": "https://opencollective.com/immer"
       }
     },
-    "node_modules/@remirror/core-constants": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
-      "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
-      "license": "MIT"
-    },
     "node_modules/@rolldown/binding-android-arm64": {
       "version": "1.0.0-rc.13",
       "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.13.tgz",
@@ -7482,16 +7477,16 @@
       }
     },
     "node_modules/@tiptap/core": {
-      "version": "3.19.0",
-      "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.19.0.tgz",
-      "integrity": "sha512-bpqELwPW+DG8gWiD8iiFtSl4vIBooG5uVJod92Qxn3rA9nFatyXRr4kNbMJmOZ66ezUvmCjXVe/5/G4i5cyzKA==",
+      "version": "3.22.4",
+      "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.4.tgz",
+      "integrity": "sha512-vGIGm/HpqLg8EAAQXQ+koV+/S828OEpzocfWcPOwo1u2QUVf9dQG47Yy6JJ8zFFaJwfv4dBcOXli+7BrJwsxDQ==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/pm": "^3.19.0"
+        "@tiptap/pm": "3.22.4"
       }
     },
     "node_modules/@tiptap/extension-blockquote": {
@@ -7687,9 +7682,9 @@
       }
     },
     "node_modules/@tiptap/extension-link": {
-      "version": "3.19.0",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.19.0.tgz",
-      "integrity": "sha512-HEGDJnnCPfr7KWu7Dsq+eRRe/mBCsv6DuI+7fhOCLDJjjKzNgrX2abbo/zG3D/4lCVFaVb+qawgJubgqXR/Smw==",
+      "version": "3.22.4",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.22.4.tgz",
+      "integrity": "sha512-uoP3yus02uwGPVzW2QaEPJWVIrUb/r5nKm6c8DiJv9fNSX1+gykZZMg42c6GwRFLZ/vyfWjVCbAE03VMUqafgA==",
       "license": "MIT",
       "dependencies": {
         "linkifyjs": "^4.3.2"
@@ -7699,8 +7694,8 @@
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.19.0",
-        "@tiptap/pm": "^3.19.0"
+        "@tiptap/core": "3.22.4",
+        "@tiptap/pm": "3.22.4"
       }
     },
     "node_modules/@tiptap/extension-list": {
@@ -7836,27 +7831,21 @@
       }
     },
     "node_modules/@tiptap/pm": {
-      "version": "3.19.0",
-      "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.19.0.tgz",
-      "integrity": "sha512-789zcnM4a8OWzvbD2DL31d0wbSm9BVeO/R7PLQwLIGysDI3qzrcclyZ8yhqOEVuvPitRRwYLq+mY14jz7kY4cw==",
+      "version": "3.22.4",
+      "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.4.tgz",
+      "integrity": "sha512-hj8Qka6WcHRllHUdeSjDnq2XaisUo4KsoGJc1WcFpoa1Yd+OeD861zUMnV7DFVGdZRy45Obht0CUYJpXQ4yA4w==",
       "license": "MIT",
       "dependencies": {
         "prosemirror-changeset": "^2.3.0",
-        "prosemirror-collab": "^1.3.1",
         "prosemirror-commands": "^1.6.2",
         "prosemirror-dropcursor": "^1.8.1",
         "prosemirror-gapcursor": "^1.3.2",
         "prosemirror-history": "^1.4.1",
-        "prosemirror-inputrules": "^1.4.0",
         "prosemirror-keymap": "^1.2.2",
-        "prosemirror-markdown": "^1.13.1",
-        "prosemirror-menu": "^1.2.4",
         "prosemirror-model": "^1.24.1",
-        "prosemirror-schema-basic": "^1.2.3",
         "prosemirror-schema-list": "^1.5.0",
         "prosemirror-state": "^1.4.3",
         "prosemirror-tables": "^1.6.4",
-        "prosemirror-trailing-node": "^3.0.0",
         "prosemirror-transform": "^1.10.2",
         "prosemirror-view": "^1.38.1"
       },
@@ -8124,28 +8113,12 @@
         "@types/unist": "*"
       }
     },
-    "node_modules/@types/linkify-it": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
-      "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
-      "license": "MIT"
-    },
     "node_modules/@types/luxon": {
       "version": "3.7.1",
       "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz",
       "integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==",
       "license": "MIT"
     },
-    "node_modules/@types/markdown-it": {
-      "version": "14.1.2",
-      "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
-      "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/linkify-it": "^5",
-        "@types/mdurl": "^2"
-      }
-    },
     "node_modules/@types/mdast": {
       "version": "4.0.4",
       "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
@@ -8155,12 +8128,6 @@
         "@types/unist": "*"
       }
     },
-    "node_modules/@types/mdurl": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
-      "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
-      "license": "MIT"
-    },
     "node_modules/@types/ms": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
@@ -8594,6 +8561,7 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
       "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true,
       "license": "Python-2.0"
     },
     "node_modules/aria-hidden": {
@@ -9877,12 +9845,6 @@
         "node": ">= 14"
       }
     },
-    "node_modules/crelt": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
-      "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
-      "license": "MIT"
-    },
     "node_modules/cron": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/cron/-/cron-4.4.0.tgz",
@@ -10483,18 +10445,6 @@
         "node": ">=10.13.0"
       }
     },
-    "node_modules/entities": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
-      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
-      "license": "BSD-2-Clause",
-      "engines": {
-        "node": ">=0.12"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/entities?sponsor=1"
-      }
-    },
     "node_modules/env-paths": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
@@ -10593,18 +10543,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/escape-string-regexp": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
-      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/esprima": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
@@ -12800,15 +12738,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/linkify-it": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
-      "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
-      "license": "MIT",
-      "dependencies": {
-        "uc.micro": "^2.0.0"
-      }
-    },
     "node_modules/linkifyjs": {
       "version": "4.3.2",
       "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz",
@@ -13004,23 +12933,6 @@
         "semver": "bin/semver"
       }
     },
-    "node_modules/markdown-it": {
-      "version": "14.1.1",
-      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
-      "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
-      "license": "MIT",
-      "dependencies": {
-        "argparse": "^2.0.1",
-        "entities": "^4.4.0",
-        "linkify-it": "^5.0.0",
-        "mdurl": "^2.0.0",
-        "punycode.js": "^2.3.1",
-        "uc.micro": "^2.1.0"
-      },
-      "bin": {
-        "markdown-it": "bin/markdown-it.mjs"
-      }
-    },
     "node_modules/markdown-table": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
@@ -13329,12 +13241,6 @@
       "dev": true,
       "license": "CC0-1.0"
     },
-    "node_modules/mdurl": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
-      "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
-      "license": "MIT"
-    },
     "node_modules/media-engine": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz",
@@ -15517,15 +15423,6 @@
         "prosemirror-transform": "^1.0.0"
       }
     },
-    "node_modules/prosemirror-collab": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
-      "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
-      "license": "MIT",
-      "dependencies": {
-        "prosemirror-state": "^1.0.0"
-      }
-    },
     "node_modules/prosemirror-commands": {
       "version": "1.7.1",
       "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
@@ -15572,16 +15469,6 @@
         "rope-sequence": "^1.3.0"
       }
     },
-    "node_modules/prosemirror-inputrules": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz",
-      "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==",
-      "license": "MIT",
-      "dependencies": {
-        "prosemirror-state": "^1.0.0",
-        "prosemirror-transform": "^1.0.0"
-      }
-    },
     "node_modules/prosemirror-keymap": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
@@ -15592,29 +15479,6 @@
         "w3c-keyname": "^2.2.0"
       }
     },
-    "node_modules/prosemirror-markdown": {
-      "version": "1.13.4",
-      "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz",
-      "integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/markdown-it": "^14.0.0",
-        "markdown-it": "^14.0.0",
-        "prosemirror-model": "^1.25.0"
-      }
-    },
-    "node_modules/prosemirror-menu": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz",
-      "integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==",
-      "license": "MIT",
-      "dependencies": {
-        "crelt": "^1.0.0",
-        "prosemirror-commands": "^1.0.0",
-        "prosemirror-history": "^1.0.0",
-        "prosemirror-state": "^1.0.0"
-      }
-    },
     "node_modules/prosemirror-model": {
       "version": "1.25.4",
       "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
@@ -15624,15 +15488,6 @@
         "orderedmap": "^2.0.0"
       }
     },
-    "node_modules/prosemirror-schema-basic": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz",
-      "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==",
-      "license": "MIT",
-      "dependencies": {
-        "prosemirror-model": "^1.25.0"
-      }
-    },
     "node_modules/prosemirror-schema-list": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
@@ -15668,21 +15523,6 @@
         "prosemirror-view": "^1.41.4"
       }
     },
-    "node_modules/prosemirror-trailing-node": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
-      "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@remirror/core-constants": "3.0.0",
-        "escape-string-regexp": "^4.0.0"
-      },
-      "peerDependencies": {
-        "prosemirror-model": "^1.22.1",
-        "prosemirror-state": "^1.4.2",
-        "prosemirror-view": "^1.33.8"
-      }
-    },
     "node_modules/prosemirror-transform": {
       "version": "1.11.0",
       "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz",
@@ -15760,15 +15600,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/punycode.js": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
-      "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/pure-rand": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
@@ -17957,12 +17788,6 @@
         "node": ">=14.17"
       }
     },
-    "node_modules/uc.micro": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
-      "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
-      "license": "MIT"
-    },
     "node_modules/undici": {
       "version": "7.24.4",
       "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.4.tgz",

+ 1 - 0
package.json

@@ -28,6 +28,7 @@
     "@react-pdf/renderer": "^4.3.2",
     "@sendgrid/mail": "^8.1.6",
     "@tanstack/react-query": "^5.90.21",
+    "@tiptap/extension-link": "^3.22.4",
     "@tiptap/extension-placeholder": "^3.19.0",
     "@tiptap/extension-underline": "^3.19.0",
     "@tiptap/pm": "^3.19.0",

+ 11 - 0
src/app/globals.css

@@ -178,6 +178,7 @@
 
 /* Tiptap editor styles */
 .tiptap-content {
+  overflow-wrap: anywhere;
   &:focus {
     outline: none;
   }
@@ -211,6 +212,11 @@
     color: var(--muted-foreground);
     font-style: italic;
   }
+  a {
+    color: var(--primary);
+    text-decoration: underline;
+    cursor: pointer;
+  }
   p.is-editor-empty:first-child::before {
     content: attr(data-placeholder);
     float: left;
@@ -222,6 +228,7 @@
 
 /* Notes display (rendered HTML) */
 .notes-content {
+  overflow-wrap: anywhere;
   h2 {
     font-size: 1.25rem;
     font-weight: 600;
@@ -249,6 +256,10 @@
     color: var(--muted-foreground);
     font-style: italic;
   }
+  a {
+    color: var(--primary);
+    text-decoration: underline;
+  }
   > * + * {
     margin-top: 0.5em;
   }

+ 1 - 1
src/components/ui/sidebar.tsx

@@ -309,7 +309,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
     <main
       data-slot="sidebar-inset"
       className={cn(
-        "bg-background relative flex w-full flex-1 flex-col",
+        "bg-background relative flex w-full min-w-0 flex-1 flex-col",
         "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
         className
       )}

+ 32 - 0
src/features/vehicles/Components/service-edit/RichTextEditor.tsx

@@ -5,11 +5,13 @@ import { useEditor, EditorContent } from '@tiptap/react'
 import StarterKit from '@tiptap/starter-kit'
 import Underline from '@tiptap/extension-underline'
 import Placeholder from '@tiptap/extension-placeholder'
+import Link from '@tiptap/extension-link'
 import { Button } from '@/components/ui/button'
 import {
   Bold,
   Italic,
   Underline as UnderlineIcon,
+  Link as LinkIcon,
   List,
   ListOrdered,
   Heading2,
@@ -36,6 +38,15 @@ export function RichTextEditor({ content, onChange, placeholder }: RichTextEdito
         heading: { levels: [2, 3] },
       }),
       Underline,
+      Link.configure({
+        openOnClick: false,
+        autolink: true,
+        linkOnPaste: true,
+        HTMLAttributes: {
+          rel: 'noopener noreferrer',
+          target: '_blank',
+        },
+      }),
       Placeholder.configure({
         placeholder: placeholder || 'Write something...',
       }),
@@ -63,6 +74,20 @@ export function RichTextEditor({ content, onChange, placeholder }: RichTextEdito
 
   if (!editor) return null
 
+  const handleSetLink = () => {
+    const previous = editor.getAttributes('link').href as string | undefined
+    const url = window.prompt('URL', previous ?? 'https://')
+    if (url === null) return
+    if (url === '') {
+      editor.chain().focus().extendMarkRange('link').unsetLink().run()
+      return
+    }
+    const normalized = /^https?:\/\//i.test(url) || url.startsWith('mailto:')
+      ? url
+      : `https://${url}`
+    editor.chain().focus().extendMarkRange('link').setLink({ href: normalized }).run()
+  }
+
   return (
     <div className="overflow-hidden rounded-md border border-input bg-background flex flex-col resize-y" style={{ minHeight: 180 }}>
       <div className="flex flex-wrap items-center gap-0.5 border-b bg-muted/30 px-1 py-1 shrink-0">
@@ -87,6 +112,13 @@ export function RichTextEditor({ content, onChange, placeholder }: RichTextEdito
         >
           <UnderlineIcon className="h-3.5 w-3.5" />
         </ToolbarButton>
+        <ToolbarButton
+          active={editor.isActive('link')}
+          onClick={handleSetLink}
+          title="Link"
+        >
+          <LinkIcon className="h-3.5 w-3.5" />
+        </ToolbarButton>
 
         <div className="mx-0.5 h-4 w-px bg-border" />