Featured artworks thumbnails
This commit is contained in:
193
resources/js/components/forum/RichCompareNode.jsx
Normal file
193
resources/js/components/forum/RichCompareNode.jsx
Normal file
@@ -0,0 +1,193 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { Node, mergeAttributes as mergeNodeAttributes } from '@tiptap/core'
|
||||
import { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'
|
||||
|
||||
function readImageAttrs(element) {
|
||||
const imageElements = Array.from(element.querySelectorAll?.('img') || [])
|
||||
const subtitleElement = element.querySelector?.('figcaption')
|
||||
|
||||
return {
|
||||
leftSrc: imageElements[0]?.getAttribute('src') || '',
|
||||
leftAlt: imageElements[0]?.getAttribute('alt') || '',
|
||||
rightSrc: imageElements[1]?.getAttribute('src') || '',
|
||||
rightAlt: imageElements[1]?.getAttribute('alt') || '',
|
||||
subtitle: subtitleElement?.textContent?.trim() || '',
|
||||
}
|
||||
}
|
||||
|
||||
function RichCompareNodeView({ editor, node, selected, updateAttributes, deleteNode, getPos }) {
|
||||
const selectNode = useCallback(() => {
|
||||
if (!editor || typeof getPos !== 'function') {
|
||||
return
|
||||
}
|
||||
|
||||
editor.chain().focus().setNodeSelection(getPos()).run()
|
||||
}, [editor, getPos])
|
||||
|
||||
return (
|
||||
<NodeViewWrapper
|
||||
as="figure"
|
||||
className={["rich-compare-node", selected ? 'is-selected' : ''].filter(Boolean).join(' ')}
|
||||
data-rich-compare="true"
|
||||
onMouseDown={(event) => {
|
||||
if (event.target instanceof HTMLElement && event.target.closest('input, textarea, button, select, label')) {
|
||||
return
|
||||
}
|
||||
|
||||
selectNode()
|
||||
}}
|
||||
>
|
||||
<div className="rich-compare-node__grid">
|
||||
<div className="rich-compare-node__tile">
|
||||
<span className="rich-compare-node__badge">Left</span>
|
||||
<img
|
||||
src={node.attrs.leftSrc}
|
||||
alt={node.attrs.leftAlt || ''}
|
||||
className="rich-compare-node__img"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="rich-compare-node__tile">
|
||||
<span className="rich-compare-node__badge">Right</span>
|
||||
<img
|
||||
src={node.attrs.rightSrc}
|
||||
alt={node.attrs.rightAlt || ''}
|
||||
className="rich-compare-node__img"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!selected && node.attrs.subtitle ? (
|
||||
<figcaption className="rich-compare-node__subtitle">{node.attrs.subtitle}</figcaption>
|
||||
) : null}
|
||||
|
||||
{selected ? (
|
||||
<div className="rich-compare-node__editor" contentEditable={false}>
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<label className="grid gap-2 text-sm text-slate-300">
|
||||
<span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Left alt text</span>
|
||||
<input
|
||||
value={node.attrs.leftAlt || ''}
|
||||
onChange={(event) => updateAttributes({ leftAlt: event.target.value })}
|
||||
placeholder="Describe the left image"
|
||||
className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="grid gap-2 text-sm text-slate-300">
|
||||
<span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Right alt text</span>
|
||||
<input
|
||||
value={node.attrs.rightAlt || ''}
|
||||
onChange={(event) => updateAttributes({ rightAlt: event.target.value })}
|
||||
placeholder="Describe the right image"
|
||||
className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="grid gap-2 text-sm text-slate-300">
|
||||
<span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Subtitle</span>
|
||||
<input
|
||||
value={node.attrs.subtitle || ''}
|
||||
onChange={(event) => updateAttributes({ subtitle: event.target.value })}
|
||||
placeholder="Visible caption below the comparison"
|
||||
className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={selectNode}
|
||||
className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]"
|
||||
>
|
||||
Keep selected
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={deleteNode}
|
||||
className="rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm font-semibold text-rose-100 transition hover:bg-rose-400/15"
|
||||
>
|
||||
Remove comparison
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
const RichCompare = Node.create({
|
||||
name: 'imageCompare',
|
||||
group: 'block',
|
||||
atom: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
leftSrc: { default: '' },
|
||||
leftAlt: { default: '' },
|
||||
rightSrc: { default: '' },
|
||||
rightAlt: { default: '' },
|
||||
subtitle: { default: '' },
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'figure[data-rich-compare]',
|
||||
getAttrs: (element) => readImageAttrs(element),
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ node, HTMLAttributes }) {
|
||||
const {
|
||||
leftSrc: _leftSrc,
|
||||
leftAlt: _leftAlt,
|
||||
rightSrc: _rightSrc,
|
||||
rightAlt: _rightAlt,
|
||||
subtitle: _subtitle,
|
||||
...figureHTMLAttributes
|
||||
} = HTMLAttributes
|
||||
|
||||
const leftImageAttributes = {
|
||||
src: node.attrs.leftSrc,
|
||||
alt: node.attrs.leftAlt || '',
|
||||
loading: 'lazy',
|
||||
decoding: 'async',
|
||||
class: 'rich-compare-node__img',
|
||||
}
|
||||
|
||||
const rightImageAttributes = {
|
||||
src: node.attrs.rightSrc,
|
||||
alt: node.attrs.rightAlt || '',
|
||||
loading: 'lazy',
|
||||
decoding: 'async',
|
||||
class: 'rich-compare-node__img',
|
||||
}
|
||||
|
||||
return [
|
||||
'figure',
|
||||
mergeNodeAttributes(this.options.HTMLAttributes, figureHTMLAttributes, {
|
||||
'data-rich-compare': 'true',
|
||||
}),
|
||||
['div', { class: 'rich-compare-node__grid' },
|
||||
['div', { class: 'rich-compare-node__tile' }, ['img', leftImageAttributes]],
|
||||
['div', { class: 'rich-compare-node__tile' }, ['img', rightImageAttributes]],
|
||||
],
|
||||
...(node.attrs.subtitle ? [['figcaption', { class: 'rich-compare-node__subtitle' }, node.attrs.subtitle]] : []),
|
||||
]
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(RichCompareNodeView)
|
||||
},
|
||||
})
|
||||
|
||||
export default RichCompare
|
||||
Reference in New Issue
Block a user