Skip to content

Commit d2f7ad3

Browse files
authored
Merge pull request #120 from hunghg255/extension-attachment
2 parents 91a169d + 269587a commit d2f7ad3

File tree

3 files changed

+106
-26
lines changed

3 files changed

+106
-26
lines changed

src/extensions/Attachment/Attachment.ts

+39-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { getDatasetAttribute } from '@/utils/dom-dataset'
55
import { NodeViewAttachment } from '@/extensions/Attachment/components/NodeViewAttachment/NodeViewAttachment'
66
import { ActionButton } from '@/components'
77
import type { GeneralOptions } from '@/types'
8+
import { getFileTypeIcon } from '@/extensions/Attachment/components/NodeViewAttachment/FileIcon'
9+
import { normalizeFileSize } from '@/utils/file'
810

911
declare module '@tiptap/core' {
1012
interface Commands<ReturnType> {
@@ -52,8 +54,43 @@ export const Attachment = Node.create<AttachmentOptions>({
5254
},
5355

5456
renderHTML({ HTMLAttributes }) {
55-
// @ts-expect-error
56-
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)]
57+
// Destructure and provide fallback defaults
58+
const {
59+
url = '',
60+
fileName = '',
61+
fileSize = '',
62+
fileType = '',
63+
fileExt = '',
64+
} = HTMLAttributes || {}
65+
66+
// Validate attributes and merge safely
67+
const mergedAttributes = mergeAttributes(
68+
// @ts-expect-error
69+
this.options.HTMLAttributes || {},
70+
HTMLAttributes || {},
71+
)
72+
73+
// Return the structured array
74+
return [
75+
'div',
76+
mergedAttributes,
77+
url
78+
? [
79+
'a',
80+
{ href: url || '#' },
81+
[
82+
'span',
83+
{ class: 'attachment__icon' },
84+
getFileTypeIcon(fileType, true),
85+
],
86+
[
87+
'span',
88+
{ class: 'attachment__text' },
89+
`${fileName}.${fileExt} (${normalizeFileSize(fileSize)})`,
90+
],
91+
]
92+
: ['div', { class: 'attachment__placeholder' }],
93+
]
5794
},
5895

5996
addAttributes() {
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,67 @@
11
import { LucideAudioLines, LucideFile, LucideImage, LucideSheet, LucideTableProperties, LucideVideo } from 'lucide-react'
2+
import ReactDOMServer from 'react-dom/server'
23
import { normalizeFileType } from '@/utils/file'
34
import { ExportPdf } from '@/components/icons/ExportPdf'
45
import ExportWord from '@/components/icons/ExportWord'
56

6-
export function getFileTypeIcon(fileType: string) {
7-
const type = normalizeFileType(fileType)
7+
function iconToProseMirror(icon: JSX.Element) {
8+
// Render SVG as a static string
9+
const svgString = ReactDOMServer.renderToStaticMarkup(icon)
810

9-
switch (type) {
10-
case 'audio':
11-
return <LucideAudioLines />
11+
// Parse the string into ProseMirror-compatible structure
12+
const parser = new DOMParser()
13+
const svgDocument = parser.parseFromString(svgString, 'image/svg+xml')
14+
const svgElement = svgDocument.documentElement
1215

13-
case 'video':
14-
return <LucideVideo />
16+
const iconToReturn = [
17+
'svg',
18+
{
19+
...Array.from(svgElement.attributes).reduce((acc: any, attr: any) => {
20+
acc[attr.name] = attr.value
21+
return acc
22+
}, {}),
23+
},
24+
]
1525

16-
case 'file':
17-
return <LucideFile />
26+
Array.from(svgElement.childNodes).forEach((child: any) => {
27+
if (child.nodeType === 1) {
28+
// Element node
29+
const childElement = [
30+
child.tagName.toLowerCase(),
31+
Array.from(child.attributes).reduce((acc: any, attr: any) => {
32+
acc[attr.name] = attr.value
33+
return acc
34+
}, {}),
35+
]
1836

19-
case 'image':
20-
return <LucideImage />
37+
if (child.textContent) {
38+
childElement.push(child.textContent)
39+
}
2140

22-
case 'pdf':
23-
return <ExportPdf />
41+
iconToReturn.push(childElement)
42+
}
43+
})
2444

25-
case 'word':
26-
return <ExportWord />
45+
return iconToReturn
46+
}
2747

28-
case 'excel':
29-
return <LucideSheet />
48+
// React components for rendering directly in JSX
49+
const icons = {
50+
audio: <LucideAudioLines />,
51+
video: <LucideVideo />,
52+
file: <LucideFile />,
53+
image: <LucideImage />,
54+
pdf: <ExportPdf />,
55+
word: <ExportWord />,
56+
excel: <LucideSheet />,
57+
ppt: <LucideTableProperties />,
58+
}
3059

31-
case 'ppt':
32-
return <LucideTableProperties />
60+
export function getFileTypeIcon(fileType: string, forProseMirror = false) {
61+
const type = normalizeFileType(fileType)
3362

34-
default: {
35-
return <></>
36-
}
37-
}
63+
const icon = icons[type] || <></>
64+
65+
// Return ProseMirror-compatible structure or React component
66+
return forProseMirror ? iconToProseMirror(icon) : icon
3867
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
1-
.wrap {
1+
.attachment, /* for browser ready HTML */
2+
.wrap { /* for NodeView */
23
border-width: 1px !important;
34
border-radius: 4px;
45
padding: 10px;
56
display: flex;
67
align-items: center;
78
justify-content: space-between;
89
margin: 10px 0;
10+
11+
:global {
12+
.attachment__icon {
13+
width: 32px;
14+
text-align: center;
15+
}
16+
17+
.attachment__icon svg {
18+
width: 32px;
19+
display: inline-block;
20+
}
21+
}
922
}
23+

0 commit comments

Comments
 (0)