Commit 82b42d2a authored by Яков's avatar Яков
Browse files

update

parent c4ae2bf2
Pipeline #9939 canceled with stages
{
"name": "react-ag-qeditor",
"version": "1.1.33",
"version": "1.1.34",
"description": "WYSIWYG html editor",
"author": "atma",
"license": "MIT",
......
......@@ -219,64 +219,89 @@ export const DragAndDrop = Extension.create({
// 3. HTML с внешними картинками (Google Docs, веб-страницы и т.д.)
if (html) {
console.log('[DragAndDrop] paste html types:', items.map(i => `${i.kind}:${i.type}`));
const tmpDoc = new DOMParser().parseFromString(html, 'text/html');
const externalImgs = Array.from(tmpDoc.querySelectorAll('img[src]')).filter(img => {
const src = img.getAttribute('src') || '';
return src.startsWith('http://') || src.startsWith('https://') || src.startsWith('data:');
});
console.log('[DragAndDrop] external imgs found:', externalImgs.length, externalImgs.map(i => i.getAttribute('src')?.slice(0, 80)));
if (externalImgs.length > 0) {
const imgData = externalImgs.map(img => ({
src: img.getAttribute('src'),
width: parseInt(img.getAttribute('width')) || null,
height: parseInt(img.getAttribute('height')) || null,
}));
// Убираем <img> из HTML — TipTap вставит только текст
externalImgs.forEach(img => img.remove());
event.preventDefault();
const cleanHtml = tmpDoc.body.innerHTML;
if (cleanHtml.trim()) {
extension.editor.commands.insertContent(cleanHtml);
}
// Асинхронно загружаем каждую картинку на сервер и вставляем
;(async () => {
for (const { src, width, height } of imgData) {
try {
let blob;
// Загружаем из URL или data URI в blob
const fetchBlob = async (src) => {
if (src.startsWith('data:')) {
// data URI — конвертируем сразу в blob
const [header, b64] = src.split(',');
const mime = (header.match(/:(.*?);/) || [])[1] || 'image/png';
const binary = atob(b64);
const arr = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) arr[i] = binary.charCodeAt(i);
blob = new Blob([arr], { type: mime });
} else {
// Внешний URL — без credentials (иначе CORS с Access-Control-Allow-Origin: * падает)
return new Blob([arr], { type: mime });
}
const resp = await fetch(src);
console.log('[DragAndDrop] fetch', src.slice(0, 80), resp.status, resp.headers.get('content-type'));
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
blob = await resp.blob();
}
if (!blob.type.startsWith('image/')) {
console.warn('[DragAndDrop] unexpected blob type:', blob.type);
continue;
return resp.blob();
};
// Загружаем файл на CDN и возвращаем file_path
const uploadBlob = async (blob, name) => {
const file = new File([blob], name, { type: blob.type });
if (extension.options.uploadHandler) {
return extension.options.uploadHandler(file);
}
const formData = new FormData();
formData.append('file', file);
const response = await axios.post(extension.options.uploadUrl, formData, {
headers: extension.options.headers
});
return response.data;
};
;(async () => {
// Загружаем все изображения параллельно
const uploads = await Promise.all(
externalImgs.map(async (img) => {
const src = img.getAttribute('src');
const width = parseInt(img.getAttribute('width')) || null;
const height = parseInt(img.getAttribute('height')) || null;
const style = img.getAttribute('style') || '';
try {
const blob = await fetchBlob(src);
if (!blob.type.startsWith('image/')) return null;
const ext = blob.type.split('/')[1]?.split('+')[0] || 'png';
const file = new File([blob], `pasted-image.${ext}`, { type: blob.type });
await handleFileUpload(file, view, null, { width, height, align: 'left' });
const result = await uploadBlob(blob, `pasted-image.${ext}`);
if (!result?.file_path) return null;
return { filePath: result.file_path, width, height, style };
} catch (e) {
console.warn('[DragAndDrop] не удалось загрузить картинку:', src?.slice(0, 80), e);
console.warn('[DragAndDrop] не удалось загрузить:', src?.slice(0, 80), e);
return null;
}
})
);
// Заменяем src на загруженные URL прямо в tmpDoc
externalImgs.forEach((img, i) => {
const upload = uploads[i];
if (upload) {
img.setAttribute('src', upload.filePath);
// Сохраняем data-align для parseHTML Image.jsx
const ml = (upload.style.match(/margin-left\s*:\s*([^;]+)/i) || [])[1]?.trim();
const mr = (upload.style.match(/margin-right\s*:\s*([^;]+)/i) || [])[1]?.trim();
const fl = (upload.style.match(/float\s*:\s*([^;]+)/i) || [])[1]?.trim();
const align = (ml === 'auto' && mr === 'auto') ? 'center'
: (fl === 'right' || ml === 'auto') ? 'right' : 'left';
img.setAttribute('data-align', align);
if (upload.width) img.setAttribute('width', String(upload.width));
if (upload.height) img.setAttribute('height', String(upload.height));
img.removeAttribute('style');
} else {
img.remove();
}
});
// Вставляем HTML целиком — текст и картинки на своих местах
extension.editor.commands.insertContent(tmpDoc.body.innerHTML);
extension.options.onUploadSuccess?.();
})();
return true;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment