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
JavaScript
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;