UNPKG

claritykit-svelte

Version:

A comprehensive Svelte component library focused on accessibility, ADHD-optimized design, developer experience, and full SSR compatibility

225 lines (224 loc) • 9.77 kB
import { Extension } from '@tiptap/core'; import { Plugin, PluginKey } from '@tiptap/pm/state'; export const MediaUploadExtension = Extension.create({ name: 'mediaUpload', addOptions() { return { acceptedTypes: ['image/*', 'video/*', 'audio/*', '.pdf', '.doc', '.docx', '.txt'], maxFileSize: 10 * 1024 * 1024, // 10MB maxFiles: 5, uploadHandler: async (files) => { // Default mock upload handler return files.map(file => ({ id: `media_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, url: URL.createObjectURL(file), filename: file.name, size: file.size, type: file.type, thumbnail: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined, })); }, enableDragDrop: true, enablePasteUpload: true, }; }, addCommands() { return { uploadFiles: (files) => async ({ editor }) => { try { // Validate files const validFiles = this.validateFiles(files); if (validFiles.length === 0) return false; this.options.onUploadStart?.(validFiles); // Upload files const results = await this.options.uploadHandler(validFiles); // Insert media into editor results.forEach(media => { editor.commands.insertMedia(media); }); this.options.onUploadComplete?.(results); return true; } catch (error) { console.error('Upload failed:', error); this.options.onUploadError?.(error, files[0]); return false; } }, insertMedia: (media) => ({ commands }) => { if (media.type.startsWith('image/')) { return commands.insertContent(` <div class="media-block image-block" data-media-id="${media.id}"> <img src="${media.url}" alt="${media.filename}" style="max-width: 100%; height: auto;" data-filename="${media.filename}" data-size="${media.size}" /> <div class="media-caption" contenteditable="true" placeholder="Add a caption..."></div> </div> `); } else if (media.type.startsWith('video/')) { return commands.insertContent(` <div class="media-block video-block" data-media-id="${media.id}"> <video controls style="max-width: 100%; height: auto;" data-filename="${media.filename}" data-size="${media.size}"> <source src="${media.url}" type="${media.type}"> Your browser does not support the video tag. </video> <div class="media-caption" contenteditable="true" placeholder="Add a caption..."></div> </div> `); } else if (media.type.startsWith('audio/')) { return commands.insertContent(` <div class="media-block audio-block" data-media-id="${media.id}"> <audio controls style="width: 100%;" data-filename="${media.filename}" data-size="${media.size}"> <source src="${media.url}" type="${media.type}"> Your browser does not support the audio tag. </audio> <div class="media-info"> <span class="media-filename">${media.filename}</span> <span class="media-size">${this.formatFileSize(media.size)}</span> </div> <div class="media-caption" contenteditable="true" placeholder="Add a caption..."></div> </div> `); } else { // File attachment return commands.insertContent(` <div class="media-block file-block" data-media-id="${media.id}"> <div class="file-attachment"> <div class="file-icon">${this.getFileIcon(media.type)}</div> <div class="file-info"> <div class="file-name">${media.filename}</div> <div class="file-size">${this.formatFileSize(media.size)}</div> </div> <a href="${media.url}" download="${media.filename}" class="file-download"> šŸ“„ Download </a> </div> <div class="media-caption" contenteditable="true" placeholder="Add a caption..."></div> </div> `); } }, }; }, addProseMirrorPlugins() { return [ new Plugin({ key: new PluginKey('mediaUpload'), props: { handleDOMEvents: { // Handle drag and drop dragover: (view, event) => { if (!this.options.enableDragDrop) return false; event.preventDefault(); event.dataTransfer.dropEffect = 'copy'; // Add visual feedback const editorElement = view.dom.closest('.composer-editor'); editorElement?.classList.add('drag-over'); return true; }, dragleave: (view, event) => { if (!this.options.enableDragDrop) return false; // Remove visual feedback const editorElement = view.dom.closest('.composer-editor'); editorElement?.classList.remove('drag-over'); return false; }, drop: (view, event) => { if (!this.options.enableDragDrop) return false; event.preventDefault(); // Remove visual feedback const editorElement = view.dom.closest('.composer-editor'); editorElement?.classList.remove('drag-over'); const files = Array.from(event.dataTransfer?.files || []); if (files.length > 0) { this.editor.commands.uploadFiles(files); return true; } return false; }, // Handle paste upload paste: (view, event) => { if (!this.options.enablePasteUpload) return false; const items = Array.from(event.clipboardData?.items || []); const files = items .filter(item => item.kind === 'file') .map(item => item.getAsFile()) .filter(file => file !== null); if (files.length > 0) { event.preventDefault(); this.editor.commands.uploadFiles(files); return true; } return false; }, }, }, }), ]; }, // Helper methods validateFiles(files) { return files.filter(file => { // Check file count if (files.length > this.options.maxFiles) { console.warn(`Too many files. Maximum ${this.options.maxFiles} files allowed.`); return false; } // Check file size if (file.size > this.options.maxFileSize) { console.warn(`File too large: ${file.name} (${this.formatFileSize(file.size)} > ${this.formatFileSize(this.options.maxFileSize)})`); return false; } // Check file type const isValidType = this.options.acceptedTypes.some(type => { if (type.includes('*')) { const baseType = type.split('/')[0]; return file.type.startsWith(baseType); } return file.type === type || file.name.toLowerCase().endsWith(type); }); if (!isValidType) { console.warn(`Unsupported file type: ${file.type}`); return false; } return true; }); }, formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }, getFileIcon(type) { if (type.includes('pdf')) return 'šŸ“„'; if (type.includes('document') || type.includes('word')) return 'šŸ“'; if (type.includes('spreadsheet') || type.includes('excel')) return 'šŸ“Š'; if (type.includes('presentation') || type.includes('powerpoint')) return 'šŸ“½ļø'; if (type.includes('text')) return 'šŸ“ƒ'; if (type.includes('zip') || type.includes('archive')) return 'šŸ—œļø'; return 'šŸ“Ž'; }, }); export default MediaUploadExtension;