diff --git a/src/components/editor/Editor.tsx b/src/components/editor/Editor.tsx index 33fc9dc..5b26b3a 100644 --- a/src/components/editor/Editor.tsx +++ b/src/components/editor/Editor.tsx @@ -7,6 +7,7 @@ import SelectComponent from './SelectComponent'; import CustomToolbar from './toolbar/CustomToolbar'; import { saveArticle } from '../../api/article'; import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model'; + import Bold from '@tiptap/extension-bold'; import Italic from '@tiptap/extension-italic'; import Strike from '@tiptap/extension-strike'; @@ -72,9 +73,12 @@ const Editor = ({ content }: { content: JSONContent[] | null }) => { Bold, Italic, Strike, + Underline, Text, + TextStyle, ListItem, BulletList.configure({ + keepAttributes: true, HTMLAttributes: { class: 'list-disc px-6', }, @@ -98,12 +102,27 @@ const Editor = ({ content }: { content: JSONContent[] | null }) => { }), Table.configure({ resizable: true, + HTMLAttributes: { + class: 'border-collapse w-full table-fixed overflow-hidden', + }, + }), + TableHeader.configure({ + HTMLAttributes: { + class: + 'bg-[rgba(61,37,20,0.05)] border border-[rgba(61,37,20,0.12)] box-border min-w-[1em] px-2 py-1 relative align-middle', + }, + }), + TableRow.configure({ + HTMLAttributes: { + class: 'h-10', + }, + }), + TableCell.configure({ + HTMLAttributes: { + class: + 'border border-[rgba(61,37,20,0.12)] box-border min-w-[1em] px-2 py-1 relative align-middle', + }, }), - TextStyle, - Underline, - TableHeader, - TableRow, - TableCell, CustomBlock, CustomParagraph, @@ -127,10 +146,17 @@ const Editor = ({ content }: { content: JSONContent[] | null }) => { editorProps: { handlePaste(view, event) { const html = event.clipboardData?.getData('text/html'); + console.log('html', html); if (html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const body = doc.body; + console.log('body', body); + body.innerHTML = body.innerHTML.replace(/\uFEFF/g, '').trim(); + + body.querySelectorAll('span[data-input-buffer]').forEach((span) => { + span.remove(); + }); body .querySelectorAll('.se-section-quotation .se-cite') @@ -141,6 +167,29 @@ const Editor = ({ content }: { content: JSONContent[] | null }) => { } }); + body + .querySelectorAll('.se-table-control') + .forEach((controlBarElement) => { + controlBarElement.remove(); + }); + + body.querySelectorAll('table').forEach((tableElement) => { + tableElement.querySelectorAll('tr').forEach((trElement, index) => { + if (index === 0) { + trElement.querySelectorAll('td').forEach((tdElement) => { + const thElement = document.createElement('th'); + thElement.innerHTML = tdElement.innerHTML; + trElement.replaceChild(thElement, tdElement); + }); + } + }); + }); + body + .querySelectorAll('.se-cell-context-menu') + .forEach((controlBarElement) => { + controlBarElement.remove(); + }); + const fragment = ProseMirrorDOMParser.fromSchema( view.state.schema, ).parse(body); @@ -216,37 +265,45 @@ const Editor = ({ content }: { content: JSONContent[] | null }) => { )} -
+
-
-

- - setArticleData({ ...articleData, title: e.target.value }) - } - /> -

- editor?.commands.focus()} - className="w-full p-4" - /> +
+
+
+
+

+ + setArticleData({ ...articleData, title: e.target.value }) + } + /> +

+ +
+
+ editor?.commands.focus()} + className="w-full px-10 pt-4" + /> +
+
+ {showPreview && ( + setShowPreview(false)} + editor={editor} + /> + )}
- {showPreview && ( - setShowPreview(false)} - editor={editor} - /> - )}
); diff --git a/src/components/editor/SelectComponent.tsx b/src/components/editor/SelectComponent.tsx index a7a01a5..08c5eb5 100644 --- a/src/components/editor/SelectComponent.tsx +++ b/src/components/editor/SelectComponent.tsx @@ -22,25 +22,23 @@ const SelectComponent: React.FC = ({ onChange, }) => { return ( -
+
{data.map(({ label, value, options }) => ( -
+
-
- -
+
))}
diff --git a/src/components/editor/common/extractPaywallData.ts b/src/components/editor/common/extractPaywallData.ts index ed27a5d..92a2ed8 100644 --- a/src/components/editor/common/extractPaywallData.ts +++ b/src/components/editor/common/extractPaywallData.ts @@ -47,6 +47,41 @@ const extractPaywallData = (editor: Editor): PaywallData => { alignment: node.attrs.alignment, } as LinkAttributes); tempDiv.appendChild(linkElement); + } else if (node.type === 'table') { + const tableWrapper = document.createElement('div'); + tableWrapper.className = + 'mt-[30px] relative max-w-[680px] mx-auto px-5 w-full'; + tableWrapper.style.width = '100%'; + + const pmNode = schema.nodeFromJSON(node); + const serializedNode = domSerializer.serializeNode(pmNode); + tableWrapper.appendChild(serializedNode); + + tempDiv.appendChild(tableWrapper); + } else if (node.type === 'customBlock') { + const blockWrapper = document.createElement('div'); + blockWrapper.className = + 'component-text mt-10 relative px-[44px] mx-[-44px]'; + + if (node.content && node.content.length > 0) { + node.content.forEach((childNode: JSONContent) => { + if ( + childNode.type === 'paragraph' && + (!childNode.content || childNode.content.length === 0) + ) { + const emptyParagraph = document.createElement('p'); + emptyParagraph.className = 'text-center text-[19px]'; + emptyParagraph.style.lineHeight = '1.8'; + emptyParagraph.appendChild(document.createElement('br')); + blockWrapper.appendChild(emptyParagraph); + } else { + const pmNode = schema.nodeFromJSON(childNode); + const serializedNode = domSerializer.serializeNode(pmNode); + blockWrapper.appendChild(serializedNode); + } + }); + } + tempDiv.appendChild(blockWrapper); } else { const pmNode = schema.nodeFromJSON(node); const serializedNode = domSerializer.serializeNode(pmNode); diff --git a/src/components/editor/customComponent/CustomBlock.ts b/src/components/editor/customComponent/CustomBlock.ts index c4ab4c4..f0a18b8 100644 --- a/src/components/editor/customComponent/CustomBlock.ts +++ b/src/components/editor/customComponent/CustomBlock.ts @@ -8,13 +8,13 @@ export const CustomBlock = Node.create({ content: 'block+', parseHTML() { - return [{ tag: 'div.se-component.se-text.se-l-default' }]; + return [{ tag: 'div.se-section.se-section-text.se-l-default' }]; }, renderHTML({ HTMLAttributes }) { return [ 'div', mergeAttributes(HTMLAttributes, { - class: 'component-text mt-10', + class: 'component-text mt-10 relative px-[44px] mx-[-44px]', }), 0, ]; diff --git a/src/components/editor/customComponent/CustomPhoto.ts b/src/components/editor/customComponent/CustomPhoto.ts index 74680b1..91cabe0 100644 --- a/src/components/editor/customComponent/CustomPhoto.ts +++ b/src/components/editor/customComponent/CustomPhoto.ts @@ -13,6 +13,7 @@ const CustomPhoto = Node.create({ width: { default: '680' }, alignment: { default: 'mr-auto ml-0' }, caption: { default: '' }, + style: { default: '' }, }; }, @@ -21,6 +22,10 @@ const CustomPhoto = Node.create({ { tag: 'div.se-section.se-section-image', getAttrs: (element) => { + const widthStyle = element?.getAttribute('style') || ''; + const widthMatch = widthStyle.match(/max-width:\s*([\d.]+)px/); + const style = widthMatch ? `${widthMatch[1]}px` : 'w-full'; + const imgElement = element.querySelector('img.se-image-resource'); const src = imgElement?.getAttribute('src') || ''; const alt = imgElement?.getAttribute('alt') || ''; @@ -46,6 +51,7 @@ const CustomPhoto = Node.create({ width, alignment, caption, + style, }; }, }, @@ -56,7 +62,7 @@ const CustomPhoto = Node.create({ return [ 'div', { - class: `w-full relative ${HTMLAttributes.alignment} mt-[30px]`, + class: `relative mt-10 ${HTMLAttributes.alignment}`, }, [ 'img', @@ -64,7 +70,7 @@ const CustomPhoto = Node.create({ src: HTMLAttributes.src, alt: HTMLAttributes.alt, width: HTMLAttributes.width, - class: 'block w-full relative h-auto', + class: `block relative h-auto ${HTMLAttributes.style}`, }, ], diff --git a/src/components/editor/customComponent/CustomPhotoStrip.ts b/src/components/editor/customComponent/CustomPhotoStrip.ts index 483d354..2b9aafe 100644 --- a/src/components/editor/customComponent/CustomPhotoStrip.ts +++ b/src/components/editor/customComponent/CustomPhotoStrip.ts @@ -27,14 +27,23 @@ const CustomPhotoStrip = Node.create({ { tag: 'div.se-section.se-section-imageStrip.se-l-default', getAttrs: (element: Element) => { - const images: ImageAttributes[] = - Array.from(element.querySelectorAll('img.se-image-resource')).map( - (img) => ({ - src: img.getAttribute('src') || '', - alt: img.getAttribute('alt') || '', - width: img.getAttribute('width') || '680', - }), - ) ?? []; + const images: ImageAttributes[] = Array.from( + element.querySelectorAll('.se-module-image'), + ).map((imgWrapper) => { + const imgElement = imgWrapper.querySelector( + 'img.se-image-resource', + ); + const widthStyle = imgWrapper.getAttribute('style') || ''; + + const widthMatch = widthStyle.match(/width:\s*([\d.]+)%/); + const width = widthMatch ? `${widthMatch[1]}%` : 'auto'; + + return { + src: imgElement?.getAttribute('src') || '', + alt: imgElement?.getAttribute('alt') || '', + width, + }; + }); const alignment = element.classList.contains( 'se-section-align-center', @@ -60,19 +69,28 @@ const CustomPhotoStrip = Node.create({ return [ 'div', { - class: `w-full relative ${HTMLAttributes.alignment} mt-[30px]`, + class: `w-full relative ${HTMLAttributes.alignment} mt-[20px]`, }, [ 'div', - { class: 'flex gap-2' }, + { + class: 'flex relative gap-2', + }, ...(HTMLAttributes.images ?? []).map((img: ImageAttributes) => [ - 'img', + 'div', { - src: img.src, - alt: img.alt, - width: img.width, - class: 'block w-[48%] h-auto', + class: 'relative', + style: `width: ${img.width};`, }, + [ + 'img', + { + src: img.src, + alt: img.alt, + width: img.width, + class: 'block relative w-full h-auto', + }, + ], ]), ], ...(HTMLAttributes.caption !== '사진 설명을 입력하세요.' diff --git a/src/components/editor/icons/CustomButtons.tsx b/src/components/editor/icons/CustomButtons.tsx index d0411ed..60d5152 100644 --- a/src/components/editor/icons/CustomButtons.tsx +++ b/src/components/editor/icons/CustomButtons.tsx @@ -30,9 +30,16 @@ const CustomIcon = { Quote: ({ editor }: { editor: Editor }) => (