Added ability to resize images in editor

This commit is contained in:
Barunes Padhy 2024-06-24 11:54:52 +03:00
parent 59929caa60
commit 43cafc363e
6 changed files with 161 additions and 37 deletions

View File

@ -1,50 +1,109 @@
/*
extension credits: Angelika Tyborska: https://angelika.me/2023/02/26/how-to-add-editing-image-alt-text-tiptap/
*/
import Image from '@tiptap/extension-image'
import { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'; import { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react';
import { import Image from '@tiptap/extension-image';
Button import { Button } from 'reactstrap';
} from 'reactstrap'; import CustomImagePropertiesModal from './custom-image-properties-modal.jsx';
import { useState, useRef, useEffect } from 'react';
const CustomImageExtension = Image.extend({
addAttributes() {
return {
...this.parent?.(),
height: {
default: '300',
parseHTML: element => element.getAttribute('height'),
renderHTML: attributes => {
if (!attributes.height) {
return {};
}
return { height: attributes.height };
},
},
width: {
default: '300',
parseHTML: element => element.getAttribute('width'),
renderHTML: attributes => {
if (!attributes.width) {
return {};
}
return { width: attributes.width };
},
},
};
},
addNodeView() {
return ReactNodeViewRenderer(ImageNode);
},
});
function ImageNode(props) { function ImageNode(props) {
const { src, alt } = props.node.attrs const imageRef = useRef(null);
const { updateAttributes } = props const [modal, setModal] = useState(false);
const onEditAlt = () => { const [height, setHeight] = useState(props.node.attrs.height || '');
const newAlt = prompt('Set alt text:', alt || '') const [width, setWidth] = useState(props.node.attrs.width || '');
updateAttributes({ alt: newAlt })
}
let className = 'image' const toggle = () => setModal(!modal);
if (props.selected) { className += ' ProseMirror-selectednode'}
const { src, alt } = props.node.attrs;
const { updateAttributes } = props;
const setAlt = (alt) => {
updateAttributes({ alt });
};
const handleSetWidth = (width) => {
setWidth(width);
updateAttributes({ width });
};
const handleSetHeight = (height) => {
setHeight(height);
updateAttributes({ height });
};
useEffect(() => {
if (imageRef.current) {
imageRef.current.height = height;
imageRef.current.width = width;
}
}, [height, width]);
useEffect(() => {
if (imageRef.current) {
if (imageRef.current.height !== ''){
setHeight(imageRef.current.height)
}
if (imageRef.current.width !== ''){
setWidth(imageRef.current.width)
}
}
});
let className = 'image';
if (props.selected) className += ' ProseMirror-selectednode';
return ( return (
<NodeViewWrapper className={className} data-drag-handle> <NodeViewWrapper className={className} data-drag-handle>
<div className="image-container d-md-block"> <CustomImagePropertiesModal
<img className='mx-auto d-block' src={src} alt={alt} /> modal={modal}
toggle={toggle}
setAlt={setAlt}
alt={alt}
imageRef={imageRef}
setWidth={handleSetWidth}
setHeight={handleSetHeight}
/>
<div className='image-container d-md-block'>
<img ref={imageRef} className='mx-auto d-block' src={src} alt={alt} height={height === '' ? '300' : height} width={width === '' ? '300' : width} />
<div className="image-overlay"> <div className="image-overlay">
<span className="image-text mx-auto d-block"> <span className="image-text mx-auto d-block">
{ alt ? <Button className="edit" type="button" onClick={toggle}>
<span></span> : Edit Image Properties
<span>!</span>
}
{ alt ?
<span className="text">Alt text: &ldquo;{alt}&ldquo;.</span>:
<span className="text">Alt text missing.</span>
}
<Button className="edit" type="button" onClick={onEditAlt}>
Edit
</Button> </Button>
</span> </span>
</div> </div>
</div> </div>
</NodeViewWrapper> </NodeViewWrapper>
) );
} }
export default Image.extend({ export default CustomImageExtension;
addNodeView() {
return ReactNodeViewRenderer(ImageNode)
}
})

View File

@ -0,0 +1,34 @@
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, InputGroup, InputGroupText, Input } from 'reactstrap';
import RangeSlider from './range-slider.jsx';
import { useRef } from 'react';
function CustomImagePropertiesModal(props) {
const altField = useRef(null)
return (
<div>
<Modal isOpen={props.modal} toggle={props.toggle}>
<ModalHeader toggle={props.toggle}>Change Image Properties</ModalHeader>
<ModalBody>
<InputGroup>
<InputGroupText>
Image Alt Text
</InputGroupText>
<Input innerRef={altField} defaultValue={props.alt} onChange={() => props.setAlt(altField.current.value)} />
<RangeSlider setRange={props.setHeight} min={0} max={1000} step={1} defaultValue={300} value={props.height} label="Select Height" />
<RangeSlider setRange={props.setWidth} min={0} max={1000} step={1} defaultValue={300} value={props.width} label="Select Width" />
</InputGroup>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={props.toggle}>
Ok
</Button>{' '}
</ModalFooter>
</Modal>
</div>
);
}
export default CustomImagePropertiesModal;

View File

@ -0,0 +1,30 @@
import React, { useState } from 'react';
import { Input, Label, FormGroup, Container } from 'reactstrap';
function RangeSlider (props) {
const [value, setValue] = useState(props.defaultValue || props.min);
const handleChange = (e) => {
setValue(e.target.value);
props.setRange(e.target.value)
};
return (
<Container className='mt-5'>
<FormGroup>
<Label for="rangeSlider">{props.label}: {value} pixels</Label>
<Input
type="range"
name="range"
id="rangeSlider"
min={props.min}
max={props.max}
step={props.step}
value={value}
onChange={handleChange}
/>
</FormGroup>
</Container>
);
}
export default RangeSlider;

View File

@ -348,7 +348,9 @@ const extensions = [
}), }),
Underline, Underline,
Blockquote, Blockquote,
CustomImageExtension, CustomImageExtension.configure({
htmlAttributes: ['height', 'width']
}),
TextAlign.configure({ TextAlign.configure({
types: ['heading', 'paragraph'], types: ['heading', 'paragraph'],
}), }),

View File

@ -35,7 +35,6 @@ a {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 70%;
} }
.image-container { .image-container {

View File

@ -25,7 +25,7 @@ a {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 65%; width: 100%;
} }
@media only screen and (max-width: 765px){ @media only screen and (max-width: 765px){