diff --git a/src/extensions/Attachment/Attachment.ts b/src/extensions/Attachment/Attachment.ts index d9f9207..df9f25b 100644 --- a/src/extensions/Attachment/Attachment.ts +++ b/src/extensions/Attachment/Attachment.ts @@ -5,6 +5,8 @@ import { getDatasetAttribute } from '@/utils/dom-dataset' import { NodeViewAttachment } from '@/extensions/Attachment/components/NodeViewAttachment/NodeViewAttachment' import { ActionButton } from '@/components' import type { GeneralOptions } from '@/types' +import { getFileTypeIcon } from '@/extensions/Attachment/components/NodeViewAttachment/FileIcon' +import { normalizeFileSize } from '@/utils/file' declare module '@tiptap/core' { interface Commands { @@ -52,8 +54,43 @@ export const Attachment = Node.create({ }, renderHTML({ HTMLAttributes }) { - // @ts-expect-error - return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)] + // Destructure and provide fallback defaults + const { + url = '', + fileName = '', + fileSize = '', + fileType = '', + fileExt = '', + } = HTMLAttributes || {} + + // Validate attributes and merge safely + const mergedAttributes = mergeAttributes( + // @ts-expect-error + this.options.HTMLAttributes || {}, + HTMLAttributes || {}, + ) + + // Return the structured array + return [ + 'div', + mergedAttributes, + url + ? [ + 'a', + { href: url || '#' }, + [ + 'span', + { class: 'attachment__icon' }, + getFileTypeIcon(fileType, true), + ], + [ + 'span', + { class: 'attachment__text' }, + `${fileName}.${fileExt} (${normalizeFileSize(fileSize)})`, + ], + ] + : ['div', { class: 'attachment__placeholder' }], + ] }, addAttributes() { diff --git a/src/extensions/Attachment/components/NodeViewAttachment/FileIcon.tsx b/src/extensions/Attachment/components/NodeViewAttachment/FileIcon.tsx index aa743f5..76da579 100644 --- a/src/extensions/Attachment/components/NodeViewAttachment/FileIcon.tsx +++ b/src/extensions/Attachment/components/NodeViewAttachment/FileIcon.tsx @@ -1,38 +1,67 @@ import { LucideAudioLines, LucideFile, LucideImage, LucideSheet, LucideTableProperties, LucideVideo } from 'lucide-react' +import ReactDOMServer from 'react-dom/server' import { normalizeFileType } from '@/utils/file' import { ExportPdf } from '@/components/icons/ExportPdf' import ExportWord from '@/components/icons/ExportWord' -export function getFileTypeIcon(fileType: string) { - const type = normalizeFileType(fileType) +function iconToProseMirror(icon: JSX.Element) { + // Render SVG as a static string + const svgString = ReactDOMServer.renderToStaticMarkup(icon) - switch (type) { - case 'audio': - return + // Parse the string into ProseMirror-compatible structure + const parser = new DOMParser() + const svgDocument = parser.parseFromString(svgString, 'image/svg+xml') + const svgElement = svgDocument.documentElement - case 'video': - return + const iconToReturn = [ + 'svg', + { + ...Array.from(svgElement.attributes).reduce((acc: any, attr: any) => { + acc[attr.name] = attr.value + return acc + }, {}), + }, + ] - case 'file': - return + Array.from(svgElement.childNodes).forEach((child: any) => { + if (child.nodeType === 1) { + // Element node + const childElement = [ + child.tagName.toLowerCase(), + Array.from(child.attributes).reduce((acc: any, attr: any) => { + acc[attr.name] = attr.value + return acc + }, {}), + ] - case 'image': - return + if (child.textContent) { + childElement.push(child.textContent) + } - case 'pdf': - return + iconToReturn.push(childElement) + } + }) - case 'word': - return + return iconToReturn +} - case 'excel': - return +// React components for rendering directly in JSX +const icons = { + audio: , + video: , + file: , + image: , + pdf: , + word: , + excel: , + ppt: , +} - case 'ppt': - return +export function getFileTypeIcon(fileType: string, forProseMirror = false) { + const type = normalizeFileType(fileType) - default: { - return <> - } - } + const icon = icons[type] || <> + + // Return ProseMirror-compatible structure or React component + return forProseMirror ? iconToProseMirror(icon) : icon } diff --git a/src/extensions/Attachment/components/NodeViewAttachment/index.module.scss b/src/extensions/Attachment/components/NodeViewAttachment/index.module.scss index 5833853..12a5995 100644 --- a/src/extensions/Attachment/components/NodeViewAttachment/index.module.scss +++ b/src/extensions/Attachment/components/NodeViewAttachment/index.module.scss @@ -1,4 +1,5 @@ -.wrap { +.attachment, /* for browser ready HTML */ +.wrap { /* for NodeView */ border-width: 1px !important; border-radius: 4px; padding: 10px; @@ -6,4 +7,17 @@ align-items: center; justify-content: space-between; margin: 10px 0; + + :global { + .attachment__icon { + width: 32px; + text-align: center; + } + + .attachment__icon svg { + width: 32px; + display: inline-block; + } + } } +