mirror of
https://github.com/codex-team/codex.docs.git
synced 2025-07-21 22:29:40 +02:00
added new logic
This commit is contained in:
parent
8bcf23278d
commit
28580e300b
9 changed files with 500 additions and 549 deletions
|
@ -1,5 +1,5 @@
|
||||||
# Stage 1 - build
|
# Stage 1 - build
|
||||||
FROM node:16.14.0-alpine3.15 as build
|
FROM node:18-alpine as build
|
||||||
|
|
||||||
## Install build toolchain, install node deps and compile native add-ons
|
## Install build toolchain, install node deps and compile native add-ons
|
||||||
RUN apk add --no-cache python3 make g++ git
|
RUN apk add --no-cache python3 make g++ git
|
||||||
|
@ -8,9 +8,9 @@ WORKDIR /usr/src/app
|
||||||
|
|
||||||
COPY package.json yarn.lock ./
|
COPY package.json yarn.lock ./
|
||||||
|
|
||||||
RUN yarn install --production
|
##RUN yarn install --production
|
||||||
|
|
||||||
RUN cp -R node_modules prod_node_modules
|
##RUN cp -R node_modules prod_node_modules
|
||||||
|
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
|
||||||
|
@ -19,12 +19,12 @@ COPY . .
|
||||||
RUN yarn build-all
|
RUN yarn build-all
|
||||||
|
|
||||||
# Stage 2 - make final image
|
# Stage 2 - make final image
|
||||||
FROM node:16.14.0-alpine3.15
|
FROM node:18-alpine
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
COPY package.json yarn.lock ./
|
COPY package.json yarn.lock ./
|
||||||
COPY --from=build /usr/src/app/prod_node_modules ./node_modules
|
COPY --from=build /usr/src/app/node_modules ./node_modules
|
||||||
COPY --from=build /usr/src/app/dist ./dist
|
COPY --from=build /usr/src/app/dist ./dist
|
||||||
COPY --from=build /usr/src/app/public ./public
|
COPY --from=build /usr/src/app/public ./public
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
"editor-upgrade": "yarn add -D @editorjs/{editorjs,header,code,delimiter,list,link,image,table,inline-code,marker,warning,checklist,raw}@latest"
|
"editor-upgrade": "yarn add -D @editorjs/{editorjs,header,code,delimiter,list,link,image,table,inline-code,marker,warning,checklist,raw}@latest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/abort-controller": "^3.374.0",
|
||||||
"@aws-sdk/client-s3": "^3.181.0",
|
"@aws-sdk/client-s3": "^3.181.0",
|
||||||
"@codex-team/config-loader": "0.1.0-rc1",
|
"@codex-team/config-loader": "0.1.0-rc1",
|
||||||
"@codexteam/shortcuts": "^1.2.0",
|
"@codexteam/shortcuts": "^1.2.0",
|
||||||
|
|
|
@ -1,26 +1,37 @@
|
||||||
{% set width = data.width|default('100%') %}
|
{% set width = width|default('100%') %}
|
||||||
{% set height = data.height|default('auto') %}
|
{% set height = height|default('auto') %}
|
||||||
{% set alignment = data.alignment|default('center') %}
|
{% set alignment = alignment|default('center') %}
|
||||||
|
{% set filetype = filetype|default('file') %}
|
||||||
|
|
||||||
<figure class="block-video" style="width: {{ width }}; max-width: 100%; margin:
|
<figure class="block-video" style="width: {{ width }}; max-width: 100%; margin:
|
||||||
{% if alignment == 'left' %}0 auto 0 0
|
{% if alignment == 'left' %}0 auto 0 0
|
||||||
{% elseif alignment == 'right' %}0 0 0 auto
|
{% elseif alignment == 'right' %}0 0 0 auto
|
||||||
{% else %}0 auto
|
{% else %}0 auto
|
||||||
{% endif %};">
|
{% endif %};">
|
||||||
<div class="{{ classes.join(' ') }}" style="padding-bottom: {{ data.aspectRatio ? (1/data.aspectRatio)*100 ~ '%' : '56.25%' }};">
|
<div class="{{ classes.join(' ') }}">
|
||||||
<video
|
{% if filetype == 'youtube' or filetype == 'rutube' %}
|
||||||
controls
|
<iframe
|
||||||
style="width: 100%; height: 100%;"
|
src="{{ url }}"
|
||||||
{% if width %}width="{{ width }}"{% endif %}
|
frameborder="0"
|
||||||
{% if height %}height="{{ height }}"{% endif %}
|
allowfullscreen
|
||||||
>
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
<source src="{{ url }}" type="{{ data.mime|default('video/mp4') }}">
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
|
||||||
Ваш браузер не поддерживает видео тег.
|
></iframe>
|
||||||
</video>
|
{% else %}
|
||||||
</div>
|
<video
|
||||||
{% if data.caption %}
|
controls
|
||||||
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
|
||||||
|
{% if width %}width="{{ width }}"{% endif %}
|
||||||
|
{% if height %}height="{{ height }}"{% endif %}
|
||||||
|
>
|
||||||
|
<source src="{{ url }}" type="{{ mime|default('video/mp4') }}">
|
||||||
|
Âàø áðàóçåð íå ïîääåðæèâàåò âèäåî òåã.
|
||||||
|
</video>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if caption %}
|
||||||
<footer class="block-video__caption">
|
<footer class="block-video__caption">
|
||||||
{{ data.caption|raw }}
|
{{ caption|raw }}
|
||||||
</footer>
|
</footer>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</figure>
|
</figure>
|
|
@ -80,7 +80,7 @@ export default class Editor {
|
||||||
byUrl: '/api/transport/fetch' // Если нужна загрузка по URL
|
byUrl: '/api/transport/fetch' // Если нужна загрузка по URL
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inlineToolbar: true
|
shortcut: 'CMD+SHIFT+V'
|
||||||
},
|
},
|
||||||
|
|
||||||
image: {
|
image: {
|
||||||
|
|
|
@ -6,26 +6,33 @@ export default class VideoTool {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor({ data, api, config }) {
|
constructor({ data, api, config, block }) { // Äîáàâëÿåì block â ïàðàìåòðû
|
||||||
this.data = data || {};
|
this.data = data || {};
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.config = config || {};
|
this.config = config || {};
|
||||||
|
this.block = block; // Ñîõðàíÿåì ññûëêó íà òåêóùèé áëîê
|
||||||
this.wrapper = null;
|
this.wrapper = null;
|
||||||
this.alignment = data.alignment || 'center';
|
this.alignment = data.alignment || 'center';
|
||||||
this.width = data.width || '100%';
|
this.width = data.width || '100%';
|
||||||
|
this.filetype = data.filetype || 'file';
|
||||||
|
|
||||||
// Привязываем контекст для обработчиков
|
// Ïðèâÿçûâàåì êîíòåêñò äëÿ îáðàáîò÷èêîâ
|
||||||
this._handleFileUpload = this._handleFileUpload.bind(this);
|
this._handleFileUpload = this._handleFileUpload.bind(this);
|
||||||
this._initResize = this._initResize.bind(this);
|
this._initResize = this._initResize.bind(this);
|
||||||
this._handleUrlSubmit = this._handleUrlSubmit.bind(this); // Added this binding
|
this._handleUrlSubmit = this._handleUrlSubmit.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
this.wrapper = document.createElement('div');
|
this.wrapper = document.createElement('div');
|
||||||
this.wrapper.classList.add('video-tool');
|
this.wrapper.classList.add('video-tool');
|
||||||
|
|
||||||
if (this.data.url) {
|
if (this.data.url) {
|
||||||
this._createVideoElement(this.data.url, this.data.caption || '');
|
// Äîáàâëÿåì ïðîâåðêó äëÿ ñîâìåñòèìîñòè ñî ñòàðûìè äàííûìè
|
||||||
|
const filetype = this.data.filetype ||
|
||||||
|
(this.data.url.includes('youtube') ? 'youtube' :
|
||||||
|
this.data.url.includes('rutube') ? 'rutube' : 'file');
|
||||||
|
|
||||||
|
this._createVideoElement(this.data.url, this.data.caption || '', filetype);
|
||||||
} else {
|
} else {
|
||||||
this._createUploadForm();
|
this._createUploadForm();
|
||||||
}
|
}
|
||||||
|
@ -34,49 +41,69 @@ export default class VideoTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
_createUploadForm() {
|
_createUploadForm() {
|
||||||
|
const currentUrl = this.wrapper?.querySelector('.embed-url')?.value || '';
|
||||||
this.wrapper.innerHTML = `
|
this.wrapper.innerHTML = `
|
||||||
<div class="video-upload-tabs">
|
<div class="video-upload-tabs">
|
||||||
<div class="tabs-header">
|
<div class="tabs-header">
|
||||||
<button class="tab-button active" data-tab="upload">Upload</button>
|
<button class="tab-button active" data-tab="upload">Upload</button>
|
||||||
<button class="tab-button" data-tab="embed">Embed URL</button>
|
<button class="tab-button" data-tab="embed">Embed URL</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content active" data-tab="Загрузить с ПК">
|
<div class="tab-content active" data-tab="upload">
|
||||||
<label class="video-upload-button">
|
<label class="video-upload-button">
|
||||||
<input type="file" accept="video/mp4,video/webm,video/ogg" class="video-file-input">
|
<input type="file" accept="video/mp4,video/webm,video/ogg" class="video-file-input">
|
||||||
<span>Select Video File</span>
|
<span>Select Video File</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content" data-tab="Вставить ссылку">
|
<div class="tab-content" data-tab="embed">
|
||||||
<div class="embed-form">
|
<div class="embed-form">
|
||||||
<select class="embed-service">
|
<select class="embed-service">
|
||||||
<option value="youtube">YouTube</option>
|
<option value="youtube">YouTube</option>
|
||||||
<option value="rutube">Rutube</option>
|
<option value="rutube">Rutube</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="text" class="embed-url" placeholder="Paste video URL...">
|
<input type="text" class="embed-url" placeholder="Paste video URL...">
|
||||||
<button class="embed-submit">Вставить видео</button>
|
<button class="embed-submit">Âñòàâèòü âèäåî</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
// Tab switching
|
|
||||||
|
this._bindFormHandlers();
|
||||||
|
if (currentUrl) {
|
||||||
|
const urlInput = this.wrapper.querySelector('.embed-url');
|
||||||
|
if (urlInput) urlInput.value = currentUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_bindFormHandlers() {
|
||||||
|
// Tab switching
|
||||||
this.wrapper.querySelectorAll('.tab-button').forEach(btn => {
|
this.wrapper.querySelectorAll('.tab-button').forEach(btn => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
this.wrapper.querySelectorAll('.tab-button, .tab-content').forEach(el => {
|
this.wrapper.querySelectorAll('.tab-button, .tab-content').forEach(el => {
|
||||||
el.classList.toggle('active', el.dataset.tab === btn.dataset.tab);
|
el.classList.toggle('active', el.dataset.tab === btn.dataset.tab);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Ïîñëå ïåðåêëþ÷åíèÿ âêëàäêè ïåðåïðèâÿçûâàåì îáðàáîò÷èêè
|
||||||
|
this._bindFormHandlers();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// File upload handler
|
// File upload handler
|
||||||
this.wrapper.querySelector('.video-file-input')
|
const fileInput = this.wrapper.querySelector('.video-file-input');
|
||||||
.addEventListener('change', this._handleFileUpload);
|
if (fileInput) {
|
||||||
|
fileInput.addEventListener('change', this._handleFileUpload);
|
||||||
|
}
|
||||||
|
|
||||||
// URL embed handler
|
// URL embed handler
|
||||||
this.wrapper.querySelector('.embed-submit')
|
const embedSubmit = this.wrapper.querySelector('.embed-submit');
|
||||||
.addEventListener('click', this._handleUrlSubmit);
|
if (embedSubmit) {
|
||||||
}
|
embedSubmit.addEventListener('click', this._handleUrlSubmit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async _handleFileUpload(event) {
|
async _handleFileUpload(event) {
|
||||||
|
if (!event.target.files || event.target.files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
|
@ -89,106 +116,202 @@ export default class VideoTool {
|
||||||
const url = await this._uploadFile(file);
|
const url = await this._uploadFile(file);
|
||||||
this.type = 'file';
|
this.type = 'file';
|
||||||
this.data = { url, type: 'file' };
|
this.data = { url, type: 'file' };
|
||||||
his._createVideoElement(url, '');
|
this._createVideoElement(url, '');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this._showError('File upload failed: ' + error.message);
|
this._showError('File upload failed: ' + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleUrlSubmit() {
|
_handleUrlSubmit() {
|
||||||
const wrapper = this.wrapper; // Store reference to wrapper
|
const wrapper = this.wrapper;
|
||||||
const service = wrapper.querySelector('.embed-service').value;
|
const service = wrapper.querySelector('.embed-service').value;
|
||||||
const url = wrapper.querySelector('.embed-url').value.trim();
|
const urlInput = wrapper.querySelector('.embed-url');
|
||||||
|
const url = urlInput.value.trim();
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
this._showError('Please enter a valid URL');
|
this._showError('Ïîæàëóéñòà, ââåäèòå URL âèäåî');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const embedUrl = this._parseEmbedUrl(service, url);
|
const submitBtn = wrapper.querySelector('.embed-submit');
|
||||||
this.type = service;
|
submitBtn.disabled = true;
|
||||||
this.data = { url: embedUrl, type: service };
|
submitBtn.textContent = 'Îáðàáîòêà...';
|
||||||
this._createVideoElement(embedUrl, '');
|
|
||||||
|
const embedUrl = this._parseEmbedUrl(service, url);
|
||||||
|
|
||||||
|
// Îáíîâëÿåì äàííûå òåêóùåãî áëîêà
|
||||||
|
this.data = {
|
||||||
|
url: embedUrl,
|
||||||
|
filetype: service,
|
||||||
|
width: this.width,
|
||||||
|
alignment: this.alignment,
|
||||||
|
caption: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ñîõðàíÿåì èçìåíåíèÿ
|
||||||
|
this.api.blocks.update(this.block.id, this.data);
|
||||||
|
|
||||||
|
// Ïåðåñîçäàåì ýëåìåíò
|
||||||
|
this.wrapper.innerHTML = '';
|
||||||
|
this._createVideoElement(embedUrl, '');
|
||||||
|
|
||||||
|
// Âîññòàíàâëèâàåì êíîïêó
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.textContent = 'Âñòàâèòü âèäåî';
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this._showError('Invalid video URL: ' + error.message);
|
this._showError(error.message);
|
||||||
|
const submitBtn = wrapper.querySelector('.embed-submit');
|
||||||
|
if (submitBtn) {
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.textContent = 'Âñòàâèòü âèäåî';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_parseEmbedUrl(service, url) {
|
_parseEmbedUrl(service, url) {
|
||||||
// YouTube
|
// YouTube
|
||||||
if (service === 'youtube') {
|
if (service === 'youtube') {
|
||||||
const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
|
// Ïîääåðæêà âñåõ ôîðìàòîâ YouTube ññûëîê
|
||||||
const match = url.match(regExp);
|
const regExp = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/i;
|
||||||
if (match && match[2].length === 11) {
|
const match = url.match(regExp);
|
||||||
return `https://www.youtube.com/embed/${match[2]}`;
|
if (match && match[1]) {
|
||||||
}
|
return `https://www.youtube.com/embed/${match[1]}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rutube
|
// Rutube
|
||||||
if (service === 'rutube') {
|
if (service === 'rutube') {
|
||||||
const regExp = /rutube\.ru\/video\/([a-z0-9]+)/i;
|
// Ïîääåðæêà âñåõ ôîðìàòîâ Rutube ññûëîê
|
||||||
|
const regExp = /rutube\.ru\/(?:video\/|play\/embed\/|video\/embed\/)?([a-zA-Z0-9]+)/i;
|
||||||
const match = url.match(regExp);
|
const match = url.match(regExp);
|
||||||
if (match && match[1]) {
|
if (match && match[1]) {
|
||||||
return `https://rutube.ru/play/embed/${match[1]}`;
|
return `https://rutube.ru/play/embed/${match[1]}`;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Invalid URL');
|
throw new Error('Íåâåðíûé URL Rutube. Ïðèìåð ïðàâèëüíîãî ôîðìàòà: https://rutube.ru/video/CODE/');
|
||||||
}
|
}
|
||||||
|
|
||||||
_createVideoElement() {
|
throw new Error('Invalid URL.');
|
||||||
this.wrapper.innerHTML = '';
|
}
|
||||||
|
|
||||||
const container = document.createElement('div');
|
_createVideoElement(url, caption, filetype = null) {
|
||||||
container.className = 'video-container';
|
this.wrapper.innerHTML = '';
|
||||||
container.style.margin = this._getAlignmentMargin();
|
|
||||||
container.style.width = this.width;
|
|
||||||
|
|
||||||
if (this.type === 'file') {
|
const container = document.createElement('div');
|
||||||
|
container.className = 'video-container';
|
||||||
|
container.style.margin = this._getAlignmentMargin();
|
||||||
|
container.style.width = this.width;
|
||||||
|
|
||||||
|
// Îïðåäåëÿåì òèï êîíòåíòà
|
||||||
|
const type = filetype || this.filetype ||
|
||||||
|
(url.match(/\.(mp4|webm|ogg)$/i) ? 'file' :
|
||||||
|
(url.includes('youtube.com/embed') || url.includes('youtu.be') ? 'youtube' :
|
||||||
|
(url.includes('rutube.ru/embed') || url.includes('rutube.ru/video') ? 'rutube' : 'file')));
|
||||||
|
|
||||||
|
const mediaContainer = document.createElement('div');
|
||||||
|
mediaContainer.style.position = 'relative';
|
||||||
|
mediaContainer.style.paddingBottom = '56.25%';
|
||||||
|
mediaContainer.style.height = '0';
|
||||||
|
mediaContainer.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
if (type === 'file') {
|
||||||
const video = document.createElement('video');
|
const video = document.createElement('video');
|
||||||
video.src = this.data.url;
|
video.src = url;
|
||||||
video.controls = true;
|
video.controls = true;
|
||||||
|
video.style.position = 'absolute';
|
||||||
|
video.style.top = '0';
|
||||||
|
video.style.left = '0';
|
||||||
video.style.width = '100%';
|
video.style.width = '100%';
|
||||||
container.appendChild(video);
|
video.style.height = '100%';
|
||||||
}
|
mediaContainer.appendChild(video);
|
||||||
else if (this.type === 'youtube' || this.type === 'rutube') {
|
} else {
|
||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement('iframe');
|
||||||
iframe.src = this.data.url;
|
iframe.src = url.includes('rutube.ru/video') ?
|
||||||
|
url.replace('rutube.ru/video', 'rutube.ru/play/embed') : url;
|
||||||
iframe.frameBorder = '0';
|
iframe.frameBorder = '0';
|
||||||
iframe.allowFullscreen = true;
|
iframe.allowFullscreen = true;
|
||||||
|
iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture";
|
||||||
|
iframe.style.position = 'absolute';
|
||||||
|
iframe.style.top = '0';
|
||||||
|
iframe.style.left = '0';
|
||||||
iframe.style.width = '100%';
|
iframe.style.width = '100%';
|
||||||
iframe.style.height = '400px';
|
iframe.style.height = '100%';
|
||||||
container.appendChild(iframe);
|
mediaContainer.appendChild(iframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._addResizeHandle(container);
|
container.appendChild(mediaContainer);
|
||||||
this._addCaption(container);
|
this._addCaption(container, caption);
|
||||||
this.wrapper.appendChild(container);
|
this._addResizeHandle(container);
|
||||||
|
this.wrapper.appendChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
_addCaption(container, captionText) {
|
||||||
|
if (!captionText) return;
|
||||||
|
|
||||||
|
const caption = document.createElement('div');
|
||||||
|
caption.className = 'video-caption';
|
||||||
|
caption.contentEditable = true;
|
||||||
|
caption.innerHTML = captionText;
|
||||||
|
container.appendChild(caption);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _uploadFile(file) {
|
async _uploadFile(file) {
|
||||||
const formData = new FormData();
|
try {
|
||||||
formData.append('file', file);
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
const response = await fetch(this.config.uploader.byFile, {
|
const response = await fetch(this.config.uploader.byFile, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) throw new Error('Upload failed');
|
if (!response.ok) {
|
||||||
const data = await response.json();
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
return data.file.url;
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data?.file?.url) {
|
||||||
|
throw new Error('Invalid response format');
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.file.url;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Upload error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_addResizeHandle() {
|
_addResizeHandle(container) {
|
||||||
const container = this.wrapper.querySelector('.video-container');
|
const video = container.querySelector('video, iframe');
|
||||||
const video = container.querySelector('video');
|
|
||||||
|
if (!video) return; // Åñëè âèäåî íå íàéäåíî, âûõîäèì
|
||||||
|
|
||||||
const handle = document.createElement('div');
|
const handle = document.createElement('div');
|
||||||
handle.className = 'resize-handle';
|
handle.className = 'resize-handle';
|
||||||
handle.innerHTML = '↔';
|
handle.innerHTML = '-';
|
||||||
handle.addEventListener('mousedown', this._initResize);
|
|
||||||
|
handle.addEventListener('mousedown', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
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 });
|
||||||
|
});
|
||||||
|
|
||||||
container.appendChild(handle);
|
container.appendChild(handle);
|
||||||
}
|
}
|
||||||
|
@ -215,14 +338,17 @@ export default class VideoTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
save(blockContent) {
|
save(blockContent) {
|
||||||
const video = blockContent.querySelector('video');
|
const video = blockContent.querySelector('video, iframe');
|
||||||
const caption = blockContent.querySelector('.video-caption');
|
const caption = blockContent.querySelector('.video-caption');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: video?.src || '',
|
url: video?.src || this.data.url || '',
|
||||||
caption: caption?.innerHTML || '',
|
caption: caption?.innerHTML || this.data.caption || '',
|
||||||
width: video?.style.width || '100%',
|
width: this.width || '100%',
|
||||||
alignment: this.alignment
|
alignment: this.alignment,
|
||||||
|
filetype: this.filetype || (video?.tagName === 'IFRAME' ?
|
||||||
|
(video.src.includes('youtube') ? 'youtube' : 'rutube')
|
||||||
|
: 'file')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,6 +361,27 @@ export default class VideoTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
_showError(message) {
|
_showError(message) {
|
||||||
this.wrapper.innerHTML = `<div class="video-error">${message}</div>`;
|
if (!this.wrapper) return;
|
||||||
}
|
|
||||||
|
// Ñîõðàíÿåì òåêóùåå ñîñòîÿíèå ôîðìû
|
||||||
|
const currentTab = this.wrapper.querySelector('.tab-button.active').dataset.tab;
|
||||||
|
const currentUrl = this.wrapper.querySelector('.embed-url')?.value || '';
|
||||||
|
|
||||||
|
this.wrapper.innerHTML = `
|
||||||
|
<div class="video-error">
|
||||||
|
<p>${message}</p>
|
||||||
|
<button class="retry-button">Ïîïðîáîâàòü ñíîâà</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Îáðàáîò÷èê äëÿ êíîïêè ïîâòîðà
|
||||||
|
this.wrapper.querySelector('.retry-button').addEventListener('click', () => {
|
||||||
|
this._createUploadForm();
|
||||||
|
// Âîññòàíàâëèâàåì ñîñòîÿíèå
|
||||||
|
if (currentTab === 'embed') {
|
||||||
|
const embedUrl = this.wrapper.querySelector('.embed-url');
|
||||||
|
if (embedUrl) embedUrl.value = currentUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -112,6 +112,7 @@ export default class Writing {
|
||||||
*/
|
*/
|
||||||
async getData() {
|
async getData() {
|
||||||
const editorData = await this.editor.save();
|
const editorData = await this.editor.save();
|
||||||
|
console.log('Editor data:', JSON.stringify(editorData, null, 2));
|
||||||
const firstBlock = editorData.blocks.length ? editorData.blocks[0] : null;
|
const firstBlock = editorData.blocks.length ? editorData.blocks[0] : null;
|
||||||
const title = firstBlock && firstBlock.type === 'header' ? firstBlock.data.text : null;
|
const title = firstBlock && firstBlock.type === 'header' ? firstBlock.data.text : null;
|
||||||
let uri = '';
|
let uri = '';
|
||||||
|
|
|
@ -495,17 +495,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-embed {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
&__iframe {
|
|
||||||
width: 100%;
|
|
||||||
height: 450px;
|
|
||||||
border: 0;
|
|
||||||
|
|
||||||
@media (--mobile) {
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -163,13 +163,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-error {
|
.video-error {
|
||||||
color: #ff0000;
|
padding: 15px;
|
||||||
padding: 1rem;
|
background: #ffebee;
|
||||||
background: #ffecec;
|
border: 1px solid #ffcdd2;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 1rem 0;
|
color: #c62828;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video-error p {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.color-palette-item {
|
.color-palette-item {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
@ -302,23 +308,18 @@
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-error {
|
|
||||||
padding: 20px;
|
|
||||||
color: #d32f2f;
|
|
||||||
background: #ffebee;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-button {
|
.retry-button {
|
||||||
margin-top: 10px;
|
background: #2196f3;
|
||||||
padding: 5px 10px;
|
color: white;
|
||||||
background: #1976d2;
|
border: none;
|
||||||
color: white;
|
padding: 5px 10px;
|
||||||
border: none;
|
border-radius: 3px;
|
||||||
border-radius: 4px;
|
cursor: pointer;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.retry-button:hover {
|
||||||
|
background: #0d8aee;
|
||||||
|
}
|
||||||
.video-tool {
|
.video-tool {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
|
@ -373,8 +374,27 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-video {
|
.block-video {
|
||||||
margin: 1.5rem auto;
|
position: relative;
|
||||||
max-width: 100%;
|
padding-bottom: 56.25%; /* 16:9 */
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-video > div {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 56.25%; /* 16:9 ñîîòíîøåíèå */
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-video iframe,
|
||||||
|
.block-video video {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-video__content {
|
.block-video__content {
|
||||||
|
@ -542,7 +562,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-container iframe {
|
.video-container video {
|
||||||
border: none;
|
display: block;
|
||||||
min-height: 400px;
|
margin: 0 auto;
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue