From 783b4970c6650330870181e5e0077b7ef79945d3 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 22 Jul 2026 12:13:03 -0400 Subject: [PATCH 01/14] Add @tiptap/extension-mention and @tiptap/suggestion deps Verified @floating-ui/dom is importable via the bundle already (a reka-ui dependency), so the AutocompleteEditor suggestion popup can use it directly without pulling in tippy.js or falling back to manual posToDOMRect positioning. Co-Authored-By: Claude --- package-lock.json | 312 ++++++++++------------------------------------ package.json | 2 + 2 files changed, 66 insertions(+), 248 deletions(-) diff --git a/package-lock.json b/package-lock.json index 91aa419dcb..dd4e4b1b76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "@tiptap/extension-italic": "^3.0.0", "@tiptap/extension-list": "^3.0.0", "@tiptap/extension-list-item": "^3.0.0", + "@tiptap/extension-mention": "^3.0.0", "@tiptap/extension-ordered-list": "^3.0.0", "@tiptap/extension-paragraph": "^3.0.0", "@tiptap/extension-strike": "^3.0.0", @@ -42,6 +43,7 @@ "@tiptap/extension-underline": "^3.0.0", "@tiptap/extensions": "^3.0.0", "@tiptap/pm": "^3.0.0", + "@tiptap/suggestion": "^3.0.0", "@tiptap/vue-3": "^3.0.0", "alpinejs": "^3.1.1", "autosize": "~6.0.1", @@ -1544,9 +1546,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1564,9 +1563,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1584,9 +1580,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1604,9 +1597,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1624,9 +1614,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1644,9 +1631,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1664,9 +1648,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1684,9 +1665,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2116,12 +2094,6 @@ "dev": true, "license": "MIT" }, - "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.3", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", @@ -2983,16 +2955,16 @@ } }, "node_modules/@tiptap/core": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.3.tgz", - "integrity": "sha512-Dv9MKK5BDWCF0N2l6/Pxv3JNCce2kwuWf2cKMBc2bEetx0Pn6o7zlFmSxMvYK4UtG1Tw9Yg/ZHi6QOFWK0Zm9Q==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.28.0.tgz", + "integrity": "sha512-gUuD5WAYfbDxNSSJya/emh2KSzXZXLUYKW4fEnc1AQ5FE2twzh4LJ9UlKFIawigrUCAksWI5Fy1hRbv+5m4ZdQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "^3.22.3" + "@tiptap/pm": "3.28.0" } }, "node_modules/@tiptap/extension-blockquote": { @@ -3231,6 +3203,21 @@ "@tiptap/extension-list": "^3.22.3" } }, + "node_modules/@tiptap/extension-mention": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-3.28.0.tgz", + "integrity": "sha512-Y3TJOTLpTCLmUJuKGA5K8Rxl5GpgwVd7atda0yCmKKYO0czTL4afN5tNIUSWoBqj0nBQdi4DU+Xnl+s+EfM/4g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0", + "@tiptap/suggestion": "3.28.0" + } + }, "node_modules/@tiptap/extension-ordered-list": { "version": "3.22.3", "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.22.3.tgz", @@ -3379,35 +3366,45 @@ } }, "node_modules/@tiptap/pm": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.3.tgz", - "integrity": "sha512-NjfWjZuvrqmpICT+GZWNIjtOdhPyqFKDMtQy7tsQ5rErM9L2ZQdy/+T/BKSO1JdTeBhdg9OP+0yfsqoYp2aT6A==", - "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" + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.28.0.tgz", + "integrity": "sha512-ALcpwZMUdat9gjJKlpscpoqXStoLhU246LPEVBDvJdIsoUKvUu3MrzfXik2Y8mtSGfhjtm9O2TRkWxQiFVMwsQ==", + "license": "MIT", + "dependencies": { + "prosemirror-changeset": "^2.4.1", + "prosemirror-commands": "^1.7.1", + "prosemirror-dropcursor": "^1.8.2", + "prosemirror-gapcursor": "^1.4.1", + "prosemirror-history": "^1.5.0", + "prosemirror-inputrules": "^1.5.1", + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.9", + "prosemirror-schema-list": "^1.5.1", + "prosemirror-state": "^1.4.4", + "prosemirror-tables": "^1.8.5", + "prosemirror-transform": "^1.12.0", + "prosemirror-view": "^1.41.9" }, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" } }, + "node_modules/@tiptap/suggestion": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-3.28.0.tgz", + "integrity": "sha512-24k0CvvazBeHAK2+WrV1n0uDBZBHNbDAq1K8b5KkWSxcAU7zSCytmApS1u7clRW5wg2xKNBdDhNhvk7KSm1kIA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0" + } + }, "node_modules/@tiptap/vue-3": { "version": "3.22.3", "resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-3.22.3.tgz", @@ -3481,12 +3478,6 @@ "@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/lodash": { "version": "4.17.24", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", @@ -3502,22 +3493,6 @@ "@types/lodash": "*" } }, - "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/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/mdx": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", @@ -4350,12 +4325,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, "node_modules/aria-hidden": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", @@ -4800,12 +4769,6 @@ "url": "https://github.com/sponsors/mesqueeb" } }, - "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/cropperjs": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.6.2.tgz", @@ -5368,18 +5331,6 @@ "node": ">=6" } }, - "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/esm-resolve": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/esm-resolve/-/esm-resolve-1.0.11.tgz", @@ -6631,25 +6582,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/linkify-it": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", - "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, "node_modules/lodash-es": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", @@ -6704,45 +6636,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/markdown-it": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", - "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.1", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdown-it/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/marked": { "version": "15.0.12", "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", @@ -6770,12 +6663,6 @@ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "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/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7449,23 +7336,14 @@ } }, "node_modules/prosemirror-changeset": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz", - "integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.1.tgz", + "integrity": "sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==", "license": "MIT", "dependencies": { "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", @@ -7532,47 +7410,15 @@ "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", - "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", + "version": "1.25.11", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.11.tgz", + "integrity": "sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==", "license": "MIT", "dependencies": { "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", @@ -7608,21 +7454,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.12.0", "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz", @@ -7633,12 +7464,12 @@ } }, "node_modules/prosemirror-view": { - "version": "1.41.8", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz", - "integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==", + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.42.1.tgz", + "integrity": "sha512-rRqzZnRgkyh69XoOMrfFJHwauHscLBmHbq772kwbic1ymQAM8gXjzEbJse5j1ep2UO2HRIAQL0bY3kZ/RoqjVw==", "license": "MIT", "dependencies": { - "prosemirror-model": "^1.20.0", + "prosemirror-model": "^1.25.8", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0" } @@ -7804,15 +7635,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/pusher-js": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.5.0.tgz", @@ -8876,12 +8698,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/unbash": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/unbash/-/unbash-2.2.0.tgz", diff --git a/package.json b/package.json index ceb6142c95..2d19403804 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@tiptap/extension-italic": "^3.0.0", "@tiptap/extension-list": "^3.0.0", "@tiptap/extension-list-item": "^3.0.0", + "@tiptap/extension-mention": "^3.0.0", "@tiptap/extension-ordered-list": "^3.0.0", "@tiptap/extension-paragraph": "^3.0.0", "@tiptap/extension-strike": "^3.0.0", @@ -55,6 +56,7 @@ "@tiptap/extension-underline": "^3.0.0", "@tiptap/extensions": "^3.0.0", "@tiptap/pm": "^3.0.0", + "@tiptap/suggestion": "^3.0.0", "@tiptap/vue-3": "^3.0.0", "alpinejs": "^3.1.1", "autosize": "~6.0.1", From 04729972714027ec42a07fe3f4f7feee234fd6a2 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 22 Jul 2026 12:19:24 -0400 Subject: [PATCH 02/14] Add shared Autocomplete mention extension and SuggestionList Reusable TipTap mention extension storing both value and label attrs, with a baked-in suggestion renderer that drives SuggestionList.vue and positions it via the suggestion utility's built-in floating-ui mount. App-specifics (char, items) are passed through .configure() so Bard can reuse it later. Co-Authored-By: Claude --- .../ui/AutocompleteEditor/SuggestionList.vue | 68 +++++++++++++++++ .../extensions/Autocomplete.js | 74 +++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 resources/js/components/ui/AutocompleteEditor/SuggestionList.vue create mode 100644 resources/js/components/ui/AutocompleteEditor/extensions/Autocomplete.js diff --git a/resources/js/components/ui/AutocompleteEditor/SuggestionList.vue b/resources/js/components/ui/AutocompleteEditor/SuggestionList.vue new file mode 100644 index 0000000000..ecd2047642 --- /dev/null +++ b/resources/js/components/ui/AutocompleteEditor/SuggestionList.vue @@ -0,0 +1,68 @@ + + + diff --git a/resources/js/components/ui/AutocompleteEditor/extensions/Autocomplete.js b/resources/js/components/ui/AutocompleteEditor/extensions/Autocomplete.js new file mode 100644 index 0000000000..cf03ca02ce --- /dev/null +++ b/resources/js/components/ui/AutocompleteEditor/extensions/Autocomplete.js @@ -0,0 +1,74 @@ +import Mention from '@tiptap/extension-mention'; +import { VueRenderer } from '@tiptap/vue-3'; +import SuggestionList from '../SuggestionList.vue'; + +function renderSuggestion() { + let component; + let unmount; + + return { + onStart(props) { + component = new VueRenderer(SuggestionList, { + props, + editor: props.editor, + }); + + unmount = props.mount(component.element); + }, + + onUpdate(props) { + component?.updateProps(props); + }, + + onKeyDown(props) { + if (props.event.key === 'Escape') { + unmount?.(); + return true; + } + + return component?.ref?.onKeyDown(props) ?? false; + }, + + onExit() { + unmount?.(); + unmount = undefined; + component?.destroy(); + component = undefined; + }, + }; +} + +export const Autocomplete = Mention.extend({ + name: 'mention', + + addOptions() { + return { + ...this.parent?.(), + HTMLAttributes: { class: 'autocomplete-mention' }, + renderText({ node }) { + return node.attrs.label ?? node.attrs.value ?? ''; + }, + renderHTML({ options, node }) { + return ['span', options.HTMLAttributes, node.attrs.label ?? node.attrs.value ?? '']; + }, + suggestion: { + render: renderSuggestion, + }, + }; + }, + + addAttributes() { + return { + value: { + default: null, + parseHTML: (element) => element.getAttribute('data-value'), + renderHTML: (attributes) => (attributes.value ? { 'data-value': attributes.value } : {}), + }, + label: { + default: null, + parseHTML: (element) => element.getAttribute('data-label'), + renderHTML: (attributes) => (attributes.label ? { 'data-label': attributes.label } : {}), + }, + }; + }, +}); From 4da0bb0ac8950e0f582e9cdbe2c025ecf6d0a8ff Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 22 Jul 2026 12:30:26 -0400 Subject: [PATCH 03/14] Add AutocompleteEditor primitive component and generic toolbar Build the AutocompleteEditor.vue primitive (TipTap block editor with conditional bold/italic/heading/list extensions, wired to the shared Autocomplete mention extension) plus a generic EditorToolbarButton.vue and AutocompleteEditorToolbar.vue. Export the primitive from the ui index. Co-Authored-By: Claude --- .../AutocompleteEditor/AutocompleteEditor.vue | 193 ++++++++++++++++++ .../AutocompleteEditorToolbar.vue | 32 +++ .../EditorToolbarButton.vue | 25 +++ resources/js/components/ui/index.js | 1 + 4 files changed, 251 insertions(+) create mode 100644 resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue create mode 100644 resources/js/components/ui/AutocompleteEditor/AutocompleteEditorToolbar.vue create mode 100644 resources/js/components/ui/AutocompleteEditor/EditorToolbarButton.vue diff --git a/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue new file mode 100644 index 0000000000..dc671c0adc --- /dev/null +++ b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue @@ -0,0 +1,193 @@ + + + diff --git a/resources/js/components/ui/AutocompleteEditor/AutocompleteEditorToolbar.vue b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditorToolbar.vue new file mode 100644 index 0000000000..b48a6f708a --- /dev/null +++ b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditorToolbar.vue @@ -0,0 +1,32 @@ + + + diff --git a/resources/js/components/ui/AutocompleteEditor/EditorToolbarButton.vue b/resources/js/components/ui/AutocompleteEditor/EditorToolbarButton.vue new file mode 100644 index 0000000000..b7781242c4 --- /dev/null +++ b/resources/js/components/ui/AutocompleteEditor/EditorToolbarButton.vue @@ -0,0 +1,25 @@ + + + diff --git a/resources/js/components/ui/index.js b/resources/js/components/ui/index.js index 9527d5461c..aaf0b950ff 100644 --- a/resources/js/components/ui/index.js +++ b/resources/js/components/ui/index.js @@ -1,5 +1,6 @@ export { default as Alert } from './Alert.vue'; export { default as AuthCard } from './AuthCard.vue'; +export { default as AutocompleteEditor } from './AutocompleteEditor/AutocompleteEditor.vue'; export { default as Badge } from './Badge.vue'; export { default as Button } from './Button/Button.vue'; export { default as ButtonGroup } from './Button/Group.vue'; From 81ef05a5b4cea2fa2986ed96cf46135259047451 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 22 Jul 2026 12:55:44 -0400 Subject: [PATCH 04/14] Implement AutocompleteEditor inline and line-break modes Use DocumentInline (single paragraph) when inline, DocumentBlock otherwise, dropping block nodes/toolbar in inline mode. Gate line breaks on enableLineBreaks: swallow Enter when false, hard break (inline) or paragraph (block) when true, always passing Ctrl/Cmd+Enter through for form submission. Co-Authored-By: Claude --- .../AutocompleteEditor/AutocompleteEditor.vue | 53 ++++++++++++++++--- .../AutocompleteEditor/extensions/Document.js | 7 +++ 2 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 resources/js/components/ui/AutocompleteEditor/extensions/Document.js diff --git a/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue index dc671c0adc..801be2f583 100644 --- a/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue +++ b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue @@ -14,11 +14,11 @@ diff --git a/src/Fieldtypes/AutocompleteEditor.php b/src/Fieldtypes/AutocompleteEditor.php new file mode 100644 index 0000000000..853b0ea787 --- /dev/null +++ b/src/Fieldtypes/AutocompleteEditor.php @@ -0,0 +1,156 @@ + [ + 'display' => __('Options'), + 'instructions' => __('statamic::fieldtypes.autocomplete_editor.config.options'), + 'type' => 'array', + 'expand' => true, + 'value_header' => __('Label').' ('.__('Optional').')', + 'field' => [ + 'type' => 'text', + ], + ], + 'trigger' => [ + 'display' => __('Trigger'), + 'instructions' => __('statamic::fieldtypes.autocomplete_editor.config.trigger'), + 'type' => 'text', + 'default' => '@', + 'width' => 50, + ], + 'placeholder' => [ + 'display' => __('Placeholder'), + 'instructions' => __('statamic::fieldtypes.autocomplete_editor.config.placeholder'), + 'type' => 'text', + 'width' => 50, + ], + 'buttons' => [ + 'display' => __('Buttons'), + 'instructions' => __('statamic::fieldtypes.autocomplete_editor.config.buttons'), + 'type' => 'checkboxes', + 'options' => [ + 'bold' => __('Bold'), + 'italic' => __('Italic'), + 'h1' => __('Heading 1'), + 'h2' => __('Heading 2'), + 'h3' => __('Heading 3'), + 'h4' => __('Heading 4'), + 'h5' => __('Heading 5'), + 'h6' => __('Heading 6'), + 'bulletlist' => __('Unordered List'), + 'orderedlist' => __('Ordered List'), + ], + 'default' => ['bold', 'italic', 'h2', 'h3', 'bulletlist', 'orderedlist'], + ], + 'inline' => [ + 'display' => __('Inline'), + 'instructions' => __('statamic::fieldtypes.autocomplete_editor.config.inline'), + 'type' => 'toggle', + 'default' => false, + 'width' => 50, + ], + 'enable_line_breaks' => [ + 'display' => __('Enable Line Breaks'), + 'instructions' => __('statamic::fieldtypes.autocomplete_editor.config.enable_line_breaks'), + 'type' => 'toggle', + 'default' => false, + 'width' => 50, + ], + ]; + } + + public function preload(): array + { + return [ + 'options' => $this->getOptions(), + ]; + } + + protected function getOptions(): array + { + $options = $this->config('options') ?? []; + + if (array_is_list($options) && ! is_array(Arr::first($options))) { + $options = collect($options)->map(fn ($value) => ['key' => $value, 'value' => $value])->all(); + } + + if (Arr::isAssoc($options)) { + $options = collect($options)->map(fn ($value, $key) => ['key' => $key, 'value' => $value])->all(); + } + + return collect($options) + ->map(fn ($item) => ['value' => $item['key'], 'label' => $item['value']]) + ->values() + ->all(); + } + + public function process($value) + { + if ($this->config('inline')) { + $value = $this->unwrapInlineValue($value); + } + + if (empty($value) || $value === [['type' => 'paragraph']]) { + return null; + } + + return $value; + } + + public function preProcess($value) + { + $value = $this->removeBrokenNodes($value); + + if (empty($value)) { + return []; + } + + if ($this->config('inline')) { + if (! in_array($value[0]['type'], ['text', 'hardBreak'])) { + $value = $this->unwrapInlineValue($value); + } + $value = $this->wrapInlineValue($value); + } elseif (in_array($value[0]['type'], ['text', 'hardBreak'])) { + $value = $this->wrapInlineValue($value); + } + + return $value; + } + + protected function removeBrokenNodes($value) + { + if (! is_array($value)) { + return []; + } + + return collect($value)->filter(function ($node) { + return array_key_exists('type', $node); + })->values()->all(); + } + + private function wrapInlineValue($value) + { + return [[ + 'type' => 'paragraph', + 'content' => $value, + ]]; + } + + private function unwrapInlineValue($value) + { + return $value[0]['content'] ?? []; + } +} From 4e021ad1d437a44ec92c9c9d4c1c4133b7440bd1 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 22 Jul 2026 13:21:26 -0400 Subject: [PATCH 07/14] Align @tiptap/* packages to a single compatible version package-lock.json had @tiptap/vue-3 pinned at 3.22.3 alongside @tiptap/core at 3.28.0. vue-3 imports cancelPositionCheck and schedulePositionCheck from core, which don't exist in 3.28.0, breaking a fresh install build. Bump all @tiptap/* packages to ^3.28.0 and regenerate the lockfile so every package resolves to the same 3.28.0 release. Co-Authored-By: Claude --- package-lock.json | 323 +++++++++++++++++++++++----------------------- package.json | 62 ++++----- 2 files changed, 193 insertions(+), 192 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd4e4b1b76..54793f2842 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,37 +14,37 @@ "@internationalized/date": "^3.7.0", "@shopify/draggable": "^1.0.0-beta.12", "@simplewebauthn/browser": "^13.2.2", - "@tiptap/core": "^3.0.0", - "@tiptap/extension-blockquote": "^3.0.0", - "@tiptap/extension-bold": "^3.0.0", - "@tiptap/extension-bullet-list": "^3.0.0", - "@tiptap/extension-character-count": "^3.0.0", - "@tiptap/extension-code": "^3.0.0", - "@tiptap/extension-code-block": "^3.0.0", - "@tiptap/extension-code-block-lowlight": "^3.0.0", - "@tiptap/extension-document": "^3.0.0", - "@tiptap/extension-hard-break": "^3.0.0", - "@tiptap/extension-heading": "^3.0.0", - "@tiptap/extension-history": "^3.0.0", - "@tiptap/extension-horizontal-rule": "^3.0.0", - "@tiptap/extension-italic": "^3.0.0", - "@tiptap/extension-list": "^3.0.0", - "@tiptap/extension-list-item": "^3.0.0", - "@tiptap/extension-mention": "^3.0.0", - "@tiptap/extension-ordered-list": "^3.0.0", - "@tiptap/extension-paragraph": "^3.0.0", - "@tiptap/extension-strike": "^3.0.0", - "@tiptap/extension-subscript": "^3.0.0", - "@tiptap/extension-superscript": "^3.0.0", - "@tiptap/extension-table": "^3.0.0", - "@tiptap/extension-text": "^3.0.0", - "@tiptap/extension-text-align": "^3.0.0", - "@tiptap/extension-typography": "^3.0.0", - "@tiptap/extension-underline": "^3.0.0", - "@tiptap/extensions": "^3.0.0", - "@tiptap/pm": "^3.0.0", - "@tiptap/suggestion": "^3.0.0", - "@tiptap/vue-3": "^3.0.0", + "@tiptap/core": "^3.28.0", + "@tiptap/extension-blockquote": "^3.28.0", + "@tiptap/extension-bold": "^3.28.0", + "@tiptap/extension-bullet-list": "^3.28.0", + "@tiptap/extension-character-count": "^3.28.0", + "@tiptap/extension-code": "^3.28.0", + "@tiptap/extension-code-block": "^3.28.0", + "@tiptap/extension-code-block-lowlight": "^3.28.0", + "@tiptap/extension-document": "^3.28.0", + "@tiptap/extension-hard-break": "^3.28.0", + "@tiptap/extension-heading": "^3.28.0", + "@tiptap/extension-history": "^3.28.0", + "@tiptap/extension-horizontal-rule": "^3.28.0", + "@tiptap/extension-italic": "^3.28.0", + "@tiptap/extension-list": "^3.28.0", + "@tiptap/extension-list-item": "^3.28.0", + "@tiptap/extension-mention": "^3.28.0", + "@tiptap/extension-ordered-list": "^3.28.0", + "@tiptap/extension-paragraph": "^3.28.0", + "@tiptap/extension-strike": "^3.28.0", + "@tiptap/extension-subscript": "^3.28.0", + "@tiptap/extension-superscript": "^3.28.0", + "@tiptap/extension-table": "^3.28.0", + "@tiptap/extension-text": "^3.28.0", + "@tiptap/extension-text-align": "^3.28.0", + "@tiptap/extension-typography": "^3.28.0", + "@tiptap/extension-underline": "^3.28.0", + "@tiptap/extensions": "^3.28.0", + "@tiptap/pm": "^3.28.0", + "@tiptap/suggestion": "^3.28.0", + "@tiptap/vue-3": "^3.28.0", "alpinejs": "^3.1.1", "autosize": "~6.0.1", "axios": "^1.18.1", @@ -2968,35 +2968,36 @@ } }, "node_modules/@tiptap/extension-blockquote": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.22.3.tgz", - "integrity": "sha512-IaUx3zh7yLHXzIXKL+fw/jzFhsIImdhJyw0lMhe8FfYrefFqXJFYW/sey6+L/e8B3AWvTksPA6VBwefzbH77JA==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.28.0.tgz", + "integrity": "sha512-y8Xi6Z3AQLXedz0J8Z3kDsu7SKBmL50gKVXx3RK1Oo1cuCGhNChm3syChkAz3PLAbrPUM5rvJDSZdF2jUrDnng==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0" } }, "node_modules/@tiptap/extension-bold": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.22.3.tgz", - "integrity": "sha512-tysipHla2zCWr8XNIWRaW9O+7i7/SoEqnRqSRUUi2ailcJjlia+RBy3RykhkgyThrQDStu5KGBS/UvrXwA+O1A==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.28.0.tgz", + "integrity": "sha512-JhZQmr0AU741bOwjVMfuwJdK4g0TQwwPbeca9aqKHv5zvZw4i4G9G6fESVyMFc3Yag1ffpnq5EtNldHKTnMhlw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0" } }, "node_modules/@tiptap/extension-bubble-menu": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.22.3.tgz", - "integrity": "sha512-Y6zQjh0ypDg32HWgICEvmPSKjGLr39k3aDxxt/H0uQEZSfw4smT0hxUyyyjVjx68C6t6MTnwdfz0hPI5lL68vQ==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.28.0.tgz", + "integrity": "sha512-7AUNoHj2K4XLKCgW4uspeD/ENejPu2BeHvLTsiOoIO+XXDNSi2j0bbaIS8f2s/qnFirscwp/sbznp1qph/76qg==", "license": "MIT", "optional": true, "dependencies": { @@ -3007,97 +3008,97 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0" } }, "node_modules/@tiptap/extension-bullet-list": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.22.3.tgz", - "integrity": "sha512-xOmW/b1hgECIE6r3IeZvKn4VVlG3+dfTjCWE6lnnyLaqdNkNhKS1CwUmDZdYNLUS2ryIUtgz5ID1W/8A3PhbiA==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.28.0.tgz", + "integrity": "sha512-dSVuNiH7PFMmWGkNER9vfUb5bXUHDARvHbJ3l+APEb+ZiusVN1KFdM3nd/RsW/Rt5Gd3XwlIEU/c2Uf7cxYDcw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "^3.22.3" + "@tiptap/extension-list": "3.28.0" } }, "node_modules/@tiptap/extension-character-count": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-3.22.3.tgz", - "integrity": "sha512-MAs446aZWyAdVVSK+jkxoxR+N3n/ucMRCoenyekqdDDwkn4l3mDVC469X8Iby80/+66cRyHFFAbOivOoB8ds/g==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-3.28.0.tgz", + "integrity": "sha512-eKo/6e16ZiACrK/lbuj6BAqXb6IboG3RKQ9Z+2SPCV6KTpPGsFdIeqvBuYJsHPNeCtGufz1T9vpJbmgm+fcjqQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extensions": "^3.22.3" + "@tiptap/extensions": "3.28.0" } }, "node_modules/@tiptap/extension-code": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.22.3.tgz", - "integrity": "sha512-wafWTDQOuMKtXpZEuk1PFQmzopabBciNLryL90MB9S03MNLaQQZYLnmYkDBlzAaLAbgF5QiC+2XZQEBQuTVjFQ==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.28.0.tgz", + "integrity": "sha512-AUw2Acof3CQE6Q6Y22saK4lyg0nkeJ4XXniU65TdAi/9TE66TGiO4NQJJNNPgu8+VMEwl5j8VsIFK8sfTcNYtg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0" } }, "node_modules/@tiptap/extension-code-block": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.22.3.tgz", - "integrity": "sha512-RiQtEjDAPrHpdo6sw6b7fOw/PijqgFIsozKKkGcSeBgWHQuFg7q9OxJTj+l0e60rVwSu/5gmKEEobzM9bX+t2Q==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.28.0.tgz", + "integrity": "sha512-bHITDzT0umTLh+SiEVQzIXkCMt/TzbPM1+HQy9ZxgQDHAJj/vdmfNAnP3RCOrz4lETXyhNQ2b6kxeu3lWckxyA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0" } }, "node_modules/@tiptap/extension-code-block-lowlight": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-3.22.3.tgz", - "integrity": "sha512-NGFuD9zb1QfnCgD2zW4XaUEdQvd/ydm9FmXXh8eawx/+C8xt3p21DIKRERvxWrCvvVNzIUEpZRRYehPOJiD1eg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-3.28.0.tgz", + "integrity": "sha512-QJW8bJrYvf5BuLxgjGGa+Woncb23ud3egdExCUo+ChmvDp9AL7YKu7B7UU1DsohKPF4SJnzEb1fVsm3s2lhHlw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/extension-code-block": "^3.22.3", - "@tiptap/pm": "^3.22.3", + "@tiptap/core": "3.28.0", + "@tiptap/extension-code-block": "3.28.0", + "@tiptap/pm": "3.28.0", "highlight.js": "^11", "lowlight": "^2 || ^3" } }, "node_modules/@tiptap/extension-document": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.22.3.tgz", - "integrity": "sha512-MCSr1PFPtTd++lA3H1RNgqAczAE59XXJ5wUFIQf2F+/0DPY5q2SU4g5QsNJVxPPft5mrNT4C6ty8xBPrALFEdA==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.28.0.tgz", + "integrity": "sha512-5SAKtlB1Tr/eZFhISL86HqmUup6rItvPtuPyRjmZo/VgbMwXEwiyAh1sMgFp5VNaeSMM14vJSyQpjhSaOmaBqg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0" } }, "node_modules/@tiptap/extension-floating-menu": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.22.3.tgz", - "integrity": "sha512-0f8b4KZ3XKai8GXWseIYJGdOfQr3evtFbBo3U08zy2aYzMMXWG0zEF7qe5/oiYp2aZ95edjjITnEceviTsZkIg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.28.0.tgz", + "integrity": "sha512-59SECvJq3pQfeJBuydEdhQqrpl0oDlk8N1Ovs1Si3/fJa6rEQAJB2zIvcpBHky9Z+5JodI2eueDnv8eimiyGbg==", "license": "MIT", "optional": true, "funding": { @@ -3106,101 +3107,101 @@ }, "peerDependencies": { "@floating-ui/dom": "^1.0.0", - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0" } }, "node_modules/@tiptap/extension-hard-break": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.22.3.tgz", - "integrity": "sha512-J0v8I99y9tbvVmgKYKzKP/JYNsWaZYS7avn4rzLft2OhnyTfwt3OoY8DtpHmmi6apSUaCtoWHWta/TmoEfK1nQ==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.28.0.tgz", + "integrity": "sha512-JHIFjX1luJgQ4VFEkIeXZpbc54Qiw+69P8FmlazYTh7AIJ6iLCpMFgQFELnivEAZ+/vS0lMPQubg0rCeM3UrHw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0" } }, "node_modules/@tiptap/extension-heading": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.22.3.tgz", - "integrity": "sha512-XBHuhiEV2EEhZHpOLcplLqAmBIhJciU3I6AtwmqeEqDC0P114uMEfAO7JGlbBZdCYotNer26PKnu44TBTeNtkw==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.28.0.tgz", + "integrity": "sha512-rKjGG/Ik2lNaXBNVmONEDm5DBfGDLM1NWgSf5RRfBISrH8Lm1xqFruOB9tIaB0YUoSV3SBOiwTF9svmzvKSoRA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0" } }, "node_modules/@tiptap/extension-history": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-3.22.3.tgz", - "integrity": "sha512-3kev2iP84YPjwUk8BaM28EQiVHbBmE0lkcIjh/6yvgRxRwblzRwyJuFcsVOY5ouBVxl9Rg1rcFFCAs8CFNFkUg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-3.28.0.tgz", + "integrity": "sha512-ScmXXDuBvRdsYW9S6P11+uHvq73fQtCpRhCCK8Vysmqb+IUGIF+RzljEX3haXHZ61wh+XIqOxSedQAyjA2bcXA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extensions": "^3.22.3" + "@tiptap/extensions": "3.28.0" } }, "node_modules/@tiptap/extension-horizontal-rule": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.22.3.tgz", - "integrity": "sha512-wI2bFzScs+KgWeBH/BtypcVKeYelCyqV0RG8nxsZMWtPrBhqixzNd0Oi3gEKtjSjKUqMQ/kjJAIRuESr5UzlHA==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.28.0.tgz", + "integrity": "sha512-neBCMWprkppQUoLWyeJZDHqBw+xKX2oNhLD9MbFlhKIr092zgfP4mpCvQnSkn1OHl156KzZMp070+NyLBjgQ7A==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0" } }, "node_modules/@tiptap/extension-italic": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.22.3.tgz", - "integrity": "sha512-LteA4cb4EGCiUtrK2JHvDF/Zg0/YqV4DUyHhAAho+oGEQDupZlsS6m0ia5wQcclkiTLzsoPrwcSNu6RDGQ16wQ==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.28.0.tgz", + "integrity": "sha512-Yur/ELz6dNVKQC7m8wGjtoLFxamGjJmA0rUEEOacTH6J39aFmcSxYkEGNDkYHbZFyHFYqbej6eI3VE0h08O0mg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0" } }, "node_modules/@tiptap/extension-list": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.22.3.tgz", - "integrity": "sha512-rqvv/dtqwbX+8KnPv0eMYp6PnBcuhPMol5cv1GlS8Nq/Cxt68EWGUHBuTFesw+hdnRQLmKwzoO1DlRn7PhxYRQ==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.28.0.tgz", + "integrity": "sha512-zQ66i5DuhVOndmZ0d7H475Y5Yb+BMx+zfCjFkCzEc6WVef5MumtxBVTkGLQ0ibxUaD71s4QQZbjHWagpaTg0eQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0" } }, "node_modules/@tiptap/extension-list-item": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.22.3.tgz", - "integrity": "sha512-80CNf4oO5y8+LdckT4CyMe1t01EyhpRrQC9H45JW20P7559Nrchp5my3vvMtIAJbpTPPZtcB7LwdzWGKsG5drg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.28.0.tgz", + "integrity": "sha512-GbXrnMac6knSetZY33NHwOrgXFPqH28uCklQqmOZQlsrdGqezDKk31qoEMr4GsKW9n/lViAeuc07oIzwLwCMng==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "^3.22.3" + "@tiptap/extension-list": "3.28.0" } }, "node_modules/@tiptap/extension-mention": { @@ -3219,150 +3220,150 @@ } }, "node_modules/@tiptap/extension-ordered-list": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.22.3.tgz", - "integrity": "sha512-orAghtmd+K4Euu4BgI1hG+iZDXBYOyl5YTwiLBc2mQn+pqtZ9LqaH2us4ETwEwNP3/IWXGSAimUZ19nuL+eM2w==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.28.0.tgz", + "integrity": "sha512-OZNYrKKL9D01ySOujlNbKlLS9/3wGgKNYeoHZUg0e+Ta8WPVvL3W1zH6TgiU5sF2ZSGUBoq3w7mdaO/iROlUkw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "^3.22.3" + "@tiptap/extension-list": "3.28.0" } }, "node_modules/@tiptap/extension-paragraph": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.22.3.tgz", - "integrity": "sha512-oO7rhfyhEuwm+50s9K3GZPjYyEEEvFAvm1wXopvZnhbkBLydIWImBfrZoC5IQh4/sRDlTIjosV2C+ji5y0tUSg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.28.0.tgz", + "integrity": "sha512-8UMlQIjzqf6r2hSJ/VeJ00RqaOrOB1J5rTk02Vcw2pYeHkOqp+B0ff0HFu4abT9x1q4oXrBAgMsxRtgh/kR14Q==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0" } }, "node_modules/@tiptap/extension-strike": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.22.3.tgz", - "integrity": "sha512-jY2InoUlKkuk5KHoIDGdML1OCA2n6PRHAtxwHNkAmiYh0Khf0zaVPGFpx4dgQrN7W5Q1WE6oBZnjrvy6qb7w0g==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.28.0.tgz", + "integrity": "sha512-VVj2ZZU9QYqiHLcjqMqRYvuHSsokj/AgUl+6TzLrKjlWwyZ18D4H2vkyI0g70iVTKnUpZ3sEy8WA/rqjgBjqBg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0" } }, "node_modules/@tiptap/extension-subscript": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-3.22.3.tgz", - "integrity": "sha512-fcHinQMye6tPSxTB74CLW4Z/HbA9FX4epKOQ0ld0wIFsQB2bVz0NH9FfwrTEsYWLjeFWkDcL41VOReZXqKz1jQ==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-3.28.0.tgz", + "integrity": "sha512-lHROuWtzuMv3V4Q11j43Grqci5t0BAMbzIFinPyDSwxDfOCdpj0AwUS+TNNmi4sxZ49QwOXm/vzVMSSi2ChO5g==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0" } }, "node_modules/@tiptap/extension-superscript": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-3.22.3.tgz", - "integrity": "sha512-q+yPA+vbvaqiVGTJddjBhvWgqLXu6j39knz3TgJKw4nBuvoAXWrlWtgOy44wwIIs8vILI4wENoUuoI0+vOzynA==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-3.28.0.tgz", + "integrity": "sha512-19MWwQIoREjZsEQ7fa/6DUMx15GKZ+iPkOcSdm/PE2rKiDXhec0acWPZTZgH5dluW9GJOMilk7Xl7U8oBNGfjQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0" } }, "node_modules/@tiptap/extension-table": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-3.22.3.tgz", - "integrity": "sha512-inbQSusJad7H0T++L1APg/anfL5d15cNGp2YG3vwo6TQr71nn2c9pepvmz3xuAIt8eygZDRba+4GT/COP1f9QA==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-3.28.0.tgz", + "integrity": "sha512-SX2Uwn/bFyrqjbBo2Hg9wfLT/ofByHm4f80BuG0h5/m89Ve78CgtaK2lGz9jHZRM7rKTG6eqel+PUZQUwcSzOg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0" } }, "node_modules/@tiptap/extension-text": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.22.3.tgz", - "integrity": "sha512-Q9R7JsTdomP5uUjtPjNKxHT1xoh/i9OJZnmgJLe7FcgZEaPOQ3bWxmKZoLZQfDfZjyB8BtH+Hc7nUvhCMOePxw==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.28.0.tgz", + "integrity": "sha512-Jqp1LgfY1mnp4TQHoy+vnHyfn6qnnAM6nHgGwbY5zA8b/Xf1Etll6pziTx9p1J1qcfAgSrnjOyAmA++H7VQqww==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0" } }, "node_modules/@tiptap/extension-text-align": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.22.3.tgz", - "integrity": "sha512-dG1NHE0yGf7fYiOdabCJuecI2IJ1uogyY/QvZqvPNaxRjZDoXYuGlMtz9jEDiIdQSaPED2MSsS7KkuNFQIEMGg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.28.0.tgz", + "integrity": "sha512-P+KQMTtCpLFq9dRvy/fbyu2BGALulT/1HtsLbpFG4tKATaKa+cFnJ7rLZF7b3AdAMDNtXdnivxAtixAMBt2QWw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0" } }, "node_modules/@tiptap/extension-typography": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-typography/-/extension-typography-3.22.3.tgz", - "integrity": "sha512-QQv1KUHsr3LTGhtPIG/ZP15Cj5VmI5ChgK4Q7UZ7JZBppBKAeMnyAnOcO2ith+qZ8qp5s+UiQ7Unp0zP1VSYkg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-typography/-/extension-typography-3.28.0.tgz", + "integrity": "sha512-6iwxXqtT6HuFv9LYrpT7YV6TpIauWBXCGGs3liHPrY4P7aMwpRmC3QTbgq655PMpjV8xMT6S2Wa6cQi2CNrXmQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0" } }, "node_modules/@tiptap/extension-underline": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.22.3.tgz", - "integrity": "sha512-Ch6CBWRa5w90yYSPUW6x9Py9JdrXMqk3pZ9OIlMYD8A7BqyZGfiHerX7XDMYDS09KjyK3U9XH60/zxYOzXdDLA==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.28.0.tgz", + "integrity": "sha512-rwxCS6vTh2DJkNIYQX7JSrrRSmm76e2y48aZeuZO5ShkvLWMuJ3/zqDHRxAQVDiJhuE2Cp8NrTmPYlUc9YrT0A==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3" + "@tiptap/core": "3.28.0" } }, "node_modules/@tiptap/extensions": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.3.tgz", - "integrity": "sha512-s5eiMq0m5N6N+W7dU6rd60KgZyyCD7FvtPNNswISfPr12EQwJBfbjWwTqd0UKNzA4fNrhQEERXnzORkykttPeA==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.28.0.tgz", + "integrity": "sha512-DJT1khCK+O/pT1gQlAnoKAx6zwDkgv7GtnhSfkqm1/4KHC3x+SSJnBIgBl9oGHymA+DxWbW1EULU4V3d/kT2uQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0" } }, "node_modules/@tiptap/pm": { @@ -3406,22 +3407,22 @@ } }, "node_modules/@tiptap/vue-3": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-3.22.3.tgz", - "integrity": "sha512-wOGZiBwIJYCZXts5VWWGgseGCRMc8tO46tgaOXNyMLVl2h2cO6/CNEcPO2pgtuoLIHoV4KYvfpw6n+XqR9cqbA==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-3.28.0.tgz", + "integrity": "sha512-twb3T6hofM0ajncCSgrLYIjfn86WfR2NVXteurdNOcnkJCIMTij5OhO/xn0z7y8+lp3TMkF6H5kajUpivBpnzw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "optionalDependencies": { - "@tiptap/extension-bubble-menu": "^3.22.3", - "@tiptap/extension-floating-menu": "^3.22.3" + "@tiptap/extension-bubble-menu": "^3.28.0", + "@tiptap/extension-floating-menu": "^3.28.0" }, "peerDependencies": { "@floating-ui/dom": "^1.0.0", - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3", + "@tiptap/core": "3.28.0", + "@tiptap/pm": "3.28.0", "vue": "^3.0.0" } }, diff --git a/package.json b/package.json index 2d19403804..cd921df4d3 100644 --- a/package.json +++ b/package.json @@ -27,37 +27,37 @@ "@internationalized/date": "^3.7.0", "@shopify/draggable": "^1.0.0-beta.12", "@simplewebauthn/browser": "^13.2.2", - "@tiptap/core": "^3.0.0", - "@tiptap/extension-blockquote": "^3.0.0", - "@tiptap/extension-bold": "^3.0.0", - "@tiptap/extension-bullet-list": "^3.0.0", - "@tiptap/extension-character-count": "^3.0.0", - "@tiptap/extension-code": "^3.0.0", - "@tiptap/extension-code-block": "^3.0.0", - "@tiptap/extension-code-block-lowlight": "^3.0.0", - "@tiptap/extension-document": "^3.0.0", - "@tiptap/extension-hard-break": "^3.0.0", - "@tiptap/extension-heading": "^3.0.0", - "@tiptap/extension-history": "^3.0.0", - "@tiptap/extension-horizontal-rule": "^3.0.0", - "@tiptap/extension-italic": "^3.0.0", - "@tiptap/extension-list": "^3.0.0", - "@tiptap/extension-list-item": "^3.0.0", - "@tiptap/extension-mention": "^3.0.0", - "@tiptap/extension-ordered-list": "^3.0.0", - "@tiptap/extension-paragraph": "^3.0.0", - "@tiptap/extension-strike": "^3.0.0", - "@tiptap/extension-subscript": "^3.0.0", - "@tiptap/extension-superscript": "^3.0.0", - "@tiptap/extension-table": "^3.0.0", - "@tiptap/extension-text": "^3.0.0", - "@tiptap/extension-text-align": "^3.0.0", - "@tiptap/extension-typography": "^3.0.0", - "@tiptap/extension-underline": "^3.0.0", - "@tiptap/extensions": "^3.0.0", - "@tiptap/pm": "^3.0.0", - "@tiptap/suggestion": "^3.0.0", - "@tiptap/vue-3": "^3.0.0", + "@tiptap/core": "^3.28.0", + "@tiptap/extension-blockquote": "^3.28.0", + "@tiptap/extension-bold": "^3.28.0", + "@tiptap/extension-bullet-list": "^3.28.0", + "@tiptap/extension-character-count": "^3.28.0", + "@tiptap/extension-code": "^3.28.0", + "@tiptap/extension-code-block": "^3.28.0", + "@tiptap/extension-code-block-lowlight": "^3.28.0", + "@tiptap/extension-document": "^3.28.0", + "@tiptap/extension-hard-break": "^3.28.0", + "@tiptap/extension-heading": "^3.28.0", + "@tiptap/extension-history": "^3.28.0", + "@tiptap/extension-horizontal-rule": "^3.28.0", + "@tiptap/extension-italic": "^3.28.0", + "@tiptap/extension-list": "^3.28.0", + "@tiptap/extension-list-item": "^3.28.0", + "@tiptap/extension-mention": "^3.28.0", + "@tiptap/extension-ordered-list": "^3.28.0", + "@tiptap/extension-paragraph": "^3.28.0", + "@tiptap/extension-strike": "^3.28.0", + "@tiptap/extension-subscript": "^3.28.0", + "@tiptap/extension-superscript": "^3.28.0", + "@tiptap/extension-table": "^3.28.0", + "@tiptap/extension-text": "^3.28.0", + "@tiptap/extension-text-align": "^3.28.0", + "@tiptap/extension-typography": "^3.28.0", + "@tiptap/extension-underline": "^3.28.0", + "@tiptap/extensions": "^3.28.0", + "@tiptap/pm": "^3.28.0", + "@tiptap/suggestion": "^3.28.0", + "@tiptap/vue-3": "^3.28.0", "alpinejs": "^3.1.1", "autosize": "~6.0.1", "axios": "^1.18.1", From 61f92d492edf937c4bb95c41fc45da5498291823 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 22 Jul 2026 13:22:51 -0400 Subject: [PATCH 08/14] Export AutocompleteEditor from @statamic/cms ui module Co-Authored-By: Claude --- packages/cms/src/ui.js | 1 + resources/js/bootstrap/cms/ui.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/cms/src/ui.js b/packages/cms/src/ui.js index 3f5150061c..d8c18b5057 100644 --- a/packages/cms/src/ui.js +++ b/packages/cms/src/ui.js @@ -1,6 +1,7 @@ export const { Alert, AuthCard, + AutocompleteEditor, Avatar, Badge, Button, diff --git a/resources/js/bootstrap/cms/ui.js b/resources/js/bootstrap/cms/ui.js index 68653dd374..80635af524 100644 --- a/resources/js/bootstrap/cms/ui.js +++ b/resources/js/bootstrap/cms/ui.js @@ -1,6 +1,7 @@ export { Alert, AuthCard, + AutocompleteEditor, Avatar, Badge, Button, From 91c4db3dae0f6b8f6fb51cf8a22a337742acdd22 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 22 Jul 2026 13:32:06 -0400 Subject: [PATCH 09/14] Add PHPUnit tests for AutocompleteEditor fieldtype Cover selectable(), config defaults/overrides, process/preProcess round-tripping for block and inline modes, broken node filtering, and preload() options normalization. Co-Authored-By: Claude --- tests/Fieldtypes/AutocompleteEditorTest.php | 278 ++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 tests/Fieldtypes/AutocompleteEditorTest.php diff --git a/tests/Fieldtypes/AutocompleteEditorTest.php b/tests/Fieldtypes/AutocompleteEditorTest.php new file mode 100644 index 0000000000..c6f94875a5 --- /dev/null +++ b/tests/Fieldtypes/AutocompleteEditorTest.php @@ -0,0 +1,278 @@ +assertFalse((new AutocompleteEditor)->selectable()); + } + + #[Test] + public function it_has_expected_config_defaults() + { + $fieldtype = $this->fieldtype(); + + $this->assertSame('@', $fieldtype->config('trigger')); + $this->assertNull($fieldtype->config('placeholder')); + $this->assertFalse($fieldtype->config('inline')); + $this->assertFalse($fieldtype->config('enable_line_breaks')); + $this->assertSame( + ['bold', 'italic', 'h2', 'h3', 'bulletlist', 'orderedlist'], + $fieldtype->config('buttons') + ); + } + + #[Test] + public function config_values_can_be_overridden() + { + $fieldtype = $this->fieldtype([ + 'trigger' => '#', + 'inline' => true, + 'enable_line_breaks' => true, + 'buttons' => ['bold'], + ]); + + $this->assertSame('#', $fieldtype->config('trigger')); + $this->assertTrue($fieldtype->config('inline')); + $this->assertTrue($fieldtype->config('enable_line_breaks')); + $this->assertSame(['bold'], $fieldtype->config('buttons')); + } + + #[Test] + public function it_processes_null_and_empty_values_to_null() + { + $fieldtype = $this->fieldtype(); + + $this->assertNull($fieldtype->process(null)); + $this->assertNull($fieldtype->process([])); + } + + #[Test] + public function it_processes_a_lone_empty_paragraph_to_null_in_block_mode() + { + $fieldtype = $this->fieldtype(); + + $this->assertNull($fieldtype->process([['type' => 'paragraph']])); + } + + #[Test] + public function it_processes_block_content_unchanged() + { + $fieldtype = $this->fieldtype(); + + $value = [ + [ + 'type' => 'paragraph', + 'content' => [ + ['type' => 'text', 'text' => 'Hello'], + ], + ], + ]; + + $this->assertSame($value, $fieldtype->process($value)); + } + + #[Test] + public function it_preprocesses_block_content_unchanged() + { + $fieldtype = $this->fieldtype(); + + $value = [ + [ + 'type' => 'paragraph', + 'content' => [ + ['type' => 'text', 'text' => 'Hello'], + ], + ], + ]; + + $this->assertSame($value, $fieldtype->preProcess($value)); + } + + #[Test] + public function it_round_trips_block_content() + { + $fieldtype = $this->fieldtype(); + + $value = [ + [ + 'type' => 'paragraph', + 'content' => [ + ['type' => 'text', 'text' => 'Hello'], + ], + ], + ]; + + $this->assertSame($value, $fieldtype->preProcess($fieldtype->process($value))); + } + + #[Test] + public function it_unwraps_a_single_paragraph_when_processing_inline_content() + { + $fieldtype = $this->fieldtype(['inline' => true]); + + $value = [ + [ + 'type' => 'paragraph', + 'content' => [ + ['type' => 'text', 'text' => 'Hello'], + ], + ], + ]; + + $this->assertSame( + [['type' => 'text', 'text' => 'Hello']], + $fieldtype->process($value) + ); + } + + #[Test] + public function it_processes_an_empty_inline_paragraph_to_null() + { + $fieldtype = $this->fieldtype(['inline' => true]); + + $this->assertNull($fieldtype->process([['type' => 'paragraph']])); + } + + #[Test] + public function it_wraps_flat_inline_content_in_a_paragraph_when_preprocessing() + { + $fieldtype = $this->fieldtype(['inline' => true]); + + $stored = [['type' => 'text', 'text' => 'Hello']]; + + $this->assertSame( + [ + [ + 'type' => 'paragraph', + 'content' => [ + ['type' => 'text', 'text' => 'Hello'], + ], + ], + ], + $fieldtype->preProcess($stored) + ); + } + + #[Test] + public function it_round_trips_inline_content() + { + $fieldtype = $this->fieldtype(['inline' => true]); + + $stored = [['type' => 'text', 'text' => 'Hello']]; + + $this->assertSame( + $stored, + $fieldtype->process($fieldtype->preProcess($stored)) + ); + } + + #[Test] + public function preprocess_removes_nodes_missing_a_type() + { + $fieldtype = $this->fieldtype(); + + $value = [ + [ + 'type' => 'paragraph', + 'content' => [ + ['type' => 'text', 'text' => 'Hello'], + ], + ], + ['no' => 'type'], + ]; + + $this->assertSame( + [ + [ + 'type' => 'paragraph', + 'content' => [ + ['type' => 'text', 'text' => 'Hello'], + ], + ], + ], + $fieldtype->preProcess($value) + ); + } + + #[Test] + public function preprocess_returns_empty_array_for_empty_value() + { + $fieldtype = $this->fieldtype(); + + $this->assertSame([], $fieldtype->preProcess([])); + $this->assertSame([], $fieldtype->preProcess(null)); + } + + #[Test] + public function preload_normalizes_a_list_of_plain_string_options() + { + $fieldtype = $this->fieldtype([ + 'options' => ['red', 'green'], + ]); + + $this->assertSame( + [ + ['value' => 'red', 'label' => 'red'], + ['value' => 'green', 'label' => 'green'], + ], + $fieldtype->preload()['options'] + ); + } + + #[Test] + public function preload_normalizes_an_associative_array_of_options() + { + $fieldtype = $this->fieldtype([ + 'options' => ['red' => 'Red', 'green' => 'Green'], + ]); + + $this->assertSame( + [ + ['value' => 'red', 'label' => 'Red'], + ['value' => 'green', 'label' => 'Green'], + ], + $fieldtype->preload()['options'] + ); + } + + #[Test] + public function preload_normalizes_key_value_rows_from_the_array_fieldtype() + { + $fieldtype = $this->fieldtype([ + 'options' => [ + ['key' => 'red', 'value' => 'Red'], + ['key' => 'green', 'value' => 'Green'], + ], + ]); + + $this->assertSame( + [ + ['value' => 'red', 'label' => 'Red'], + ['value' => 'green', 'label' => 'Green'], + ], + $fieldtype->preload()['options'] + ); + } + + #[Test] + public function preload_returns_an_empty_options_array_when_none_are_configured() + { + $fieldtype = $this->fieldtype(); + + $this->assertSame([], $fieldtype->preload()['options']); + } + + private function fieldtype($config = []) + { + return (new AutocompleteEditor)->setField(new Field('test', array_merge(['type' => 'autocomplete_editor'], $config))); + } +} From fa0a56912fe2006df09455dff942318175fc24a1 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 22 Jul 2026 13:32:10 -0400 Subject: [PATCH 10/14] Add Vitest tests for AutocompleteEditor primitive Cover toolbar rendering per buttons config, inline hiding the toolbar, Enter/line-break behavior in inline and block modes, mention node insertion, update:modelValue emitting the PM content array, and suggestion item filtering. Also add AutocompleteEditor to Package.test.js's ui export parity list, which was missed when the component was exported. Co-Authored-By: Claude --- resources/js/tests/Package.test.js | 1 + .../components/ui/AutocompleteEditor.test.js | 151 ++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 resources/js/tests/components/ui/AutocompleteEditor.test.js diff --git a/resources/js/tests/Package.test.js b/resources/js/tests/Package.test.js index 4da3ead238..21345897d1 100644 --- a/resources/js/tests/Package.test.js +++ b/resources/js/tests/Package.test.js @@ -124,6 +124,7 @@ it('exports ui', async () => { const expectedCmsPackageExports = [ 'Alert', 'AuthCard', + 'AutocompleteEditor', 'Badge', 'Button', 'ButtonGroup', diff --git a/resources/js/tests/components/ui/AutocompleteEditor.test.js b/resources/js/tests/components/ui/AutocompleteEditor.test.js new file mode 100644 index 0000000000..b31b27bb18 --- /dev/null +++ b/resources/js/tests/components/ui/AutocompleteEditor.test.js @@ -0,0 +1,151 @@ +import { mount } from '@vue/test-utils'; +import { expect, test } from 'vitest'; +import AutocompleteEditor from '@/components/ui/AutocompleteEditor/AutocompleteEditor.vue'; + +window.__ = (key) => key; + +const sampleOptions = [ + { value: 'jason', label: 'Jason Varga' }, + { value: 'jesse', label: 'Jesse Leite' }, +]; + +async function mountEditor(props = {}) { + const wrapper = mount(AutocompleteEditor, { + props: { modelValue: [], options: sampleOptions, ...props }, + }); + + // useEditor() initializes the tiptap instance asynchronously, so the + // ProseMirror DOM isn't guaranteed to exist after a single tick. + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + return wrapper; +} + +function pressEnter(dom) { + dom.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true })); +} + +test('renders a toolbar button for each configured button', async () => { + const wrapper = await mountEditor({ buttons: ['bold', 'h2'] }); + + const labels = wrapper.findAll('button').map((button) => button.attributes('aria-label')); + + expect(labels).toEqual(['Bold', 'Heading 2']); +}); + +test('unknown button keys are ignored', async () => { + const wrapper = await mountEditor({ buttons: ['bold', 'not-a-real-button'] }); + + const labels = wrapper.findAll('button').map((button) => button.attributes('aria-label')); + + expect(labels).toEqual(['Bold']); +}); + +test('inline mode hides the toolbar', async () => { + const wrapper = await mountEditor({ inline: true, buttons: ['bold', 'italic'] }); + + expect(wrapper.findAll('button')).toHaveLength(0); +}); + +test('enableLineBreaks false blocks Enter in inline mode', async () => { + const wrapper = await mountEditor({ inline: true, enableLineBreaks: false }); + const { editor } = wrapper.vm; + + editor.commands.insertContent('hello'); + editor.commands.focus('end'); + pressEnter(wrapper.find('.ProseMirror').element); + await wrapper.vm.$nextTick(); + + expect(editor.getJSON().content).toEqual([ + { type: 'paragraph', content: [{ type: 'text', text: 'hello' }] }, + ]); +}); + +test('enableLineBreaks true inserts a hard break in inline mode', async () => { + const wrapper = await mountEditor({ inline: true, enableLineBreaks: true }); + const { editor } = wrapper.vm; + + editor.commands.insertContent('hello'); + editor.commands.focus('end'); + pressEnter(wrapper.find('.ProseMirror').element); + await wrapper.vm.$nextTick(); + + expect(editor.getJSON().content).toEqual([ + { + type: 'paragraph', + content: [{ type: 'text', text: 'hello' }, { type: 'hardBreak' }], + }, + ]); +}); + +test('enableLineBreaks false blocks Enter in block mode', async () => { + const wrapper = await mountEditor({ inline: false, enableLineBreaks: false }); + const { editor } = wrapper.vm; + + editor.commands.insertContent('hello'); + editor.commands.focus('end'); + pressEnter(wrapper.find('.ProseMirror').element); + await wrapper.vm.$nextTick(); + + expect(editor.getJSON().content).toEqual([ + { type: 'paragraph', content: [{ type: 'text', text: 'hello' }] }, + ]); +}); + +test('enableLineBreaks true splits into a new paragraph in block mode', async () => { + const wrapper = await mountEditor({ inline: false, enableLineBreaks: true }); + const { editor } = wrapper.vm; + + editor.commands.insertContent('hello'); + editor.commands.focus('end'); + pressEnter(wrapper.find('.ProseMirror').element); + await wrapper.vm.$nextTick(); + + expect(editor.getJSON().content).toEqual([ + { type: 'paragraph', content: [{ type: 'text', text: 'hello' }] }, + { type: 'paragraph' }, + ]); +}); + +test('inserting a mention produces a node with value and label attrs', async () => { + const wrapper = await mountEditor(); + const { editor } = wrapper.vm; + + editor.commands.insertContent({ type: 'mention', attrs: { value: 'jesse', label: 'Jesse Leite' } }); + + expect(editor.getJSON().content).toEqual([ + { + type: 'paragraph', + content: [{ type: 'mention', attrs: { value: 'jesse', label: 'Jesse Leite' } }], + }, + ]); +}); + +test('update:modelValue emits the ProseMirror content array', async () => { + const wrapper = await mountEditor(); + const { editor } = wrapper.vm; + + editor.commands.insertContent('hello'); + await wrapper.vm.$nextTick(); + + const emitted = wrapper.emitted('update:modelValue'); + + expect(emitted).toBeTruthy(); + expect(emitted.at(-1)[0]).toEqual([ + { type: 'paragraph', content: [{ type: 'text', text: 'hello' }] }, + ]); +}); + +test('suggestion items are filtered by query against the option label', async () => { + const wrapper = await mountEditor(); + const { editor } = wrapper.vm; + + const mention = editor.extensionManager.extensions.find((extension) => extension.name === 'mention'); + + expect(mention.options.suggestion.items({ query: 'jes' })).toEqual([ + { value: 'jesse', label: 'Jesse Leite' }, + ]); + expect(mention.options.suggestion.items({ query: '' })).toEqual(sampleOptions); + expect(mention.options.suggestion.items({ query: 'zzz' })).toEqual([]); +}); From 8f06a54edf38d7b229bff54a94ab15c0aa31aa0b Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 22 Jul 2026 13:38:19 -0400 Subject: [PATCH 11/14] Register AutocompleteEditor fieldtype Was implemented but never added to the fieldtypes registry, so Statamic couldn't resolve it (FieldtypeNotFoundException) when used in a blueprint. Co-Authored-By: Claude --- src/Providers/ExtensionServiceProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Providers/ExtensionServiceProvider.php b/src/Providers/ExtensionServiceProvider.php index 5371de78bc..009f412449 100644 --- a/src/Providers/ExtensionServiceProvider.php +++ b/src/Providers/ExtensionServiceProvider.php @@ -66,6 +66,7 @@ class ExtensionServiceProvider extends ServiceProvider Fieldtypes\AssetContainer::class, Fieldtypes\AssetFolder::class, Fieldtypes\Assets\Assets::class, + Fieldtypes\AutocompleteEditor::class, Fieldtypes\Bard::class, Fieldtypes\Bard\Buttons::class, Fieldtypes\Blueprints::class, From 2032a59ab96517acaae4ebe14f618adf06ae49d0 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 22 Jul 2026 14:41:13 -0400 Subject: [PATCH 12/14] Fix AutocompleteEditor UI to match Bard treatment - enable_line_breaks only gates Enter in inline mode; block mode always splits into a new paragraph on Enter, matching Bard. The config field is now conditional on inline, like Bard's equivalent. - Focus ring now wraps the content area (below the toolbar) with padding inside it, instead of hugging the unpadded ProseMirror element with padding rendered outside the ring. - Toolbar background is gray to match Bard's fixed toolbar instead of blending into the white field background. Co-Authored-By: Claude --- .../AutocompleteEditor/AutocompleteEditor.vue | 24 +++++++++++++++---- .../components/ui/AutocompleteEditor.test.js | 3 ++- src/Fieldtypes/AutocompleteEditor.php | 1 + 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue index 801be2f583..5a9f5f3b8f 100644 --- a/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue +++ b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue @@ -3,11 +3,16 @@ class="autocomplete-editor shadow-ui-sm rounded-lg border border-gray-300 bg-white dark:border-gray-700 dark:bg-gray-900" :class="{ 'border-dashed': readOnly }" > -
+
- +
+ +
@@ -103,7 +108,7 @@ function resolveOptions({ query }) { } function keyboardExtension() { - const disableEnter = !props.enableLineBreaks; + const disableEnter = props.inline && !props.enableLineBreaks; return Extension.create({ name: 'autocompleteEditorKeymap', @@ -145,8 +150,10 @@ function getExtensions() { keyboardExtension(), ]; - if (props.enableLineBreaks) { - extensions.push(props.inline ? inlineHardBreak() : HardBreak); + if (props.inline) { + if (props.enableLineBreaks) extensions.push(inlineHardBreak()); + } else { + extensions.push(HardBreak); } if (props.placeholder) extensions.push(Placeholder.configure({ placeholder: props.placeholder })); @@ -228,3 +235,10 @@ watch( (readOnly) => editor.value?.setEditable(!readOnly), ); + + diff --git a/resources/js/tests/components/ui/AutocompleteEditor.test.js b/resources/js/tests/components/ui/AutocompleteEditor.test.js index b31b27bb18..a88b5144b3 100644 --- a/resources/js/tests/components/ui/AutocompleteEditor.test.js +++ b/resources/js/tests/components/ui/AutocompleteEditor.test.js @@ -79,7 +79,7 @@ test('enableLineBreaks true inserts a hard break in inline mode', async () => { ]); }); -test('enableLineBreaks false blocks Enter in block mode', async () => { +test('enableLineBreaks false still splits into a new paragraph in block mode', async () => { const wrapper = await mountEditor({ inline: false, enableLineBreaks: false }); const { editor } = wrapper.vm; @@ -90,6 +90,7 @@ test('enableLineBreaks false blocks Enter in block mode', async () => { expect(editor.getJSON().content).toEqual([ { type: 'paragraph', content: [{ type: 'text', text: 'hello' }] }, + { type: 'paragraph' }, ]); }); diff --git a/src/Fieldtypes/AutocompleteEditor.php b/src/Fieldtypes/AutocompleteEditor.php index 853b0ea787..eff5165464 100644 --- a/src/Fieldtypes/AutocompleteEditor.php +++ b/src/Fieldtypes/AutocompleteEditor.php @@ -68,6 +68,7 @@ protected function configFieldItems(): array 'type' => 'toggle', 'default' => false, 'width' => 50, + 'if' => ['inline' => 'equals true'], ], ]; } From 6480d01808035c55e95004e96bc18cceed249bfe Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 22 Jul 2026 14:43:55 -0400 Subject: [PATCH 13/14] Render mention as a Badge instead of plain text Add a Vue NodeView for the mention node using VueNodeViewRenderer, pointing at a new MentionBadge component that wraps the label in the existing Badge UI component. Schema/attrs and getJSON output are unchanged. Co-Authored-By: Claude --- .../ui/AutocompleteEditor/MentionBadge.vue | 12 ++++++++++++ .../ui/AutocompleteEditor/extensions/Autocomplete.js | 7 ++++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 resources/js/components/ui/AutocompleteEditor/MentionBadge.vue diff --git a/resources/js/components/ui/AutocompleteEditor/MentionBadge.vue b/resources/js/components/ui/AutocompleteEditor/MentionBadge.vue new file mode 100644 index 0000000000..0607ec69fc --- /dev/null +++ b/resources/js/components/ui/AutocompleteEditor/MentionBadge.vue @@ -0,0 +1,12 @@ + + + diff --git a/resources/js/components/ui/AutocompleteEditor/extensions/Autocomplete.js b/resources/js/components/ui/AutocompleteEditor/extensions/Autocomplete.js index cf03ca02ce..d07b14ee6f 100644 --- a/resources/js/components/ui/AutocompleteEditor/extensions/Autocomplete.js +++ b/resources/js/components/ui/AutocompleteEditor/extensions/Autocomplete.js @@ -1,5 +1,6 @@ import Mention from '@tiptap/extension-mention'; -import { VueRenderer } from '@tiptap/vue-3'; +import { VueNodeViewRenderer, VueRenderer } from '@tiptap/vue-3'; +import MentionBadge from '../MentionBadge.vue'; import SuggestionList from '../SuggestionList.vue'; function renderSuggestion() { @@ -41,6 +42,10 @@ function renderSuggestion() { export const Autocomplete = Mention.extend({ name: 'mention', + addNodeView() { + return VueNodeViewRenderer(MentionBadge); + }, + addOptions() { return { ...this.parent?.(), From 8fe2f18573f58ce3292997b4664b165520f068a7 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 22 Jul 2026 15:00:19 -0400 Subject: [PATCH 14/14] Fix AutocompleteEditor mention badge alignment and toolbar focus ring - MentionBadge: vertically center the inline badge with surrounding text using inline-flex + align-middle instead of sitting on the line's baseline. - AutocompleteEditor: when the toolbar is visible, square off the top corners of the content's focus ring so it meets the toolbar's bottom edge cleanly, while keeping the full 4-sided ring (matching Bard). Inline mode (no toolbar) keeps the full rounded ring. Co-Authored-By: Claude --- .../components/ui/AutocompleteEditor/AutocompleteEditor.vue | 5 ++++- .../js/components/ui/AutocompleteEditor/MentionBadge.vue | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue index 5a9f5f3b8f..25ef27bb55 100644 --- a/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue +++ b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue @@ -10,7 +10,10 @@
-
+
diff --git a/resources/js/components/ui/AutocompleteEditor/MentionBadge.vue b/resources/js/components/ui/AutocompleteEditor/MentionBadge.vue index 0607ec69fc..6711c19498 100644 --- a/resources/js/components/ui/AutocompleteEditor/MentionBadge.vue +++ b/resources/js/components/ui/AutocompleteEditor/MentionBadge.vue @@ -6,7 +6,7 @@ const props = defineProps(nodeViewProps);