├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── dependabot.yml ├── .gitignore ├── LICENSE ├── LICENSE.md ├── README.md ├── _config.yml ├── package-lock.json ├── package.json ├── src ├── component │ └── ImageResizeComponent.tsx ├── image.tsx └── index.ts └── tiptap-imagresize.iml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | litters-backend/node_modules/ 4 | build/ 5 | tmp/ 6 | temp/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Bram Hammer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, überdosis GbR 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @breakerh/tiptap-image-resize 2 | [![Version](https://img.shields.io/github/package-json/v/breakerh/tiptap-image-resize)](https://github.com/breakerh/tiptap-image-resize) 3 | [![License](https://img.shields.io/github/license/breakerh/tiptap-image-resize)](https://github.com/breakerh/tiptap-image-resize) 4 | 5 | ## Introduction 6 | tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*. 7 | I am not affiliated with TipTap in any way. I build this module for myself and thought maybe I can help other with it.. Please [let me know](https://github.com/breakerh/tiptap-image-resize/issues) if you experience any problems whatsoever! 8 | 9 | ## Installation 10 | Install the package through NPM ( `` npm i tiptap-imagresize `` ) or yarn ( `` yarn add tiptap-imagresize `` ), and don't forget to include it in the use editor explained [here](https://tiptap.dev/guide/configuration#nodes-marks-and-extensions). 11 | 12 | ### Configuration 13 | While adding Image Resizer to your TipTap editor you can set a few options. 14 | 15 | | Key | Description | Default | 16 | |----------------|-------------------------------------------------|---------| 17 | | inline | Is the image inline? | `false` | 18 | | allowBase64 | Can you insert Base64 encoded images? | `false` | 19 | | HTMLAttributes | Do you want to add custom attributes? | empty | 20 | | resizeIcon | What type if resize icon would you want to see? | `⊙` | 21 | | useFigure | Do you want to wrap your image in a figure tag? | `false` | 22 | 23 | #### Example: 24 | ```js 25 | const editor = useEditor({ 26 | extensions: [ 27 | StarterKit, ImageResize.configure({resizeIcon: <>ResizeMe}) 28 | ], 29 | content: '

Hello World!

', 30 | }) 31 | ``` 32 | 33 | ## Styling 34 | I didn't include any styling since a assume you have your reasons you will use TipTap instead of other editors. 35 | Do you still want a quick result or just want some starter css? 36 | Add this to your (s)css or convert it to react styles markup. 37 | ### Scss 38 | ```css 39 | .image-resizer { 40 | display: inline-flex; 41 | position: relative; 42 | flex-grow: 0; 43 | .resize-trigger { 44 | position: absolute; 45 | right: -6px; 46 | bottom: -9px; 47 | opacity: 0; 48 | transition: opacity .3s ease; 49 | color: #3259a5; 50 | } 51 | &:hover .resize-trigger { 52 | opacity: 1; 53 | } 54 | } 55 | ``` 56 | ### Css 57 | ```css 58 | .image-resizer { 59 | display: inline-flex; 60 | position: relative; 61 | flex-grow: 0; 62 | } 63 | .image-resizer .resize-trigger { 64 | position: absolute; 65 | right: -6px; 66 | bottom: -9px; 67 | opacity: 0; 68 | transition: opacity .3s ease; 69 | color: #3259a5; 70 | } 71 | .image-resizer:hover .resize-trigger { 72 | opacity: 1; 73 | } 74 | ``` 75 | 76 | ## Official Documentation 77 | Documentation can be found on the [tiptap website](https://tiptap.dev). 78 | 79 | ## License 80 | tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md). 81 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiptap-imagresize", 3 | "version": "1.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "tiptap-imagresize", 9 | "version": "1.1.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@types/react": "^18.0.38" 13 | }, 14 | "peerDependencies": { 15 | "@tiptap/core": "^2.0.0-beta.1", 16 | "@tiptap/extension-image": "^2.0.0-beta.27", 17 | "@tiptap/react": "^2.0.0-beta.109", 18 | "react": "^17.0.2 || ^18" 19 | } 20 | }, 21 | "node_modules/@popperjs/core": { 22 | "version": "2.11.8", 23 | "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", 24 | "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", 25 | "peer": true, 26 | "funding": { 27 | "type": "opencollective", 28 | "url": "https://opencollective.com/popperjs" 29 | } 30 | }, 31 | "node_modules/@remirror/core-constants": { 32 | "version": "2.0.2", 33 | "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.2.tgz", 34 | "integrity": "sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==", 35 | "peer": true 36 | }, 37 | "node_modules/@tiptap/core": { 38 | "version": "2.3.1", 39 | "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.3.1.tgz", 40 | "integrity": "sha512-ycpQlmczAOc05TgB5sc3RUTEEBXAVmS8MR9PqQzg96qidaRfVkgE+2w4k7t83PMHl2duC0MGqOCy96pLYwSpeg==", 41 | "peer": true, 42 | "funding": { 43 | "type": "github", 44 | "url": "https://github.com/sponsors/ueberdosis" 45 | }, 46 | "peerDependencies": { 47 | "@tiptap/pm": "^2.0.0" 48 | } 49 | }, 50 | "node_modules/@tiptap/extension-bubble-menu": { 51 | "version": "2.3.1", 52 | "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.3.1.tgz", 53 | "integrity": "sha512-6PGrk65f0eXHcCEe6A2/GpooMsD6RPZY1kWSSWUNfklJO54R/8uAtsSVIBr7wQ34pvrYkNaluRUrDWUokWyBOQ==", 54 | "peer": true, 55 | "dependencies": { 56 | "tippy.js": "^6.3.7" 57 | }, 58 | "funding": { 59 | "type": "github", 60 | "url": "https://github.com/sponsors/ueberdosis" 61 | }, 62 | "peerDependencies": { 63 | "@tiptap/core": "^2.0.0", 64 | "@tiptap/pm": "^2.0.0" 65 | } 66 | }, 67 | "node_modules/@tiptap/extension-floating-menu": { 68 | "version": "2.3.1", 69 | "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.3.1.tgz", 70 | "integrity": "sha512-3+dONthHRMFzJjLF9JtRbm9u4XJs8txCoChsZjwD0wBf8XfPtUGZQn9W5xNJG+5pozrOQhj9KC1UZL4tuvSRkg==", 71 | "peer": true, 72 | "dependencies": { 73 | "tippy.js": "^6.3.7" 74 | }, 75 | "funding": { 76 | "type": "github", 77 | "url": "https://github.com/sponsors/ueberdosis" 78 | }, 79 | "peerDependencies": { 80 | "@tiptap/core": "^2.0.0", 81 | "@tiptap/pm": "^2.0.0" 82 | } 83 | }, 84 | "node_modules/@tiptap/extension-image": { 85 | "version": "2.3.1", 86 | "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.3.1.tgz", 87 | "integrity": "sha512-3RhVBySQA2LbftWhtZ0p2Mqf9lihNAYs3uQ3iyaB+BYViQiHyVpui09Wny0BwNy0oV6ryUWjBifko2Z1AZgANw==", 88 | "peer": true, 89 | "funding": { 90 | "type": "github", 91 | "url": "https://github.com/sponsors/ueberdosis" 92 | }, 93 | "peerDependencies": { 94 | "@tiptap/core": "^2.0.0" 95 | } 96 | }, 97 | "node_modules/@tiptap/pm": { 98 | "version": "2.3.1", 99 | "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.3.1.tgz", 100 | "integrity": "sha512-jdd1PFAFeewcu1rWsiqoCc04u5NCplHVjsGPN4jxUmqKdU0YN/9sp7h8gRG6YN1GZRoC1Y6KD+WPLMdzkwizZQ==", 101 | "peer": true, 102 | "dependencies": { 103 | "prosemirror-changeset": "^2.2.1", 104 | "prosemirror-collab": "^1.3.1", 105 | "prosemirror-commands": "^1.5.2", 106 | "prosemirror-dropcursor": "^1.8.1", 107 | "prosemirror-gapcursor": "^1.3.2", 108 | "prosemirror-history": "^1.3.2", 109 | "prosemirror-inputrules": "^1.3.0", 110 | "prosemirror-keymap": "^1.2.2", 111 | "prosemirror-markdown": "^1.12.0", 112 | "prosemirror-menu": "^1.2.4", 113 | "prosemirror-model": "^1.19.4", 114 | "prosemirror-schema-basic": "^1.2.2", 115 | "prosemirror-schema-list": "^1.3.0", 116 | "prosemirror-state": "^1.4.3", 117 | "prosemirror-tables": "^1.3.5", 118 | "prosemirror-trailing-node": "^2.0.7", 119 | "prosemirror-transform": "^1.8.0", 120 | "prosemirror-view": "^1.32.7" 121 | }, 122 | "funding": { 123 | "type": "github", 124 | "url": "https://github.com/sponsors/ueberdosis" 125 | } 126 | }, 127 | "node_modules/@tiptap/react": { 128 | "version": "2.3.1", 129 | "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.3.1.tgz", 130 | "integrity": "sha512-MM6UOi5nmdM/dZXYtbBYHJEsVtyyFFnOCXlXmhTlhz0WYI8VkEAY7XWLB96KrqsbRk9PUWwdev7iT1q40zxVeg==", 131 | "peer": true, 132 | "dependencies": { 133 | "@tiptap/extension-bubble-menu": "^2.3.1", 134 | "@tiptap/extension-floating-menu": "^2.3.1" 135 | }, 136 | "funding": { 137 | "type": "github", 138 | "url": "https://github.com/sponsors/ueberdosis" 139 | }, 140 | "peerDependencies": { 141 | "@tiptap/core": "^2.0.0", 142 | "@tiptap/pm": "^2.0.0", 143 | "react": "^17.0.0 || ^18.0.0", 144 | "react-dom": "^17.0.0 || ^18.0.0" 145 | } 146 | }, 147 | "node_modules/@types/prop-types": { 148 | "version": "15.7.12", 149 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", 150 | "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" 151 | }, 152 | "node_modules/@types/react": { 153 | "version": "18.3.3", 154 | "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", 155 | "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", 156 | "dependencies": { 157 | "@types/prop-types": "*", 158 | "csstype": "^3.0.2" 159 | } 160 | }, 161 | "node_modules/argparse": { 162 | "version": "2.0.1", 163 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 164 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 165 | "peer": true 166 | }, 167 | "node_modules/crelt": { 168 | "version": "1.0.6", 169 | "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", 170 | "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", 171 | "peer": true 172 | }, 173 | "node_modules/csstype": { 174 | "version": "3.1.3", 175 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 176 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" 177 | }, 178 | "node_modules/entities": { 179 | "version": "4.5.0", 180 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 181 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 182 | "peer": true, 183 | "engines": { 184 | "node": ">=0.12" 185 | }, 186 | "funding": { 187 | "url": "https://github.com/fb55/entities?sponsor=1" 188 | } 189 | }, 190 | "node_modules/escape-string-regexp": { 191 | "version": "4.0.0", 192 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 193 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 194 | "peer": true, 195 | "engines": { 196 | "node": ">=10" 197 | }, 198 | "funding": { 199 | "url": "https://github.com/sponsors/sindresorhus" 200 | } 201 | }, 202 | "node_modules/js-tokens": { 203 | "version": "4.0.0", 204 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 205 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 206 | "peer": true 207 | }, 208 | "node_modules/linkify-it": { 209 | "version": "5.0.0", 210 | "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", 211 | "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", 212 | "peer": true, 213 | "dependencies": { 214 | "uc.micro": "^2.0.0" 215 | } 216 | }, 217 | "node_modules/loose-envify": { 218 | "version": "1.4.0", 219 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 220 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 221 | "peer": true, 222 | "dependencies": { 223 | "js-tokens": "^3.0.0 || ^4.0.0" 224 | }, 225 | "bin": { 226 | "loose-envify": "cli.js" 227 | } 228 | }, 229 | "node_modules/markdown-it": { 230 | "version": "14.1.0", 231 | "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", 232 | "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", 233 | "peer": true, 234 | "dependencies": { 235 | "argparse": "^2.0.1", 236 | "entities": "^4.4.0", 237 | "linkify-it": "^5.0.0", 238 | "mdurl": "^2.0.0", 239 | "punycode.js": "^2.3.1", 240 | "uc.micro": "^2.1.0" 241 | }, 242 | "bin": { 243 | "markdown-it": "bin/markdown-it.mjs" 244 | } 245 | }, 246 | "node_modules/mdurl": { 247 | "version": "2.0.0", 248 | "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", 249 | "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", 250 | "peer": true 251 | }, 252 | "node_modules/orderedmap": { 253 | "version": "2.1.1", 254 | "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", 255 | "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", 256 | "peer": true 257 | }, 258 | "node_modules/prosemirror-changeset": { 259 | "version": "2.2.1", 260 | "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz", 261 | "integrity": "sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==", 262 | "peer": true, 263 | "dependencies": { 264 | "prosemirror-transform": "^1.0.0" 265 | } 266 | }, 267 | "node_modules/prosemirror-collab": { 268 | "version": "1.3.1", 269 | "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", 270 | "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", 271 | "peer": true, 272 | "dependencies": { 273 | "prosemirror-state": "^1.0.0" 274 | } 275 | }, 276 | "node_modules/prosemirror-commands": { 277 | "version": "1.5.2", 278 | "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.5.2.tgz", 279 | "integrity": "sha512-hgLcPaakxH8tu6YvVAaILV2tXYsW3rAdDR8WNkeKGcgeMVQg3/TMhPdVoh7iAmfgVjZGtcOSjKiQaoeKjzd2mQ==", 280 | "peer": true, 281 | "dependencies": { 282 | "prosemirror-model": "^1.0.0", 283 | "prosemirror-state": "^1.0.0", 284 | "prosemirror-transform": "^1.0.0" 285 | } 286 | }, 287 | "node_modules/prosemirror-dropcursor": { 288 | "version": "1.8.1", 289 | "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz", 290 | "integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==", 291 | "peer": true, 292 | "dependencies": { 293 | "prosemirror-state": "^1.0.0", 294 | "prosemirror-transform": "^1.1.0", 295 | "prosemirror-view": "^1.1.0" 296 | } 297 | }, 298 | "node_modules/prosemirror-gapcursor": { 299 | "version": "1.3.2", 300 | "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", 301 | "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", 302 | "peer": true, 303 | "dependencies": { 304 | "prosemirror-keymap": "^1.0.0", 305 | "prosemirror-model": "^1.0.0", 306 | "prosemirror-state": "^1.0.0", 307 | "prosemirror-view": "^1.0.0" 308 | } 309 | }, 310 | "node_modules/prosemirror-history": { 311 | "version": "1.4.0", 312 | "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.0.tgz", 313 | "integrity": "sha512-UUiGzDVcqo1lovOPdi9YxxUps3oBFWAIYkXLu3Ot+JPv1qzVogRbcizxK3LhHmtaUxclohgiOVesRw5QSlMnbQ==", 314 | "peer": true, 315 | "dependencies": { 316 | "prosemirror-state": "^1.2.2", 317 | "prosemirror-transform": "^1.0.0", 318 | "prosemirror-view": "^1.31.0", 319 | "rope-sequence": "^1.3.0" 320 | } 321 | }, 322 | "node_modules/prosemirror-inputrules": { 323 | "version": "1.4.0", 324 | "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz", 325 | "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==", 326 | "peer": true, 327 | "dependencies": { 328 | "prosemirror-state": "^1.0.0", 329 | "prosemirror-transform": "^1.0.0" 330 | } 331 | }, 332 | "node_modules/prosemirror-keymap": { 333 | "version": "1.2.2", 334 | "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz", 335 | "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==", 336 | "peer": true, 337 | "dependencies": { 338 | "prosemirror-state": "^1.0.0", 339 | "w3c-keyname": "^2.2.0" 340 | } 341 | }, 342 | "node_modules/prosemirror-markdown": { 343 | "version": "1.12.0", 344 | "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.12.0.tgz", 345 | "integrity": "sha512-6F5HS8Z0HDYiS2VQDZzfZP6A0s/I0gbkJy8NCzzDMtcsz3qrfqyroMMeoSjAmOhDITyon11NbXSzztfKi+frSQ==", 346 | "peer": true, 347 | "dependencies": { 348 | "markdown-it": "^14.0.0", 349 | "prosemirror-model": "^1.0.0" 350 | } 351 | }, 352 | "node_modules/prosemirror-menu": { 353 | "version": "1.2.4", 354 | "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz", 355 | "integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==", 356 | "peer": true, 357 | "dependencies": { 358 | "crelt": "^1.0.0", 359 | "prosemirror-commands": "^1.0.0", 360 | "prosemirror-history": "^1.0.0", 361 | "prosemirror-state": "^1.0.0" 362 | } 363 | }, 364 | "node_modules/prosemirror-model": { 365 | "version": "1.20.0", 366 | "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.20.0.tgz", 367 | "integrity": "sha512-q7AY7vMjKYqDCeoedgUiAgrLabliXxndJuuFmcmc2+YU1SblvnOiG2WEACF2lwAZsMlfLpiAilA3L+TWlDqIsQ==", 368 | "peer": true, 369 | "dependencies": { 370 | "orderedmap": "^2.0.0" 371 | } 372 | }, 373 | "node_modules/prosemirror-schema-basic": { 374 | "version": "1.2.2", 375 | "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.2.tgz", 376 | "integrity": "sha512-/dT4JFEGyO7QnNTe9UaKUhjDXbTNkiWTq/N4VpKaF79bBjSExVV2NXmJpcM7z/gD7mbqNjxbmWW5nf1iNSSGnw==", 377 | "peer": true, 378 | "dependencies": { 379 | "prosemirror-model": "^1.19.0" 380 | } 381 | }, 382 | "node_modules/prosemirror-schema-list": { 383 | "version": "1.3.0", 384 | "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.3.0.tgz", 385 | "integrity": "sha512-Hz/7gM4skaaYfRPNgr421CU4GSwotmEwBVvJh5ltGiffUJwm7C8GfN/Bc6DR1EKEp5pDKhODmdXXyi9uIsZl5A==", 386 | "peer": true, 387 | "dependencies": { 388 | "prosemirror-model": "^1.0.0", 389 | "prosemirror-state": "^1.0.0", 390 | "prosemirror-transform": "^1.7.3" 391 | } 392 | }, 393 | "node_modules/prosemirror-state": { 394 | "version": "1.4.3", 395 | "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", 396 | "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", 397 | "peer": true, 398 | "dependencies": { 399 | "prosemirror-model": "^1.0.0", 400 | "prosemirror-transform": "^1.0.0", 401 | "prosemirror-view": "^1.27.0" 402 | } 403 | }, 404 | "node_modules/prosemirror-tables": { 405 | "version": "1.3.7", 406 | "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.3.7.tgz", 407 | "integrity": "sha512-oEwX1wrziuxMtwFvdDWSFHVUWrFJWt929kVVfHvtTi8yvw+5ppxjXZkMG/fuTdFo+3DXyIPSKfid+Be1npKXDA==", 408 | "peer": true, 409 | "dependencies": { 410 | "prosemirror-keymap": "^1.1.2", 411 | "prosemirror-model": "^1.8.1", 412 | "prosemirror-state": "^1.3.1", 413 | "prosemirror-transform": "^1.2.1", 414 | "prosemirror-view": "^1.13.3" 415 | } 416 | }, 417 | "node_modules/prosemirror-trailing-node": { 418 | "version": "2.0.8", 419 | "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.8.tgz", 420 | "integrity": "sha512-ujRYhSuhQb1Jsarh1IHqb2KoSnRiD7wAMDGucP35DN7j5af6X7B18PfdPIrbwsPTqIAj0fyOvxbuPsWhNvylmA==", 421 | "peer": true, 422 | "dependencies": { 423 | "@remirror/core-constants": "^2.0.2", 424 | "escape-string-regexp": "^4.0.0" 425 | }, 426 | "peerDependencies": { 427 | "prosemirror-model": "^1.19.0", 428 | "prosemirror-state": "^1.4.2", 429 | "prosemirror-view": "^1.31.2" 430 | } 431 | }, 432 | "node_modules/prosemirror-transform": { 433 | "version": "1.8.0", 434 | "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.8.0.tgz", 435 | "integrity": "sha512-BaSBsIMv52F1BVVMvOmp1yzD3u65uC3HTzCBQV1WDPqJRQ2LuHKcyfn0jwqodo8sR9vVzMzZyI+Dal5W9E6a9A==", 436 | "peer": true, 437 | "dependencies": { 438 | "prosemirror-model": "^1.0.0" 439 | } 440 | }, 441 | "node_modules/prosemirror-view": { 442 | "version": "1.33.6", 443 | "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.6.tgz", 444 | "integrity": "sha512-zRLUNgLIQfd8IfGprsXxWTjdA8xEAFJe8cDNrOptj6Mop9sj+BMeVbJvceyAYCm5G2dOdT2prctH7K9dfnpIMw==", 445 | "peer": true, 446 | "dependencies": { 447 | "prosemirror-model": "^1.20.0", 448 | "prosemirror-state": "^1.0.0", 449 | "prosemirror-transform": "^1.1.0" 450 | } 451 | }, 452 | "node_modules/punycode.js": { 453 | "version": "2.3.1", 454 | "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", 455 | "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", 456 | "peer": true, 457 | "engines": { 458 | "node": ">=6" 459 | } 460 | }, 461 | "node_modules/react": { 462 | "version": "18.3.1", 463 | "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", 464 | "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", 465 | "peer": true, 466 | "dependencies": { 467 | "loose-envify": "^1.1.0" 468 | }, 469 | "engines": { 470 | "node": ">=0.10.0" 471 | } 472 | }, 473 | "node_modules/react-dom": { 474 | "version": "18.3.1", 475 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", 476 | "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", 477 | "peer": true, 478 | "dependencies": { 479 | "loose-envify": "^1.1.0", 480 | "scheduler": "^0.23.2" 481 | }, 482 | "peerDependencies": { 483 | "react": "^18.3.1" 484 | } 485 | }, 486 | "node_modules/rope-sequence": { 487 | "version": "1.3.4", 488 | "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", 489 | "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", 490 | "peer": true 491 | }, 492 | "node_modules/scheduler": { 493 | "version": "0.23.2", 494 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", 495 | "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", 496 | "peer": true, 497 | "dependencies": { 498 | "loose-envify": "^1.1.0" 499 | } 500 | }, 501 | "node_modules/tippy.js": { 502 | "version": "6.3.7", 503 | "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", 504 | "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", 505 | "peer": true, 506 | "dependencies": { 507 | "@popperjs/core": "^2.9.0" 508 | } 509 | }, 510 | "node_modules/uc.micro": { 511 | "version": "2.1.0", 512 | "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", 513 | "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", 514 | "peer": true 515 | }, 516 | "node_modules/w3c-keyname": { 517 | "version": "2.2.8", 518 | "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", 519 | "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", 520 | "peer": true 521 | } 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiptap-imagresize", 3 | "description": "image extension + resize function for tiptap", 4 | "version": "1.1.0", 5 | "homepage": "https://github.com/breakerh/ ", 6 | "license": "MIT", 7 | "keywords": [ 8 | "tiptap", 9 | "tiptap extension" 10 | ], 11 | "peerDependencies": { 12 | "@tiptap/core": "^2.0.0-beta.1", 13 | "@tiptap/extension-image": "^2.0.0-beta.27", 14 | "@tiptap/react": "^2.0.0-beta.109", 15 | "react": "^17.0.2 || ^18" 16 | }, 17 | "main": "src/index.ts", 18 | "files": [ 19 | "src" 20 | ], 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/breakerh/tiptap-image-resize" 24 | }, 25 | "dependencies": { 26 | "@types/react": "^18.0.38" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/component/ImageResizeComponent.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/anchor-is-valid */ 2 | import React from 'react' 3 | import { NodeViewWrapper } from '@tiptap/react' 4 | 5 | export default (props:any) => { 6 | const handler = (mouseDownEvent: React.MouseEvent) => { 7 | const parent = (mouseDownEvent.target as HTMLElement).closest('.image-resizer') 8 | const image = parent?.querySelector('img.postimage') ?? null 9 | if(image===null) 10 | return 11 | const startSize = { x: image.clientWidth, y: image.clientHeight } 12 | const startPosition = { x: mouseDownEvent.pageX, y: mouseDownEvent.pageY } 13 | 14 | function onMouseMove(mouseMoveEvent:MouseEvent) { 15 | props.updateAttributes({ 16 | width: startSize.x - startPosition.x + mouseMoveEvent.pageX, 17 | height: startSize.y - startPosition.y + mouseMoveEvent.pageY 18 | }) 19 | } 20 | function onMouseUp() { 21 | document.body.removeEventListener("mousemove", onMouseMove); 22 | } 23 | 24 | document.body.addEventListener("mousemove", onMouseMove); 25 | document.body.addEventListener("mouseup", onMouseUp, { once: true }); 26 | }; 27 | return ( 28 | 29 | {props.extension.options.useFigure? ( 30 |
31 | 32 |
33 | ) : ()} 34 |
35 | {props.extension.options.resizeIcon} 36 |
37 |
38 | ) 39 | } -------------------------------------------------------------------------------- /src/image.tsx: -------------------------------------------------------------------------------- 1 | import React, {Component, FC, ReactElement} from "react"; 2 | import {mergeAttributes, nodeInputRule, Node} from "@tiptap/core"; 3 | import {ReactNodeViewRenderer} from "@tiptap/react"; 4 | import ImageResizeComponent from "./component/ImageResizeComponent"; 5 | import Image from '@tiptap/extension-image' 6 | 7 | export interface ImageOptions { 8 | inline: boolean, 9 | allowBase64: boolean, 10 | HTMLAttributes: Record, 11 | resizeIcon: FC|Component|ReactElement, 12 | useFigure: boolean 13 | } 14 | declare module '@tiptap/core' { 15 | interface Commands { 16 | imageResize: { 17 | setImage: (options: { src: string, alt?: string, title?: string, width?: string|number, height?: string|number, isDraggable?: boolean }) => ReturnType, 18 | } 19 | } 20 | } 21 | export const inputRegex = /(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/ 22 | export const ImageResize = Image.extend({ 23 | name: "imageResize", 24 | addOptions() { 25 | return { 26 | inline: false, 27 | allowBase64: false, 28 | HTMLAttributes: {}, 29 | resizeIcon: <>⊙, 30 | useFigure: false 31 | } 32 | }, 33 | addAttributes() { 34 | return { 35 | ...this.parent?.(), 36 | width: { 37 | default: '100%', 38 | renderHTML: (attributes) => { 39 | return { 40 | width: attributes.width 41 | }; 42 | } 43 | }, 44 | height: { 45 | default: 'auto', 46 | renderHTML: (attributes) => { 47 | return { 48 | height: attributes.height 49 | }; 50 | } 51 | }, 52 | }; 53 | }, 54 | 55 | addNodeView() { 56 | return ReactNodeViewRenderer(ImageResizeComponent) 57 | }, 58 | addInputRules() { 59 | return [ 60 | nodeInputRule({ 61 | find: inputRegex, 62 | type: this.type, 63 | getAttributes: match => { 64 | const [,, alt, src, title, height, width, isDraggable] = match 65 | return { src, alt, title, height, width, isDraggable } 66 | }, 67 | }), 68 | ] 69 | }, 70 | }) 71 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { ImageResize } from './image' 2 | 3 | export * from './image' 4 | 5 | export default ImageResize 6 | -------------------------------------------------------------------------------- /tiptap-imagresize.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------