UNPKG

tiptap-extension-resize-image

Version:

A tiptap image resizing extension for React, Vue, Next, and VanillaJS. Additionally, it can align the image position.

219 lines (216 loc) 9.96 kB
import Image from '@tiptap/extension-image'; const ImageResize = Image.extend({ addAttributes() { return { src: { default: null, }, alt: { default: null, }, style: { default: 'width: 100%; height: auto; cursor: pointer;', parseHTML: element => { const width = element.getAttribute('width'); return width ? `width: ${width}px; height: auto; cursor: pointer;` : `${element.style.cssText}`; }, }, title: { default: null, }, loading: { default: null, }, srcset: { default: null, }, sizes: { default: null, }, crossorigin: { default: null, }, usemap: { default: null, }, ismap: { default: null, }, width: { default: null, }, height: { default: null, }, referrerpolicy: { default: null, }, longdesc: { default: null, }, decoding: { default: null, }, class: { default: null, }, id: { default: null, }, name: { default: null, }, draggable: { default: true, }, tabindex: { default: null, }, 'aria-label': { default: null, }, 'aria-labelledby': { default: null, }, 'aria-describedby': { default: null, }, }; }, addNodeView() { return ({ node, editor, getPos }) => { const { view, options: { editable }, } = editor; const { style } = node.attrs; const $wrapper = document.createElement('div'); const $container = document.createElement('div'); const $img = document.createElement('img'); const iconStyle = 'width: 24px; height: 24px; cursor: pointer;'; const dispatchNodeView = () => { if (typeof getPos === 'function') { const newAttrs = Object.assign(Object.assign({}, node.attrs), { style: `${$img.style.cssText}` }); view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, newAttrs)); } }; const paintPositionContoller = () => { const $postionController = document.createElement('div'); const $leftController = document.createElement('img'); const $centerController = document.createElement('img'); const $rightController = document.createElement('img'); const controllerMouseOver = e => { e.target.style.opacity = 0.3; }; const controllerMouseOut = e => { e.target.style.opacity = 1; }; $postionController.setAttribute('style', 'position: absolute; top: 0%; left: 50%; width: 100px; height: 25px; z-index: 999; background-color: rgba(255, 255, 255, 0.7); border-radius: 4px; border: 2px solid #6C6C6C; cursor: pointer; transform: translate(-50%, -50%); display: flex; justify-content: space-between; align-items: center; padding: 0 10px;'); $leftController.setAttribute('src', 'https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/format_align_left/default/20px.svg'); $leftController.setAttribute('style', iconStyle); $leftController.addEventListener('mouseover', controllerMouseOver); $leftController.addEventListener('mouseout', controllerMouseOut); $centerController.setAttribute('src', 'https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/format_align_center/default/20px.svg'); $centerController.setAttribute('style', iconStyle); $centerController.addEventListener('mouseover', controllerMouseOver); $centerController.addEventListener('mouseout', controllerMouseOut); $rightController.setAttribute('src', 'https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/format_align_right/default/20px.svg'); $rightController.setAttribute('style', iconStyle); $rightController.addEventListener('mouseover', controllerMouseOver); $rightController.addEventListener('mouseout', controllerMouseOut); $leftController.addEventListener('click', () => { $img.setAttribute('style', `${$img.style.cssText} margin: 0 auto 0 0;`); dispatchNodeView(); }); $centerController.addEventListener('click', () => { $img.setAttribute('style', `${$img.style.cssText} margin: 0 auto;`); dispatchNodeView(); }); $rightController.addEventListener('click', () => { $img.setAttribute('style', `${$img.style.cssText} margin: 0 0 0 auto;`); dispatchNodeView(); }); $postionController.appendChild($leftController); $postionController.appendChild($centerController); $postionController.appendChild($rightController); $container.appendChild($postionController); }; $wrapper.setAttribute('style', `display: flex;`); $wrapper.appendChild($container); $container.setAttribute('style', `${style}`); $container.appendChild($img); Object.entries(node.attrs).forEach(([key, value]) => { if (value === undefined || value === null) return; $img.setAttribute(key, value); }); if (!editable) return { dom: $img }; const dotsPosition = [ 'top: -4px; left: -4px; cursor: nwse-resize;', 'top: -4px; right: -4px; cursor: nesw-resize;', 'bottom: -4px; left: -4px; cursor: nesw-resize;', 'bottom: -4px; right: -4px; cursor: nwse-resize;', ]; let isResizing = false; let startX, startWidth; $container.addEventListener('click', () => { //remove remaining dots and position controller if ($container.childElementCount > 3) { for (let i = 0; i < 5; i++) { $container.removeChild($container.lastChild); } } paintPositionContoller(); $container.setAttribute('style', `position: relative; border: 1px dashed #6C6C6C; ${style} cursor: pointer;`); Array.from({ length: 4 }, (_, index) => { const $dot = document.createElement('div'); $dot.setAttribute('style', `position: absolute; width: 9px; height: 9px; border: 1.5px solid #6C6C6C; border-radius: 50%; ${dotsPosition[index]}`); $dot.addEventListener('mousedown', e => { e.preventDefault(); isResizing = true; startX = e.clientX; startWidth = $container.offsetWidth; const onMouseMove = (e) => { if (!isResizing) return; const deltaX = index % 2 === 0 ? -(e.clientX - startX) : e.clientX - startX; const newWidth = startWidth + deltaX; $container.style.width = newWidth + 'px'; $img.style.width = newWidth + 'px'; }; const onMouseUp = () => { if (isResizing) { isResizing = false; } dispatchNodeView(); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); $container.appendChild($dot); }); }); document.addEventListener('click', (e) => { const $target = e.target; const isClickInside = $container.contains($target) || $target.style.cssText === iconStyle; if (!isClickInside) { const containerStyle = $container.getAttribute('style'); const newStyle = containerStyle === null || containerStyle === void 0 ? void 0 : containerStyle.replace('border: 1px dashed #6C6C6C;', ''); $container.setAttribute('style', newStyle); if ($container.childElementCount > 3) { for (let i = 0; i < 5; i++) { $container.removeChild($container.lastChild); } } } }); return { dom: $wrapper, }; }; }, }); export { ImageResize, ImageResize as default }; //# sourceMappingURL=index.js.map