@@ -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 >
@@ -35,10 +37,8 @@ export function AttachmentComponent(
3537 const [ isDragging , setIsDragging ] = useState ( false ) ;
3638 const [ audioSrc , setAudioSrc ] = useState < string > ( ) ;
3739
38- // Check if this is an audio file
3940 const isAudioFile = mime && mime . startsWith ( "audio/" ) ;
4041
41- // Load audio data if it's an audio file
4242 useEffect ( ( ) => {
4343 if ( isAudioFile && editor . storage ?. getAttachmentData && hash ) {
4444 editor . storage
@@ -48,17 +48,11 @@ export function AttachmentComponent(
4848 } )
4949 . then ( ( data : string | undefined ) => {
5050 if ( data ) {
51- // Convert base64 data to blob URL for audio playback
5251 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 ) ;
52+ const url = toBlobURL ( data , "other" , mime , hash ) ;
53+ if ( url ) {
54+ setAudioSrc ( url ) ;
5755 }
58- const byteArray = new Uint8Array ( byteNumbers ) ;
59- const blob = new Blob ( [ byteArray ] , { type : mime } ) ;
60- const url = URL . createObjectURL ( blob ) ;
61- setAudioSrc ( url ) ;
6256 } catch ( error ) {
6357 console . error ( "Failed to create audio blob:" , error ) ;
6458 }
@@ -68,105 +62,13 @@ export function AttachmentComponent(
6862 }
6963 } , [ isAudioFile , editor . storage , hash , mime ] ) ;
7064
71- // Clean up blob URL on unmount
7265 useEffect ( ( ) => {
7366 return ( ) => {
74- if ( audioSrc ) {
75- URL . revokeObjectURL ( audioSrc ) ;
67+ if ( audioSrc && hash ) {
68+ revokeBloburl ( hash ) ;
7669 }
7770 } ;
78- } , [ audioSrc ] ) ;
79-
80- if ( isAudioFile && audioSrc ) {
81- return (
82- < Box
83- ref = { elementRef }
84- as = "span"
85- contentEditable = { false }
86- variant = { "body" }
87- 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- }
102- } }
103- data-drag-handle
104- onDragStart = { ( ) => setIsDragging ( true ) }
105- onDragEnd = { ( ) => setIsDragging ( false ) }
106- >
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 >
134-
135- < Box
136- sx = { {
137- width : "100%" ,
138- "& audio" : {
139- width : "100%" ,
140- height : "32px"
141- }
142- } }
143- >
144- < audio controls preload = "metadata" src = { audioSrc } />
145- </ Box >
146-
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- }
71+ } , [ audioSrc , hash ] ) ;
17072
17173 return (
17274 < Box
@@ -177,57 +79,94 @@ export function AttachmentComponent(
17779 sx = { {
17880 display : "inline-flex" ,
17981 position : "relative" ,
180- justifyContent : "center" ,
18182 userSelect : "none" ,
182- alignItems : "center" ,
18383 backgroundColor : "var(--background-secondary)" ,
184- px : 1 ,
18584 m : 1 ,
18685 borderRadius : "default" ,
18786 border : "1px solid var(--border)" ,
188- cursor : "pointer" ,
189- maxWidth : 250 ,
19087 borderColor : selected ? "accent" : "border" ,
19188 ":hover" : {
19289 bg : "hover"
193- }
90+ } ,
91+ ...( isAudioFile && audioSrc
92+ ? {
93+ flexDirection : "column" ,
94+ p : 2 ,
95+ width : "50%"
96+ }
97+ : {
98+ justifyContent : "center" ,
99+ alignItems : "center" ,
100+ px : 1 ,
101+ cursor : "pointer" ,
102+ maxWidth : 250
103+ } )
194104 } }
195- title = { filename }
105+ title = { ! isAudioFile || ! audioSrc ? filename : undefined }
196106 onDragStart = { ( ) => setIsDragging ( true ) }
197107 onDragEnd = { ( ) => setIsDragging ( false ) }
198108 data-drag-handle
199109 >
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"
110+ < Box
215111 sx = { {
216- ml : 1 ,
217- fontSize : "0.65rem" ,
218- color : "var(--paragraph-secondary)" ,
219- flexShrink : 0
112+ display : "flex" ,
113+ alignItems : "center" ,
114+ mb : isAudioFile && audioSrc ? 1 : 0
220115 } }
221116 >
222- { progress ? `${ progress } %` : formatBytes ( size ) }
223- </ Text >
117+ < Icon
118+ path = { Icons . attachment }
119+ size = { isAudioFile && audioSrc ? 16 : 14 }
120+ />
121+ < Text
122+ as = "span"
123+ sx = { {
124+ ml : "small" ,
125+ fontSize : "body" ,
126+ whiteSpace : "nowrap" ,
127+ textOverflow : "ellipsis" ,
128+ overflow : "hidden" ,
129+ flex : isAudioFile && audioSrc ? 1 : "none"
130+ } }
131+ >
132+ { filename }
133+ </ Text >
134+ < Text
135+ as = "span"
136+ sx = { {
137+ ml : 1 ,
138+ fontSize : "0.65rem" ,
139+ color : "var(--paragraph-secondary)" ,
140+ flexShrink : 0
141+ } }
142+ >
143+ { progress ? `${ progress } %` : formatBytes ( size ) }
144+ </ Text >
145+ </ Box >
146+ { isAudioFile && audioSrc && (
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+ ) }
224159 < DesktopOnly >
225160 { selected && ! isDragging && (
226161 < ToolbarGroup
227162 editor = { editor }
228163 groupId = "attachmentTools"
229164 tools = {
230- editor . isEditable
165+ isAudioFile
166+ ? editor . isEditable
167+ ? [ "removeAttachment" , "downloadAttachment" ]
168+ : [ "downloadAttachment" ]
169+ : editor . isEditable
231170 ? [
232171 "removeAttachment" ,
233172 "downloadAttachment" ,
0 commit comments