@@ -25,6 +25,8 @@ import { Icons } from "../../toolbar/icons.js";
2525import { ReactNodeViewProps } from "../react/index.js" ;
2626import { ToolbarGroup } from "../../toolbar/components/toolbar-group.js" ;
2727import { DesktopOnly } from "../../components/responsive/index.js" ;
28+ import { ToolbarGroupDefinition } from "../../toolbar/types.js" ;
29+ import { toBlobURL , revokeBloburl } from "../../utils/downloader.js" ;
2830
2931export function AttachmentComponent (
3032 props : ReactNodeViewProps < FileAttachment | AudioAttachment >
@@ -50,15 +52,10 @@ export function AttachmentComponent(
5052 if ( data ) {
5153 // Convert base64 data to blob URL for audio playback
5254 try {
53- const byteCharacters = atob ( data . split ( "," ) [ 1 ] || data ) ;
54- const byteNumbers = new Array ( byteCharacters . length ) ;
55- for ( let i = 0 ; i < byteCharacters . length ; i ++ ) {
56- byteNumbers [ i ] = byteCharacters . charCodeAt ( i ) ;
55+ const url = toBlobURL ( data , "other" , mime , hash ) ;
56+ if ( url ) {
57+ setAudioSrc ( url ) ;
5758 }
58- const byteArray = new Uint8Array ( byteNumbers ) ;
59- const blob = new Blob ( [ byteArray ] , { type : mime } ) ;
60- const url = URL . createObjectURL ( blob ) ;
61- setAudioSrc ( url ) ;
6259 } catch ( error ) {
6360 console . error ( "Failed to create audio blob:" , error ) ;
6461 }
@@ -71,102 +68,94 @@ export function AttachmentComponent(
7168 // Clean up blob URL on unmount
7269 useEffect ( ( ) => {
7370 return ( ) => {
74- if ( audioSrc ) {
75- URL . revokeObjectURL ( audioSrc ) ;
71+ if ( audioSrc && hash ) {
72+ revokeBloburl ( hash ) ;
7673 }
7774 } ;
78- } , [ audioSrc ] ) ;
75+ } , [ audioSrc , hash ] ) ;
7976
80- if ( isAudioFile && audioSrc ) {
81- return (
82- < Box
83- ref = { elementRef }
77+ const getToolbarTools = ( ) : ToolbarGroupDefinition => {
78+ if ( isAudioFile ) {
79+ return editor . isEditable
80+ ? [ "removeAttachment" , "downloadAttachment" ]
81+ : [ "downloadAttachment" ] ;
82+ }
83+
84+ return editor . isEditable
85+ ? [ "removeAttachment" , "downloadAttachment" , "previewAttachment" ]
86+ : [ "downloadAttachment" , "previewAttachment" ] ;
87+ } ;
88+
89+ const renderFileInfo = ( ) => (
90+ < Box
91+ sx = { {
92+ display : "flex" ,
93+ alignItems : "center" ,
94+ mb : isAudioFile && audioSrc ? 1 : 0
95+ } }
96+ >
97+ < Icon path = { Icons . attachment } size = { isAudioFile && audioSrc ? 16 : 14 } />
98+ < Text
8499 as = "span"
85- contentEditable = { false }
86- variant = { "body" }
87100 sx = { {
88- display : "inline-flex" ,
89- flexDirection : "column" ,
90- position : "relative" ,
91- userSelect : "none" ,
92- backgroundColor : "var(--background-secondary)" ,
93- p : 2 ,
94- m : 1 ,
95- borderRadius : "default" ,
96- border : "1px solid var(--border)" ,
97- maxWidth : 350 ,
98- borderColor : selected ? "accent" : "border" ,
99- ":hover" : {
100- bg : "hover"
101- }
101+ ml : "small" ,
102+ fontSize : "body" ,
103+ whiteSpace : "nowrap" ,
104+ textOverflow : "ellipsis" ,
105+ overflow : "hidden" ,
106+ flex : isAudioFile && audioSrc ? 1 : "none"
102107 } }
103- data-drag-handle
104- onDragStart = { ( ) => setIsDragging ( true ) }
105- onDragEnd = { ( ) => setIsDragging ( false ) }
106108 >
107- < Box sx = { { display : "flex" , alignItems : "center" , mb : 1 } } >
108- < Icon path = { Icons . attachment } size = { 16 } />
109- < Text
110- as = "span"
111- sx = { {
112- ml : "small" ,
113- fontSize : "body" ,
114- whiteSpace : "nowrap" ,
115- textOverflow : "ellipsis" ,
116- overflow : "hidden" ,
117- flex : 1
118- } }
119- >
120- { filename }
121- </ Text >
122- < Text
123- as = "span"
124- sx = { {
125- ml : 1 ,
126- fontSize : "0.65rem" ,
127- color : "var(--paragraph-secondary)" ,
128- flexShrink : 0
129- } }
130- >
131- { progress ? `${ progress } %` : formatBytes ( size ) }
132- </ Text >
133- </ Box >
109+ { filename }
110+ </ Text >
111+ < Text
112+ as = "span"
113+ sx = { {
114+ ml : 1 ,
115+ fontSize : "0.65rem" ,
116+ color : "var(--paragraph-secondary)" ,
117+ flexShrink : 0
118+ } }
119+ >
120+ { progress ? `${ progress } %` : formatBytes ( size ) }
121+ </ Text >
122+ </ Box >
123+ ) ;
134124
135- < Box
125+ // Common toolbar component
126+ const renderToolbar = ( ) => (
127+ < DesktopOnly >
128+ { selected && ! isDragging && (
129+ < ToolbarGroup
130+ editor = { editor }
131+ groupId = "attachmentTools"
132+ tools = { getToolbarTools ( ) }
136133 sx = { {
137- width : "100% " ,
138- "& audio" : {
139- width : "100% " ,
140- height : "32px"
141- }
134+ boxShadow : "menu " ,
135+ borderRadius : "default" ,
136+ bg : "background " ,
137+ position : "absolute" ,
138+ top : - 35
142139 } }
143- >
144- < audio controls preload = "metadata" src = { audioSrc } />
145- </ Box >
140+ />
141+ ) }
142+ </ DesktopOnly >
143+ ) ;
146144
147- < DesktopOnly >
148- { selected && ! isDragging && (
149- < ToolbarGroup
150- editor = { editor }
151- groupId = "attachmentTools"
152- tools = {
153- editor . isEditable
154- ? [ "removeAttachment" , "downloadAttachment" ]
155- : [ "downloadAttachment" ]
156- }
157- sx = { {
158- boxShadow : "menu" ,
159- borderRadius : "default" ,
160- bg : "background" ,
161- position : "absolute" ,
162- top : - 35
163- } }
164- />
165- ) }
166- </ DesktopOnly >
167- </ Box >
168- ) ;
169- }
145+ // Audio player section for audio files
146+ const renderAudioPlayer = ( ) => (
147+ < Box
148+ sx = { {
149+ width : "100%" ,
150+ "& audio" : {
151+ width : "100%" ,
152+ height : "32px"
153+ }
154+ } }
155+ >
156+ < audio controls preload = "metadata" src = { audioSrc } />
157+ </ Box >
158+ ) ;
170159
171160 return (
172161 < Box
@@ -177,74 +166,38 @@ export function AttachmentComponent(
177166 sx = { {
178167 display : "inline-flex" ,
179168 position : "relative" ,
180- justifyContent : "center" ,
181169 userSelect : "none" ,
182- alignItems : "center" ,
183170 backgroundColor : "var(--background-secondary)" ,
184- px : 1 ,
185171 m : 1 ,
186172 borderRadius : "default" ,
187173 border : "1px solid var(--border)" ,
188- cursor : "pointer" ,
189- maxWidth : 250 ,
190174 borderColor : selected ? "accent" : "border" ,
191175 ":hover" : {
192176 bg : "hover"
193- }
177+ } ,
178+ // Audio-specific overrides
179+ ...( isAudioFile && audioSrc
180+ ? {
181+ flexDirection : "column" ,
182+ p : 2 ,
183+ width : "50%" ,
184+ }
185+ : {
186+ justifyContent : "center" ,
187+ alignItems : "center" ,
188+ px : 1 ,
189+ cursor : "pointer" ,
190+ maxWidth : 250
191+ } )
194192 } }
195- title = { filename }
193+ title = { ! isAudioFile || ! audioSrc ? filename : undefined }
196194 onDragStart = { ( ) => setIsDragging ( true ) }
197195 onDragEnd = { ( ) => setIsDragging ( false ) }
198196 data-drag-handle
199197 >
200- < Icon path = { Icons . attachment } size = { 14 } />
201- < Text
202- as = "span"
203- sx = { {
204- ml : "small" ,
205- fontSize : "body" ,
206- whiteSpace : "nowrap" ,
207- textOverflow : "ellipsis" ,
208- overflow : "hidden"
209- } }
210- >
211- { filename }
212- </ Text >
213- < Text
214- as = "span"
215- sx = { {
216- ml : 1 ,
217- fontSize : "0.65rem" ,
218- color : "var(--paragraph-secondary)" ,
219- flexShrink : 0
220- } }
221- >
222- { progress ? `${ progress } %` : formatBytes ( size ) }
223- </ Text >
224- < DesktopOnly >
225- { selected && ! isDragging && (
226- < ToolbarGroup
227- editor = { editor }
228- groupId = "attachmentTools"
229- tools = {
230- editor . isEditable
231- ? [
232- "removeAttachment" ,
233- "downloadAttachment" ,
234- "previewAttachment"
235- ]
236- : [ "downloadAttachment" , "previewAttachment" ]
237- }
238- sx = { {
239- boxShadow : "menu" ,
240- borderRadius : "default" ,
241- bg : "background" ,
242- position : "absolute" ,
243- top : - 35
244- } }
245- />
246- ) }
247- </ DesktopOnly >
198+ { renderFileInfo ( ) }
199+ { isAudioFile && audioSrc && renderAudioPlayer ( ) }
200+ { renderToolbar ( ) }
248201 </ Box >
249202 ) ;
250203}
0 commit comments