export default class VideoTool { static get toolbox() { return { icon: ``, title: 'Video', }; } constructor({ data, api, config }) { this.data = data || {}; this.api = api; this.config = config || {}; this.wrapper = null; this.alignment = data.alignment || 'center'; this.width = data.width || '100%'; // Привязываем контекст для обработчиков this._handleFileUpload = this._handleFileUpload.bind(this); this._initResize = this._initResize.bind(this); this._handleUrlSubmit = this._handleUrlSubmit.bind(this); // Added this binding } render() { this.wrapper = document.createElement('div'); this.wrapper.classList.add('video-tool'); if (this.data.url) { this._createVideoElement(this.data.url, this.data.caption || ''); } else { this._createUploadForm(); } return this.wrapper; } _createUploadForm() { this.wrapper.innerHTML = `
`; // Tab switching this.wrapper.querySelectorAll('.tab-button').forEach(btn => { btn.addEventListener('click', () => { this.wrapper.querySelectorAll('.tab-button, .tab-content').forEach(el => { el.classList.toggle('active', el.dataset.tab === btn.dataset.tab); }); }); }); // File upload handler this.wrapper.querySelector('.video-file-input') .addEventListener('change', this._handleFileUpload); // URL embed handler this.wrapper.querySelector('.embed-submit') .addEventListener('click', this._handleUrlSubmit); } async _handleFileUpload(event) { const file = event.target.files[0]; if (!file) return; if (!this.config.uploader || !this.config.uploader.byFile) { this._showError('File upload is not configured'); return; } try { const url = await this._uploadFile(file); this.type = 'file'; this.data = { url, type: 'file' }; his._createVideoElement(url, ''); } catch (error) { this._showError('File upload failed: ' + error.message); } } _handleUrlSubmit() { const wrapper = this.wrapper; // Store reference to wrapper const service = wrapper.querySelector('.embed-service').value; const url = wrapper.querySelector('.embed-url').value.trim(); if (!url) { this._showError('Please enter a valid URL'); return; } try { const embedUrl = this._parseEmbedUrl(service, url); this.type = service; this.data = { url: embedUrl, type: service }; this._createVideoElement(embedUrl, ''); } catch (error) { this._showError('Invalid video URL: ' + error.message); } } _parseEmbedUrl(service, url) { // YouTube if (service === 'youtube') { const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/; const match = url.match(regExp); if (match && match[2].length === 11) { return `https://www.youtube.com/embed/${match[2]}`; } } // Rutube if (service === 'rutube') { const regExp = /rutube\.ru\/video\/([a-z0-9]+)/i; const match = url.match(regExp); if (match && match[1]) { return `https://rutube.ru/play/embed/${match[1]}`; } } throw new Error('Invalid URL'); } _createVideoElement() { this.wrapper.innerHTML = ''; const container = document.createElement('div'); container.className = 'video-container'; container.style.margin = this._getAlignmentMargin(); container.style.width = this.width; if (this.type === 'file') { const video = document.createElement('video'); video.src = this.data.url; video.controls = true; video.style.width = '100%'; container.appendChild(video); } else if (this.type === 'youtube' || this.type === 'rutube') { const iframe = document.createElement('iframe'); iframe.src = this.data.url; iframe.frameBorder = '0'; iframe.allowFullscreen = true; iframe.style.width = '100%'; iframe.style.height = '400px'; container.appendChild(iframe); } this._addResizeHandle(container); this._addCaption(container); this.wrapper.appendChild(container); } async _uploadFile(file) { const formData = new FormData(); formData.append('file', file); const response = await fetch(this.config.uploader.byFile, { method: 'POST', body: formData }); if (!response.ok) throw new Error('Upload failed'); const data = await response.json(); return data.file.url; } _addResizeHandle() { const container = this.wrapper.querySelector('.video-container'); const video = container.querySelector('video'); const handle = document.createElement('div'); handle.className = 'resize-handle'; handle.innerHTML = '↔'; handle.addEventListener('mousedown', this._initResize); container.appendChild(handle); } _initResize(e) { e.preventDefault(); const video = this.wrapper.querySelector('video'); const startWidth = parseInt(video.style.width) || video.offsetWidth; const startX = e.clientX; const doResize = (e) => { const newWidth = Math.max(200, startWidth + (e.clientX - startX)); video.style.width = `${newWidth}px`; }; const stopResize = () => { document.removeEventListener('mousemove', doResize); document.removeEventListener('mouseup', stopResize); this.width = video.style.width; }; document.addEventListener('mousemove', doResize); document.addEventListener('mouseup', stopResize, { once: true }); } save(blockContent) { const video = blockContent.querySelector('video'); const caption = blockContent.querySelector('.video-caption'); return { url: video?.src || '', caption: caption?.innerHTML || '', width: video?.style.width || '100%', alignment: this.alignment }; } _getAlignmentMargin() { switch(this.alignment) { case 'left': return '0 auto 0 0'; case 'right': return '0 0 0 auto'; default: return '0 auto'; } } _showError(message) { this.wrapper.innerHTML = `
${message}
`; } }