diff --git a/lang/en/fieldtypes.php b/lang/en/fieldtypes.php index df2ed07b900..0fa96e9b1f0 100644 --- a/lang/en/fieldtypes.php +++ b/lang/en/fieldtypes.php @@ -23,6 +23,13 @@ 'assets.dynamic_folder_pending_save' => 'This field will be available after saving.', 'assets.title' => 'Assets', 'asset_folders.config.container' => 'Choose which asset container to use for this field.', + 'autocomplete_editor.config.buttons' => 'Choose which buttons to show in the toolbar.', + 'autocomplete_editor.config.enable_line_breaks' => 'Allows adding line breaks to the content.', + 'autocomplete_editor.config.inline' => 'Disable all block-level elements like headings and lists.', + 'autocomplete_editor.config.options' => 'Set the values and their optional labels available for autocompletion.', + 'autocomplete_editor.config.placeholder' => 'Text to display when the field is empty.', + 'autocomplete_editor.config.trigger' => 'The character that triggers the autocomplete suggestions.', + 'autocomplete_editor.title' => 'Autocomplete Editor', 'bard.config.allow_source' => 'Enable to view the HTML source code while writing.', 'bard.config.always_show_set_button' => 'Enable to always show the "Add Set" button.', 'bard.config.buttons' => 'Choose which buttons to show in the toolbar.', diff --git a/package-lock.json b/package-lock.json index 91aa419dcb8..54793f2842f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,35 +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-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/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", @@ -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,48 +2955,49 @@ } }, "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": { - "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": { @@ -3035,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": { @@ -3134,297 +3107,322 @@ }, "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": { + "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", - "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": { - "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", - "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" } }, @@ -3481,12 +3479,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 +3494,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 +4326,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 +4770,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 +5332,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 +6583,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 +6637,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 +6664,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 +7337,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 +7411,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 +7455,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 +7465,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 +7636,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 +8699,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 ceb6142c955..cd921df4d3d 100644 --- a/package.json +++ b/package.json @@ -27,35 +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-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/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", diff --git a/packages/cms/src/ui.js b/packages/cms/src/ui.js index 3f5150061c9..d8c18b5057e 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 68653dd3746..80635af5242 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, diff --git a/resources/js/bootstrap/fieldtypes.js b/resources/js/bootstrap/fieldtypes.js index 5cb99a0c35e..0a25f470291 100644 --- a/resources/js/bootstrap/fieldtypes.js +++ b/resources/js/bootstrap/fieldtypes.js @@ -9,6 +9,7 @@ import ArrayFieldtype from '../components/fieldtypes/ArrayFieldtype.vue'; import AssetsFieldtype from '../components/fieldtypes/assets/AssetsFieldtype.vue'; import AssetsIndexFieldtype from '../components/fieldtypes/assets/AssetsIndexFieldtype.vue'; import AssetFolderFieldtype from '../components/fieldtypes/AssetFolderFieldtype.vue'; +import AutocompleteEditorFieldtype from '../components/fieldtypes/AutocompleteEditorFieldtype.vue'; import BardButtonsSettingFieldtype from '../components/fieldtypes/bard/BardButtonsSettingFieldtype.vue'; import BlueprintsFieldtype from '../components/fieldtypes/BlueprintsFieldtype.vue'; import ButtonGroupFieldtype from '../components/fieldtypes/ButtonGroupFieldtype.vue'; @@ -71,6 +72,7 @@ export default function registerFieldtypes(app) { app.component('assets-fieldtype', AssetsFieldtype); app.component('assets-fieldtype-index', AssetsIndexFieldtype); app.component('asset_folder-fieldtype', AssetFolderFieldtype); + app.component('autocomplete_editor-fieldtype', AutocompleteEditorFieldtype); app.component( 'bard-fieldtype', defineAsyncComponent(() => import('../components/fieldtypes/bard/BardFieldtype.vue')), diff --git a/resources/js/components/fieldtypes/AutocompleteEditorFieldtype.vue b/resources/js/components/fieldtypes/AutocompleteEditorFieldtype.vue new file mode 100644 index 00000000000..048324e2f86 --- /dev/null +++ b/resources/js/components/fieldtypes/AutocompleteEditorFieldtype.vue @@ -0,0 +1,22 @@ + + + diff --git a/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue new file mode 100644 index 00000000000..25ef27bb55b --- /dev/null +++ b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditor.vue @@ -0,0 +1,247 @@ + + + + + diff --git a/resources/js/components/ui/AutocompleteEditor/AutocompleteEditorToolbar.vue b/resources/js/components/ui/AutocompleteEditor/AutocompleteEditorToolbar.vue new file mode 100644 index 00000000000..b48a6f708ab --- /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 00000000000..b7781242c40 --- /dev/null +++ b/resources/js/components/ui/AutocompleteEditor/EditorToolbarButton.vue @@ -0,0 +1,25 @@ + + + diff --git a/resources/js/components/ui/AutocompleteEditor/MentionBadge.vue b/resources/js/components/ui/AutocompleteEditor/MentionBadge.vue new file mode 100644 index 00000000000..6711c194981 --- /dev/null +++ b/resources/js/components/ui/AutocompleteEditor/MentionBadge.vue @@ -0,0 +1,12 @@ + + + diff --git a/resources/js/components/ui/AutocompleteEditor/SuggestionList.vue b/resources/js/components/ui/AutocompleteEditor/SuggestionList.vue new file mode 100644 index 00000000000..ecd2047642d --- /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 00000000000..d07b14ee6f6 --- /dev/null +++ b/resources/js/components/ui/AutocompleteEditor/extensions/Autocomplete.js @@ -0,0 +1,79 @@ +import Mention from '@tiptap/extension-mention'; +import { VueNodeViewRenderer, VueRenderer } from '@tiptap/vue-3'; +import MentionBadge from '../MentionBadge.vue'; +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', + + addNodeView() { + return VueNodeViewRenderer(MentionBadge); + }, + + 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 } : {}), + }, + }; + }, +}); diff --git a/resources/js/components/ui/AutocompleteEditor/extensions/Document.js b/resources/js/components/ui/AutocompleteEditor/extensions/Document.js new file mode 100644 index 00000000000..ef39c616da5 --- /dev/null +++ b/resources/js/components/ui/AutocompleteEditor/extensions/Document.js @@ -0,0 +1,7 @@ +import Document from '@tiptap/extension-document'; + +export const DocumentBlock = Document; + +export const DocumentInline = Document.extend({ + content: 'paragraph', +}); diff --git a/resources/js/components/ui/index.js b/resources/js/components/ui/index.js index 9527d5461c8..aaf0b950ff0 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'; diff --git a/resources/js/stories/AutocompleteEditor.stories.ts b/resources/js/stories/AutocompleteEditor.stories.ts new file mode 100644 index 00000000000..8176c11f151 --- /dev/null +++ b/resources/js/stories/AutocompleteEditor.stories.ts @@ -0,0 +1,246 @@ +import type { Meta, StoryObj } from '@storybook/vue3'; +import { expect, fn, userEvent, waitFor, within } from 'storybook/test'; +import { AutocompleteEditor } from '@ui'; +import { ref } from 'vue'; + +const meta = { + title: 'Components/AutocompleteEditor', + component: AutocompleteEditor, + argTypes: { + 'update:modelValue': { + description: 'Event handler called when the editor content changes.', + table: { + category: 'events', + type: { summary: '(value: object[]) => void' }, + }, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const sampleOptions = [ + { value: 'jack', label: 'Jack McDade' }, + { value: 'jason', label: 'Jason Varga' }, + { value: 'jesse', label: 'Jesse Leite' }, + { value: 'duncan', label: 'Duncan McClean' }, +]; + +const defaultCode = ` + +`; + +export const Default: Story = { + render: () => ({ + components: { AutocompleteEditor }, + setup() { + const value = ref([]); + const options = sampleOptions; + return { value, options }; + }, + template: defaultCode, + }), +}; + +export const _DocsIntro: Story = { + tags: ['!dev'], + parameters: { + docs: { + source: { code: defaultCode }, + }, + }, + render: () => ({ + components: { AutocompleteEditor }, + setup() { + const value = ref([]); + const options = sampleOptions; + return { value, options }; + }, + template: defaultCode, + }), +}; + +const inlineCode = ` + +`; + +export const _Inline: Story = { + tags: ['!dev'], + parameters: { + docs: { + source: { code: inlineCode }, + description: { + story: 'Set `inline` to drop the toolbar and block-level nodes (headings, lists) for a single-line editing experience.', + }, + }, + }, + render: () => ({ + components: { AutocompleteEditor }, + setup() { + const value = ref([]); + const options = sampleOptions; + return { value, options }; + }, + template: inlineCode, + }), +}; + +const inlineLineBreaksCode = ` + +`; + +export const _InlineWithLineBreaks: Story = { + tags: ['!dev'], + parameters: { + docs: { + source: { code: inlineLineBreaksCode }, + description: { + story: 'Combine `inline` with `enableLineBreaks` to allow soft line breaks (Enter) without introducing paragraphs or other block nodes.', + }, + }, + }, + render: () => ({ + components: { AutocompleteEditor }, + setup() { + const value = ref([]); + const options = sampleOptions; + return { value, options }; + }, + template: inlineLineBreaksCode, + }), +}; + +const readOnlyValue = [ + { + type: 'paragraph', + content: [ + { type: 'text', text: 'Assigned to ' }, + { type: 'mention', attrs: { value: 'jesse', label: 'Jesse Leite' } }, + { type: 'text', text: '. This field is read only.' }, + ], + }, +]; + +const readOnlyCode = ` + +`; + +export const _ReadOnly: Story = { + tags: ['!dev'], + parameters: { + docs: { + source: { code: readOnlyCode }, + }, + }, + render: () => ({ + components: { AutocompleteEditor }, + setup() { + const value = ref(readOnlyValue); + const options = sampleOptions; + return { value, options }; + }, + template: readOnlyCode, + }), +}; + +const autocompleteValue = [ + { + type: 'paragraph', + content: [ + { type: 'text', text: 'Reach out to ' }, + { type: 'mention', attrs: { value: 'jason', label: 'Jason Varga' } }, + { type: 'text', text: ' for details. Type @ to mention someone else.' }, + ], + }, +]; + +const autocompleteCode = ` + +`; + +export const _Autocomplete: Story = { + tags: ['!dev'], + parameters: { + docs: { + source: { code: autocompleteCode }, + description: { + story: 'Type `@` to open the autocomplete list and insert a mention chip. This example starts with a pre-populated mention already in the content.', + }, + }, + }, + render: () => ({ + components: { AutocompleteEditor }, + setup() { + const value = ref(autocompleteValue); + const options = sampleOptions; + return { value, options }; + }, + template: autocompleteCode, + }), +}; + +export const TestAutocompleteSelectViaEnterInlineNoBreaks: Story = { + tags: ['!dev', 'test'], + args: { + 'onUpdate:modelValue': fn(), + }, + render: (args) => ({ + components: { AutocompleteEditor }, + setup() { + const value = ref([]); + const options = sampleOptions; + return { value, options, onUpdate: args['onUpdate:modelValue'] }; + }, + template: ` + + `, + }), + play: async ({ canvasElement, args }) => { + const editable = canvasElement.querySelector('.ProseMirror') as HTMLElement; + await expect(editable).toBeTruthy(); + + await userEvent.click(editable); + await userEvent.keyboard('@jas'); + + await waitFor(() => { + expect(within(document.body).getByText('Jason Varga')).toBeTruthy(); + }); + + await userEvent.keyboard('{Enter}'); + + await waitFor(() => { + expect(args['onUpdate:modelValue']).toHaveBeenCalled(); + }); + + const lastCall = args['onUpdate:modelValue'].mock.calls.at(-1); + expect(JSON.stringify(lastCall[0])).toContain('"type":"mention"'); + expect(editable.textContent).toContain('Jason Varga'); + }, +}; diff --git a/resources/js/tests/Package.test.js b/resources/js/tests/Package.test.js index 4da3ead2380..21345897d1a 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 00000000000..a88b5144b35 --- /dev/null +++ b/resources/js/tests/components/ui/AutocompleteEditor.test.js @@ -0,0 +1,152 @@ +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 still splits into a new paragraph 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' }] }, + { type: 'paragraph' }, + ]); +}); + +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([]); +}); diff --git a/src/Fieldtypes/AutocompleteEditor.php b/src/Fieldtypes/AutocompleteEditor.php new file mode 100644 index 00000000000..eff51654646 --- /dev/null +++ b/src/Fieldtypes/AutocompleteEditor.php @@ -0,0 +1,157 @@ + [ + '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, + 'if' => ['inline' => 'equals true'], + ], + ]; + } + + 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'] ?? []; + } +} diff --git a/src/Providers/ExtensionServiceProvider.php b/src/Providers/ExtensionServiceProvider.php index 5371de78bcf..009f412449a 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, diff --git a/tests/Fieldtypes/AutocompleteEditorTest.php b/tests/Fieldtypes/AutocompleteEditorTest.php new file mode 100644 index 00000000000..c6f94875a5e --- /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))); + } +}