2727 border : 2px dashed # aaa ;
2828 border-radius : 5px ;
2929 resize : vertical;
30+ background-color : # fff ;
3031 }
3132 # output {
3233 background-color : white;
3334 border : 1px solid # ddd ;
3435 border-radius : 5px ;
3536 padding : 20px ;
36- max-height : 70 vh ;
37+ max-height : 75 vh ; /* Increased max-height slightly */
3738 overflow-y : auto;
3839 }
3940 .format {
4041 margin-bottom : 20px ;
4142 border-bottom : 1px solid # eee ;
4243 padding-bottom : 10px ;
4344 }
45+ .format : last-child {
46+ border-bottom : none; /* Remove border for the last item */
47+ margin-bottom : 0 ;
48+ padding-bottom : 0 ;
49+ }
4450 .format h2 {
4551 color : # 0066cc ;
52+ margin-top : 0 ;
4653 margin-bottom : 10px ;
54+ font-size : 1.1em ;
4755 }
4856 .format-content {
49- max-height : 200 px ;
57+ max-height : 300 px ; /* Increased max-height for text content */
5058 overflow-y : auto;
51- background-color : # f5f5f5 ;
52- border : 1px solid # ddd ;
59+ background-color : # f9f9f9 ; /* Slightly adjusted background */
60+ border : 1px solid # e0e0e0 ; /* Slightly adjusted border */
5361 border-radius : 3px ;
5462 padding : 10px ;
63+ font-size : 0.9em ; /* Slightly smaller font for content */
64+ }
65+ /* Style for the container holding file info + image */
66+ .file-entry {
67+ margin-bottom : 15px ;
68+ padding-bottom : 10px ;
69+ border-bottom : 1px dotted # ddd ;
70+ }
71+ .file-entry : last-child {
72+ margin-bottom : 0 ;
73+ padding-bottom : 0 ;
74+ border-bottom : none;
75+ }
76+ /* Style for displayed images */
77+ .pasted-image {
78+ max-width : 100% ;
79+ height : auto; /* Maintain aspect ratio */
80+ display : block; /* Ensure it takes its own line */
81+ margin-top : 10px ; /* Space below the text info */
82+ border : 1px solid # ccc ; /* Add a light border around images */
5583 }
5684 pre {
5785 margin : 0 ;
5886 white-space : pre-wrap;
5987 word-wrap : break-word;
88+ font-family : Consolas, Monaco, 'Andale Mono' , 'Ubuntu Mono' , monospace; /* Use monospace font */
89+ }
90+ .empty-format-note {
91+ font-style : italic;
92+ color : # 888 ;
93+ padding : 10px ;
6094 }
6195 </ style >
6296</ head >
6397< body >
6498 < h1 > Clipboard Format Viewer</ h1 >
65- < textarea id ="paste-area " placeholder ="Paste here or anywhere on the page "> </ textarea >
66- < div id ="output "> </ div >
99+ < textarea id ="paste-area " placeholder ="Paste anywhere on the page (Ctrl+V or Cmd+V) "> </ textarea >
100+ < div id ="output "> < p > Paste content to see the available clipboard formats and data. </ p > < /div >
67101
68102 < script >
69103 const pasteArea = document . getElementById ( 'paste-area' ) ;
70104 const output = document . getElementById ( 'output' ) ;
71105
72106 function handlePaste ( event ) {
107+ // Prevent the default paste behavior (e.g., pasting into the textarea)
73108 event . preventDefault ( ) ;
74109 const clipboardData = event . clipboardData || window . clipboardData ;
75- output . innerHTML = '' ;
110+ output . innerHTML = '' ; // Clear previous output
111+
112+ if ( ! clipboardData ) {
113+ output . innerHTML = '<p>Could not access clipboard data. Your browser might not support this feature or requires user permission.</p>' ;
114+ return ;
115+ }
76116
77- // Get available formats
78117 const formats = clipboardData . types ;
79-
118+ const files = clipboardData . files ; // Get files list once (for Files format)
119+ const items = clipboardData . items ; // Get items list once (for modern API access, especially images)
120+
121+ console . log ( "Clipboard Data:" , clipboardData ) ;
122+ console . log ( "Available Formats:" , formats ) ;
123+ console . log ( "Number of Files:" , files . length ) ;
124+ console . log ( "Number of Items:" , items ? items . length : 'N/A' ) ;
125+
126+ if ( formats . length === 0 && files . length === 0 && ( ! items || items . length === 0 ) ) {
127+ output . innerHTML = '<p>Paste detected, but no standard formats, files, or items were found in the clipboard data.</p>' ;
128+ return ;
129+ }
130+
131+ // --- Iterate through detected formats ---
80132 formats . forEach ( format => {
81133 const formatDiv = document . createElement ( 'div' ) ;
82134 formatDiv . className = 'format' ;
83-
135+
84136 const formatTitle = document . createElement ( 'h2' ) ;
85137 formatTitle . textContent = format ;
86138 formatDiv . appendChild ( formatTitle ) ;
87139
140+ // Wrapper for content (text or image)
88141 const formatContentWrapper = document . createElement ( 'div' ) ;
89- formatContentWrapper . className = 'format-content' ;
90-
91- const formatContent = document . createElement ( 'pre' ) ;
92- let content = clipboardData . getData ( format ) ;
93-
94- // Special handling for certain formats
95- if ( format === 'text/html' ) {
96- const tempElement = document . createElement ( 'div' ) ;
97- tempElement . innerHTML = content ;
98- content = tempElement . innerHTML ;
99- } else if ( format === 'Files' ) {
100- const files = clipboardData . files ;
101- content = Array . from ( files ) . map ( file => `${ file . name } (${ file . type } , ${ file . size } bytes)` ) . join ( '\n' ) ;
142+ let contentDisplayed = false ; // Flag to check if content was handled by special cases
143+
144+ // --- 1. Special Handling for "Files" format ---
145+ // This is common when pasting files from the OS.
146+ if ( format === 'Files' && files . length > 0 ) {
147+ const fileListContent = document . createElement ( 'div' ) ;
148+ fileListContent . className = 'format-content' ; // Apply styling
149+
150+ Array . from ( files ) . forEach ( file => {
151+ const fileContainer = document . createElement ( 'div' ) ;
152+ fileContainer . className = 'file-entry' ; // Container for each file's info + image
153+
154+ // Display file information text
155+ const fileInfo = document . createElement ( 'div' ) ;
156+ fileInfo . textContent = `${ file . name } (${ file . type || 'unknown type' } , ${ file . size } bytes)` ;
157+ fileContainer . appendChild ( fileInfo ) ;
158+
159+ // Check if the file is an image and display it below the text
160+ if ( file . type . startsWith ( 'image/' ) ) {
161+ try {
162+ const img = document . createElement ( 'img' ) ;
163+ const objectURL = URL . createObjectURL ( file ) ;
164+
165+ img . src = objectURL ;
166+ img . alt = `${ file . name } (Pasted Image)` ;
167+ img . className = 'pasted-image' ; // Apply image styles
168+
169+ // IMPORTANT: Revoke the object URL once the image is loaded (or fails)
170+ // to prevent memory leaks.
171+ img . onload = ( ) => {
172+ console . log ( `Image ${ file . name } loaded, revoking ${ objectURL } ` ) ;
173+ URL . revokeObjectURL ( objectURL ) ;
174+ } ;
175+ img . onerror = ( ) => {
176+ console . error ( "Error loading image:" , file . name ) ;
177+ URL . revokeObjectURL ( objectURL ) ; // Revoke even on error
178+ img . alt += ' (Error loading)' ; // Update alt text
179+ // Optionally add an error message element
180+ const errorMsg = document . createElement ( 'div' ) ;
181+ errorMsg . textContent = 'Could not load image preview.' ;
182+ errorMsg . style . color = 'red' ;
183+ fileContainer . appendChild ( errorMsg ) ;
184+ } ;
185+
186+ fileContainer . appendChild ( img ) ; // Append the image below the file info
187+ } catch ( err ) {
188+ console . error ( `Error creating object URL for ${ file . name } :` , err ) ;
189+ const errorMsg = document . createElement ( 'div' ) ;
190+ errorMsg . textContent = 'Error creating image preview.' ;
191+ errorMsg . style . color = 'red' ;
192+ fileContainer . appendChild ( errorMsg ) ;
193+ }
194+ }
195+ fileListContent . appendChild ( fileContainer ) ; // Append container for this file
196+ } ) ;
197+ formatContentWrapper . appendChild ( fileListContent ) ;
198+ contentDisplayed = true ;
102199 }
200+ // --- 2. Special Handling for "image/*" types (if not already handled by Files) ---
201+ // This is common when pasting image data directly (e.g., screenshots, copy image)
202+ // We use clipboardData.items for this, which is more reliable than getData for blobs.
203+ else if ( format . startsWith ( 'image/' ) && files . length === 0 && items ) {
204+ // Check items only if Files format wasn't present or empty
205+ let imageFromItemsDisplayed = false ;
206+ for ( let i = 0 ; i < items . length ; i ++ ) {
207+ const item = items [ i ] ;
208+ // Find the first item matching the current format that is a file/blob
209+ if ( item . kind === 'file' && item . type === format ) {
210+ const file = item . getAsFile ( ) ; // This gets the image data as a Blob
211+ if ( file ) {
212+ try {
213+ // Display image from item
214+ const img = document . createElement ( 'img' ) ;
215+ const objectURL = URL . createObjectURL ( file ) ;
103216
104- formatContent . textContent = content ;
105- formatContentWrapper . appendChild ( formatContent ) ;
106- formatDiv . appendChild ( formatContentWrapper ) ;
217+ img . src = objectURL ;
218+ img . alt = `Pasted image (${ format } , ${ file . size } bytes)` ;
219+ img . className = 'pasted-image' ; // Apply image styles
220+
221+ img . onload = ( ) => {
222+ console . log ( `Image from item ${ format } loaded, revoking ${ objectURL } ` ) ;
223+ URL . revokeObjectURL ( objectURL ) ;
224+ } ;
225+ img . onerror = ( ) => {
226+ console . error ( "Error loading image from item:" , format ) ;
227+ URL . revokeObjectURL ( objectURL ) ;
228+ img . alt += ' (Error loading)' ;
229+ const errorMsg = document . createElement ( 'div' ) ;
230+ errorMsg . textContent = 'Could not load image preview.' ;
231+ errorMsg . style . color = 'red' ;
232+ formatContentWrapper . appendChild ( errorMsg ) ;
233+ } ;
234+
235+ // Add a descriptive text above the image
236+ const imgInfo = document . createElement ( 'div' ) ;
237+ // Use file.size if available, otherwise indicate unknown size
238+ const sizeText = file . size ? `, ${ file . size } bytes` : '' ;
239+ imgInfo . textContent = `Image data (${ format } ${ sizeText } )` ;
240+ // No 'format-content' class needed here, just simple text
241+ formatContentWrapper . appendChild ( imgInfo ) ;
242+
243+ formatContentWrapper . appendChild ( img ) ; // Append image
244+ imageFromItemsDisplayed = true ;
245+ contentDisplayed = true ;
246+ break ; // Handle only the first matching item for this format
247+ } catch ( err ) {
248+ console . error ( `Error creating object URL for image item ${ format } :` , err ) ;
249+ const errorMsg = document . createElement ( 'div' ) ;
250+ errorMsg . textContent = 'Error creating image preview.' ;
251+ errorMsg . style . color = 'red' ;
252+ formatContentWrapper . appendChild ( errorMsg ) ;
253+ }
254+ }
255+ }
256+ }
257+ // If image wasn't found via items, fall through to default text handling below
258+ }
259+
260+ // --- 3. Default Handling (Text, HTML, Fallbacks) ---
261+ // Handles text/*, application/*, and serves as fallback if image handling failed
262+ if ( ! contentDisplayed ) {
263+ const formatContent = document . createElement ( 'pre' ) ;
264+ let content = '' ;
265+ let retrievalError = false ;
107266
108- output . appendChild ( formatDiv ) ;
267+ try {
268+ content = clipboardData . getData ( format ) ;
269+
270+ if ( format === 'Files' && files . length === 0 ) {
271+ // Explicitly handle Files format reported but with no actual files
272+ content = '(No files found in clipboard)' ;
273+ } else if ( format . startsWith ( 'image/' ) && files . length > 0 ) {
274+ // If it's an image format but Files were present, assume handled there
275+ content = '(Image data likely shown in the "Files" section above)' ;
276+ } else if ( content === '' && ! format . startsWith ( 'text/' ) ) {
277+ // If getData returns empty for non-text, it might be intentional or unavailable
278+ content = '(Empty or non-retrievable data for this format)' ;
279+ }
280+ // Note: text/html is displayed as raw source by default in <pre>
281+ formatContent . textContent = content ;
282+
283+ } catch ( e ) {
284+ retrievalError = true ;
285+ console . warn ( `Could not getData for format: ${ format } ` , e ) ;
286+ // Provide more context based on format type
287+ if ( format . startsWith ( 'image/' ) ) {
288+ content = '(Could not retrieve image data directly. It might be available via "Files" or Items API if present.)' ;
289+ } else if ( format === 'Files' ) {
290+ content = '(Error accessing file list details.)' ;
291+ } else {
292+ content = `(Unable to retrieve data directly: ${ e . message } )` ;
293+ }
294+ formatContent . textContent = content ;
295+ }
296+
297+ // Only add the <pre> element if there's content or an error message
298+ if ( content || retrievalError ) {
299+ formatContentWrapper . appendChild ( formatContent ) ;
300+ formatContentWrapper . className = 'format-content' ; // Add class for styling text content
301+ } else {
302+ // If formatContentWrapper is still empty, it means getData returned empty string
303+ // and it wasn't an explicit Files/Image fallback case. Add a note.
304+ const emptyNote = document . createElement ( 'div' ) ;
305+ emptyNote . textContent = '(No data retrieved or data is empty string)' ;
306+ emptyNote . className = 'empty-format-note' ;
307+ formatContentWrapper . appendChild ( emptyNote ) ;
308+ }
309+ }
310+
311+ // Append the content wrapper (containing text or image) to the format div
312+ formatDiv . appendChild ( formatContentWrapper ) ;
313+ output . appendChild ( formatDiv ) ; // Append the whole section for this format
109314 } ) ;
110315
111- // Additional information about the clipboard event
316+ // --- Additional Clipboard Event Information ---
112317 const eventInfo = document . createElement ( 'div' ) ;
113- eventInfo . className = 'format' ;
318+ eventInfo . className = 'format' ; // Reuse format styling
114319 eventInfo . innerHTML = `
115320 <h2>Clipboard Event Information</h2>
116321 <div class="format-content">
117322 <pre>
118323Event type: ${ event . type }
119- Formats available: ${ formats . join ( ', ' ) }
324+ Formats available (${ formats . length } ): ${ formats . join ( ', ' ) || 'None' }
325+ Number of files reported: ${ files . length }
326+ Number of clipboard items: ${ items ? items . length : 'N/A (Items API not supported/available)' }
120327 </pre>
121328 </div>
122329 ` ;
123- output . appendChild ( eventInfo ) ;
330+ // Append event info only if there was some content pasted initially
331+ if ( output . hasChildNodes ( ) ) {
332+ output . appendChild ( eventInfo ) ;
333+ } else if ( ! output . innerHTML ) { // Double check if output is truly empty
334+ output . innerHTML = '<p>Paste detected, but no data could be read or displayed.</p>' ;
335+ }
124336 }
125337
126- // Listen for paste events on the textarea
338+ // Listen for paste events on the textarea (useful if focused)
127339 pasteArea . addEventListener ( 'paste' , handlePaste ) ;
128340
129- // Listen for paste events on the entire document
341+ // Listen for paste events on the entire document (allows pasting anywhere)
130342 document . addEventListener ( 'paste' , handlePaste ) ;
343+
344+ // Initial message update
345+ if ( navigator . clipboard && typeof navigator . clipboard . read !== 'function' ) {
346+ const warning = document . createElement ( 'p' ) ;
347+ warning . style . color = 'orange' ;
348+ warning . textContent = 'Warning: Your browser might not fully support the modern Clipboard API (read permission). Pasting might rely on the legacy event method.' ;
349+ output . appendChild ( warning ) ;
350+ }
131351 </ script >
132352</ body >
133- </ html >
353+ </ html >
0 commit comments