Вот решение на PHP, которое удаляет часть строки после символа @ до закрывающей кавычки ", оставляя остальное без изменений:
$string = 'src="cid:image003.png@01DBC4D3.B01D85F0"'; $result = preg_replace('/@[^"]+/', '', $string); echo $result;
src="cid:image003.png"
preg_replace выполняет замену по регулярному выражению.
Регулярное выражение /@[^"]+/ ищет:
@ — символ, после которого нужно удалить текст.
[^"]+ — один или более символов, которые не являются кавычкой (").
Заменяем найденное совпадение на пустую строку (''), тем самым удаляя ненужную часть.
Этот способ работает для любых случайных значений после @, сохраняя структуру строки до @ и после кавычки.
Если у вас есть изображение в формате blob: (например, загруженное через <input type="file"> и отображенное в <img src="blob:...">), то для его сохранения на сервер нужно:
Получить blob-данные (обычно это File или Blob объект).
Отправить их на сервер с помощью FormData или fetch.
Сохранить на сервере (PHP).
Допустим, у вас есть <img> с src="blob:...", и вы хотите отправить его на сервер при отправке формы.
// Предположим, что у вас есть форма с id="myForm" $('#myForm').on('submit', function(e) { e.preventDefault(); // Отменяем стандартную отправку // Находим blob-изображение (например, первое в форме) const imgElement = $('img[src^="blob:"]')[0]; if (!imgElement) { alert('No blob image found!'); return; } // Получаем blob-данные из src fetch(imgElement.src) .then(res => res.blob()) .then(blob => { // Создаем FormData для отправки const formData = new FormData(); formData.append('image', blob, 'uploaded_image.png'); // 'image' - имя поля на сервере // Отправляем на сервер return fetch('upload.php', { method: 'POST', body: formData, }); }) .then(response => response.json()) .then(data => { console.log('Success:', data); alert('Image saved!'); }) .catch(error => { console.error('Error:', error); alert('Error uploading image!'); }); });
Создайте файл upload.php, который примет изображение и сохранит его:
<?php
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['error' => 'Invalid request method']);
exit;
}
if (!isset($_FILES['image'])) {
echo json_encode(['error' => 'No image uploaded']);
exit;
}
$uploadDir = 'uploads/'; // Папка для сохранения
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$file = $_FILES['image'];
$fileName = uniqid() . '.png'; // Генерируем уникальное имя
$filePath = $uploadDir . $fileName;
if (move_uploaded_file($file['tmp_name'], $filePath)) {
echo json_encode([
'success' => true,
'path' => $filePath, // Путь к сохраненному файлу
'url' => 'https://yourdomain.com/' . $filePath, // Полный URL
]);
} else {
echo json_encode(['error' => 'Failed to save image']);
}
?>blob: на реальный URL после загрузкиПосле успешной загрузки вы можете заменить src изображения на серверный URL:
.then(data => { if (data.success) { // Заменяем blob: на реальный URL $(imgElement).attr('src', data.url); alert('Image saved at: ' + data.url); } })
Клиентская часть (JS/jQuery):
Находит <img src="blob:...">.
Конвертирует blob: в FormData.
Отправляет на сервер через AJAX.
Серверная часть (PHP):
Принимает файл через $_FILES.
Сохраняет в папку uploads/.
Возвращает URL сохраненного изображения.
После успешной загрузки:
Заменяет blob: на реальный URL.
Теперь изображение будет сохранено на сервере, а в HTML останется нормальный путь (/uploads/123abc.png). 🚀
Если у вас несколько изображений с blob:-URL, и их нужно отправить на сервер, сохранить, а затем заменить src на реальные пути, то процесс немного усложняется.
Найти все <img> с src="blob:..." в форме/документе.
Подготовить данные (конвертировать blob: в Blob или File).
Отправить их на сервер (FormData, fetch).
Обработать на сервере (PHP, сохранить каждое изображение).
Обновить src у всех <img> после успешной загрузки.
// Находим все <img>, у которых src начинается с "blob:" const blobImages = $('img[src^="blob:"]'); if (blobImages.length === 0) { console.log('No blob images found'); return; }
FormDataconst formData = new FormData(); // Добавляем каждое изображение в FormData blobImages.each(function(index, imgElement) { fetch(imgElement.src) .then(res => res.blob()) .then(blob => { // Добавляем в FormData с уникальным именем formData.append(`images[${index}]`, blob, `image_${index}.png`); }); });
fetch('upload.php', { method: 'POST', body: formData, }) .then(response => response.json()) .then(data => { if (data.success) { // Проходим по всем изображениям и обновляем src blobImages.each(function(index, imgElement) { if (data.savedPaths[index]) { $(imgElement).attr('src', data.savedPaths[index]); } }); alert('All images saved!'); } else { console.error('Error:', data.error); } }) .catch(error => { console.error('Upload failed:', error); });
<?php
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['error' => 'Invalid request method']);
exit;
}
if (empty($_FILES['images'])) {
echo json_encode(['error' => 'No images uploaded']);
exit;
}
$uploadDir = 'uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$savedPaths = [];
$errors = [];
foreach ($_FILES['images']['tmp_name'] as $index => $tmpName) {
if ($_FILES['images']['error'][$index] !== UPLOAD_ERR_OK) {
$errors[] = "Error uploading image {$index}";
continue;
}
$fileName = uniqid() . '.png'; // или оригинальное имя: $_FILES['images']['name'][$index]
$filePath = $uploadDir . $fileName;
if (move_uploaded_file($tmpName, $filePath)) {
$savedPaths[$index] = $filePath; // или полный URL: 'https://site.com/' . $filePath
} else {
$errors[] = "Failed to save image {$index}";
}
}
if (empty($errors)) {
echo json_encode([
'success' => true,
'savedPaths' => $savedPaths,
]);
} else {
echo json_encode([
'success' => false,
'error' => implode(', ', $errors),
]);
}
?>После успешного ответа от сервера (data.savedPaths содержит пути к сохранённым изображениям), мы заменяем blob: на реальные URL:
blobImages.each(function(index, imgElement) { if (data.savedPaths[index]) { $(imgElement).attr('src', data.savedPaths[index]); } });
Находим все <img src="blob:..."> ($('img[src^="blob:"]')).
Конвертируем blob: в Blob (fetch().then(res => res.blob())).
Отправляем массив файлов (FormData, append('images[]', ...)).
Сервер сохраняет каждое изображение (move_uploaded_file).
Обновляем src у всех <img> после успешной загрузки.
До:
<img src="blob:https://example.com/abc123" class="fr-fic"> <img src="blob:https://example.com/def456" class="fr-fic">
После:
<img src="/uploads/65a8d3e1.png" class="fr-fic"> <img src="/uploads/7b2e4f0c.png" class="fr-fic">
Теперь все изображения сохранены на сервере, а в HTML подставлены реальные URL. 🚀
Если Froala Editor вставляет изображения в формате blob:, но при отправке формы они не передаются на сервер, значит, нужно:
blob:-изображения доступны перед отправкойFroala Editor хранит загруженные изображения в памяти браузера (blob:), но они не прикрепляются автоматически к форме при отправке.
blob:-изображения и отправить их отдельноНужно:
Найти все blob:-изображения в редакторе.
Конвертировать их в File/Blob.
Отправить на сервер через FormData.
Заменить blob: на серверные URL после загрузки.
blob:-изображения в Froala Editor$(document).ready(function() { $('#myForm').on('submit', function(e) { e.preventDefault(); // Отменяем стандартную отправку // Находим все blob-изображения внутри редактора Froala const editor = $('#froalaEditor').data('froala.editor'); // Если Froala инициализирован const blobImages = $(editor ? editor.$el : '#froalaEditor').find('img[src^="blob:"]'); if (blobImages.length === 0) { this.submit(); // Если нет blob-изображений, отправляем форму как обычно return; } const formData = new FormData(this); // Основные данные формы // Добавляем каждое blob-изображение в FormData Promise.all( blobImages.map(async (imgElement, index) => { const blob = await fetch(imgElement.src).then(res => res.blob()); formData.append(`images[${index}]`, blob, `image_${index}.png`); return { index, element: imgElement }; }) ).then(async (images) => { // Отправляем FormData на сервер const response = await fetch('upload.php', { method: 'POST', body: formData, }); const data = await response.json(); if (data.success) { // Заменяем blob: на серверные URL images.forEach(({ index, element }) => { if (data.savedPaths[index]) { $(element).attr('src', data.savedPaths[index]); } }); this.submit(); // Отправляем форму с обновлёнными URL } else { alert('Ошибка загрузки изображений: ' + data.error); } }).catch(error => { console.error('Ошибка:', error); alert('Не удалось обработать изображения'); }); }); });
upload.php)<?php
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => false, 'error' => 'Invalid request method']);
exit;
}
$uploadDir = 'uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$savedPaths = [];
$errors = [];
// Обрабатываем загруженные файлы
if (!empty($_FILES['images'])) {
foreach ($_FILES['images']['tmp_name'] as $index => $tmpName) {
if ($_FILES['images']['error'][$index] !== UPLOAD_ERR_OK) {
$errors[] = "Ошибка загрузки изображения {$index}";
continue;
}
$ext = pathinfo($_FILES['images']['name'][$index], PATHINFO_EXTENSION) ?: 'png';
$fileName = uniqid() . '.' . $ext;
$filePath = $uploadDir . $fileName;
if (move_uploaded_file($tmpName, $filePath)) {
$savedPaths[$index] = '/uploads/' . $fileName; // Можно заменить на полный URL
} else {
$errors[] = "Не удалось сохранить изображение {$index}";
}
}
}
if (empty($errors)) {
echo json_encode([
'success' => true,
'savedPaths' => $savedPaths,
]);
} else {
echo json_encode([
'success' => false,
'error' => implode(', ', $errors),
]);
}
?>Если Froala Editor уже загружает изображения на сервер (например, через imageUploadURL), но всё равно оставляет blob: в редакторе, можно:
blob: и использовать только серверные URLnew FroalaEditor('#froalaEditor', { imageUploadURL: '/upload_froala.php', // Серверный обработчик загрузки imageUploadParams: { // Доп. параметры (если нужно) key: 'your-api-key', }, // Отключаем временные blob-URL imagePaste: false, imageInsertButtons: ['upload'], });
blob: после вставкиnew FroalaEditor('#froalaEditor', { events: { 'image.inserted': function ($img, response) { // Если Froala возвращает URL изображения, заменяем blob: if (response && response.link) { $img.attr('src', response.link); } }, }, });
Проблема:
Froala Editor вставляет изображения как blob:, но они не отправляются с формой.
Решение:
Перед отправкой формы извлекаем blob:-изображения.
Конвертируем их в File и добавляем в FormData.
Отправляем на сервер и заменяем blob: на реальные URL.
Дополнительно:
Можно настроить Froala Editor на автоматическую загрузку изображений (без blob:).
Теперь форма будет корректно сохранять все изображения! 🎯
Вот самый простой пример с использованием:
✅ Легкий WYSIWYG-редактор (TinyMCE или Summernote)
✅ Загрузка изображений через AJAX (jQuery)
✅ Сохранение в PHP
index.html)<!DOCTYPE html> <html> <head> <title>Простой редактор с загрузкой изображений</title> <!-- Подключаем jQuery + Summernote --> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-lite.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-lite.min.js"></script> </head> <body> <form id="editorForm"> <textarea id="summernote" name="content"></textarea> <button type="submit">Сохранить</button> </form> <script> $(document).ready(function() { // Инициализация Summernote $('#summernote').summernote({ height: 300, toolbar: [ ['style', ['bold', 'italic', 'underline']], ['insert', ['picture']], // Кнопка вставки изображений ], callbacks: { onImageUpload: function(files) { uploadImage(files[0]); // Загружаем 1 файл } } }); // Отправка формы $('#editorForm').on('submit', function(e) { e.preventDefault(); const content = $('#summernote').summernote('code'); $.ajax({ url: 'save.php', type: 'POST', data: { content: content }, success: function(response) { alert('Сохранено!'); }, error: function() { alert('Ошибка сохранения!'); } }); }); }); // Загрузка изображения на сервер function uploadImage(file) { const formData = new FormData(); formData.append('image', file); $.ajax({ url: 'upload.php', type: 'POST', data: formData, contentType: false, processData: false, success: function(response) { $('#summernote').summernote('insertImage', response.url); // Вставляем изображение }, error: function() { alert('Ошибка загрузки изображения!'); } }); } </script> </body> </html>
upload.php)<?php
header('Content-Type: application/json');
if (isset($_FILES['image'])) {
$uploadDir = 'uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
$fileName = uniqid() . '.png';
$filePath = $uploadDir . $fileName;
if (move_uploaded_file($_FILES['image']['tmp_name'], $filePath)) {
echo json_encode([
'success' => true,
'url' => $filePath // Можно заменить на полный URL (http://site.com/uploads/...)
]);
} else {
echo json_encode(['success' => false, 'error' => 'Ошибка загрузки']);
}
} else {
echo json_encode(['success' => false, 'error' => 'Нет файла']);
}
?>save.php)<?php
if (isset($_POST['content'])) {
$content = $_POST['content'];
file_put_contents('saved_content.html', $content);
echo 'Текст сохранён!';
} else {
echo 'Ошибка: нет данных!';
}
?>Пользователь вставляет изображение в Summernote → срабатывает onImageUpload.
Изображение отправляется на upload.php → сохраняется в папку uploads/.
Summernote получает URL изображения и вставляет его в редактор.
При отправке формы весь HTML (с <img src="uploads/...">) сохраняется в saved_content.html.
/ ├── index.html # Форма с редактором ├── upload.php # Загрузка изображений ├── save.php # Сохранение текста ├── uploads/ # Папка для картинок └── saved_content.html # Сохранённый HTML
Валидация изображений (проверка $_FILES['image']['type']).
Сжатие изображений (imagewebp(), imagejpeg()).
Защита от перезаписи (проверка file_exists()).
Теперь у вас есть простой WYSIWYG-редактор с загрузкой изображений! 🚀
Проблема в том, что у вас несколько одинаковых checkbox с одинаковыми id и name, но только один из них реагирует на клик по <label>. Это происходит потому, что:
ID должны быть уникальными в HTML-документе
Лейбел связан только с первым чекбоксом, так как ищет элемент по id="not_post_msg"
<!-- Пример 1 --> <div class="form-check"> <input class="form-check-input not-post-msg-checkbox" name="not_post_msg[]" type="checkbox" value="1"> <label class="form-check-label not-post-msg-label">без отправки письма</label> </div> <!-- Пример 2 --> <div class="form-check"> <input class="form-check-input not-post-msg-checkbox" name="not_post_msg[]" type="checkbox" value="1"> <label class="form-check-label not-post-msg-label">без отправки письма</label> </div>
Добавьте этот код:
$(document).ready(function() { // Обработка клика по любому лейбелу $('.not-post-msg-label').click(function() { // Находим связанный чекбокс в том же контейнере const checkbox = $(this).siblings('.not-post-msg-checkbox'); // Переключаем состояние checkbox.prop('checked', !checkbox.prop('checked')); }); });
Убрали дублирование id - теперь используем классы
Лейбелы привязаны к своим чекбоксам через структуру DOM
JavaScript обрабатывает клики по всем лейбелам с классом not-post-msg-label
Если нужно синхронизировать все чекбоксы:
$(document).ready(function() { $('.not-post-msg-label').click(function() { const isChecked = !$(this).siblings('.not-post-msg-checkbox').prop('checked'); $('.not-post-msg-checkbox').prop('checked', isChecked); }); });
Теперь при клике на любой лейбел будут переключаться все чекбоксы одновременно.
Чтобы добавлять новые элементы в самый верх <div class="add"> с помощью jQuery, используйте метод .prepend().
// Добавляем новый элемент В НАЧАЛО блока .add $('.add').prepend('<li>Новый элемент (вверху)</li>');
$('.add') — выбирает все элементы с классом add.
.prepend() — вставляет переданный HTML в начало выбранного элемента (перед всем существующим содержимым).
<div class="add"> <li>1</li> </div> <button id="addNewItem">Добавить элемент</button> <script> $(document).ready(function() { let counter = 1; // Счётчик для примера $('#addNewItem').click(function() { counter++; $('.add').prepend(`<li>${counter} (новый, вверху)</li>`); }); }); </script>
Каждый новый элемент будет добавляться сверху:
<div class="add"> <li>3 (новый, вверху)</li> <li>2 (новый, вверху)</li> <li>1</li> </div>
Если нужно добавить перед определенным элементом — используйте .before():
$('.add li:first').before('<li>Новый элемент перед первым</li>');
Если нужно добавить сложный элемент (например, с обработчиками событий):
const $newItem = $('<li>').text(counter + ' (динамический)').click(function() { alert('Клик по новому элементу!'); }); $('.add').prepend($newItem);
Теперь новые элементы всегда будут появляться первыми в блоке! 🚀
Да, браузер действительно может "засыпать" или приостанавливать выполнение JavaScript, особенно:
Когда вкладка неактивна (Chrome, Firefox и другие замедляют setInterval до ~1 раза в секунду).
При режиме энергосбережения (на мобильных устройствах или ноутбуках).
Если компьютер перешел в спящий режим.
visibilitychange для контроля активности вкладкиlet isTabActive = true; // Следим за активностью вкладки document.addEventListener('visibilitychange', function() { isTabActive = !document.hidden; }); function UpdateOrder() { if (!isTabActive) return; // Не обновлять, если вкладка неактивна const ordersID = '256'; $.post('ajax/post.php', { update_deal_full: ordersID }, function(data) { console.log(data); }, 'json').fail(function() { console.error("Ошибка запроса"); // Лучше console.error, чем alert }); } // Запускаем интервал const interval = setInterval(UpdateOrder, 3000);
Иногда сервер может блокировать частые запросы (например, из-за rate-limiting).
В post.php добавьте:
header('Content-Type: application/json'); // Чтобы избежать кеширования: header("Cache-Control: no-cache, must-revalidate"); echo json_encode(['status' => 'success', 'data' => $someData]);
Если запросы перестают работать, можно пересоздать setInterval.
let retryCount = 0; const MAX_RETRIES = 3; function UpdateOrder() { const ordersID = '256'; $.post('ajax/post.php', { update_deal_full: ordersID }, function(data) { console.log(data); retryCount = 0; // Сброс счетчика ошибок }, 'json').fail(function() { console.error("Ошибка запроса"); retryCount++; if (retryCount >= MAX_RETRIES) { clearInterval(interval); console.log("Прекращаем опрос из-за ошибок"); } }); } let interval = setInterval(UpdateOrder, 3000);
WebSocket вместо setInterval (если возможно)Если нужны мгновенные обновления, лучше использовать WebSockets или Server-Sent Events (SSE).
Проблема: Браузер замедляет/останавливает setInterval в фоновых вкладках.
Решение:
Контролировать активность вкладки (visibilitychange).
Обрабатывать ошибки и перезапускать интервал.
Использовать WebSockets/SSE для мгновенных обновлений.
Попробуйте эти методы, и запросы должны стабилизироваться! 🚀
Ошибка Uncaught SyntaxError: Identifier 'UpdateOrder' has already been declared возникает, когда вы пытаетесь повторно объявить функцию или переменную с тем же именем в одной области видимости.
Вы определяете функцию UpdateOrder() несколько раз (например, при повторном вызове setInterval или загрузке скрипта).
Возможно, скрипт подключается несколько раз на странице.
Убедитесь, что UpdateOrder() не определяется повторно.
let/const вместо var (если нужно переопределение)Если функция должна перезаписываться, можно использовать:
const UpdateOrder = function() { ... }; // или let
setInterval перед созданием нового// Если interval уже существует, очистите его if (typeof interval !== 'undefined') { clearInterval(interval); } // Затем создайте новый const interval = setInterval(UpdateOrder, 3000);
Убедитесь, что:
Скрипт не подключается несколько раз (например, через <script src="...">).
Нет вставки кода в нескольких местах.
// Объявляем функцию только один раз! function UpdateOrder() { const ordersID = '256'; $.post('ajax/post.php', { update_deal_full: ordersID }, function(data) { console.log(data); }, 'json').fail(function() { console.error("Ошибка запроса"); }); } // Очищаем старый интервал (если есть) if (window.updateInterval) { clearInterval(window.updateInterval); } // Запускаем новый интервал window.updateInterval = setInterval(UpdateOrder, 3000);
Проверьте консоль браузера (F12 → Console) на другие ошибки.
Используйте console.log() для отладки.
Если код вставляется через eval() или динамически, убедитесь, что он не дублируется.
Теперь ошибка Identifier 'UpdateOrder' has already been declared исчезнет! 🚀
Unchecked runtime.lastError: The message port closed before a response was receivedЭта ошибка возникает в расширениях Chrome (реже — в Firefox) при работе с асинхронными сообщениями между:
Фоновым скриптом (background.js)
Контент-скриптом (content_script.js)
Popup/Options-страницами
Ошибка означает, что порт для обмена сообщениями закрылся раньше, чем пришел ответ на отправленный запрос.
Отправка сообщения, но отсутствие return true в chrome.runtime.sendMessage
Если расширение отправляет сообщение, но не получает ответ (например, из-за ошибки в обработчике).
Слишком долгий ответ от chrome.runtime.onMessage
Если обработчик сообщения не отвечает вовремя, Chrome закрывает порт.
Использование chrome.tabs.sendMessage к несуществующей вкладке
Например, вкладка закрылась до получения ответа.
Неправильная обработка Promise в расширении
Если async/await или .then() не обработаны корректно.
return true в обработчик сообщений// В контент-скрипте или фоновом скрипте: chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.log("Получено сообщение:", request); sendResponse({ status: "OK" }); // Ответ серверу return true; // ⚠️ Важно: оставить порт открытым для асинхронного ответа });
sendResponse вызывается ДО завершения функции// ❌ Плохо (sendResponse после await): chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { const data = await fetch(...); sendResponse(data); // Может быть слишком поздно! }); // ✅ Правильно (ответ перед await): chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { fetch(...).then(data => sendResponse(data)); return true; // Разрешаем асинхронный ответ });
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { if (tabs[0]?.id) { chrome.tabs.sendMessage(tabs[0].id, { action: "ping" }, (response) => { if (chrome.runtime.lastError) { console.error("Ошибка:", chrome.runtime.lastError); } else { console.log("Ответ:", response); } }); } });
chrome.runtime.sendMessage({ action: "getData" }, (response) => { if (chrome.runtime.lastError) { console.error("Ошибка:", chrome.runtime.lastError); } else { console.log("Успех:", response); } });
Нет, если расширение работает корректно.
Да, если из-за этого ломается функционал (например, не приходят ответы).
Ошибка чаще всего влияет только на отладку, но не на пользователей. Однако лучше её починить.
Добавьте return true в onMessage.
Убедитесь, что sendResponse вызывается вовремя.
Проверяйте chrome.runtime.lastError.
Закрывайте порты явно, если нужно.
После этих правок ошибка пропадет! ✅
Проблема в том, что после вызова clearInterval(interval) интервал действительно прекращает работу, и для его возобновления нужно заново вызвать setInterval(). Вот как это правильно реализовать:
let interval; function startPolling() { interval = setInterval(UpdateOrder, 3000); // Запускаем интервал } function stopPolling() { clearInterval(interval); // Останавливаем интервал interval = null; // Очищаем переменную } // Для возобновления нужно снова вызвать: startPolling(); // Перезапускает интервал
let interval; let retryCount = 0; const MAX_RETRIES = 3; function UpdateOrder() { $.post('ajax/post.php', { update_deal_full: '256' }) .done(function(data) { console.log(data); retryCount = 0; // Сброс счетчика при успехе }) .fail(function() { console.error("Ошибка запроса"); retryCount++; if (retryCount >= MAX_RETRIES) { stopPolling(); // Останавливаем при многократных ошибках setTimeout(startPolling, 10000); // Перезапуск через 10 сек } }); } // Первый запуск startPolling();
function isPollingActive() { return interval !== null && interval !== undefined; } // Проверка перед действиями if (!isPollingActive()) { startPolling(); }
clearInterval() полностью удаляет таймер, и его нужно создавать заново
Переменная interval сохраняет ID таймера, но после очистки становится неактивной
class Poller { constructor() { this.interval = null; this.retryCount = 0; } start() { this.interval = setInterval(() => this.execute(), 3000); } stop() { clearInterval(this.interval); this.interval = null; } execute() { $.post('ajax/post.php', { update_deal_full: '256' }) .done(data => { console.log(data); this.retryCount = 0; }) .fail(() => { this.retryCount++; if (this.retryCount > 3) { this.stop(); setTimeout(() => this.start(), 10000); } }); } } // Использование const poller = new Poller(); poller.start(); // Для остановки // poller.stop();
После clearInterval() интервал нужно пересоздавать
Лучше использовать класс для управления состоянием
Добавляйте задержку перед перезапуском при ошибках
Всегда очищайте старый интервал перед созданием нового
Теперь ваш интервал будет корректно перезапускаться после остановки!
Проблема в том, что стандартный <input type="file"> сбрасывает предыдущие файлы при новом выборе. Вот как сделать, чтобы файлы накапливались:
// Массив для хранения всех выбранных файлов let allFiles = []; $(document).ready(function() { $('#files_to').on('change', function(e) { // Добавляем новые файлы к уже выбранным const newFiles = Array.from(e.target.files); allFiles = [...allFiles, ...newFiles]; // Создаем новый DataTransfer и добавляем все файлы const dataTransfer = new DataTransfer(); allFiles.forEach(file => dataTransfer.items.add(file)); // Присваиваем обновленный список файлов input'у e.target.files = dataTransfer.files; // Можно вывести информацию о выбранных файлах console.log('Всего файлов:', allFiles.length); allFiles.forEach((file, i) => { console.log(`${i+1}. ${file.name} (${(file.size/1024).toFixed(2)} KB)`); }); }); });
<input type="file" id="files_to" multiple class="custom-file-input"> <div id="file-list"></div> <script> $(document).ready(function() { const fileList = $('#file-list'); let allFiles = []; $('#files_to').on('change', function(e) { const newFiles = Array.from(e.target.files); allFiles = [...allFiles, ...newFiles]; // Обновляем отображение updateFileList(); // Очищаем значение input, чтобы можно было добавить те же файлы снова $(this).val(''); }); function updateFileList() { fileList.empty(); allFiles.forEach((file, index) => { fileList.append(` <div class="file-item"> ${file.name} (${(file.size/1024).toFixed(2)} KB) <button class="remove-file" data-index="${index}">×</button> </div> `); }); // Обработчик удаления файлов $('.remove-file').on('click', function() { const index = $(this).data('index'); allFiles.splice(index, 1); updateFileList(); }); } }); </script> <style> .file-item { padding: 5px; margin: 5px 0; background: #f5f5f5; display: flex; justify-content: space-between; } .remove-file { background: #ff4444; color: white; border: none; cursor: pointer; } </style>
При каждом выборе файлов мы добавляем их в массив allFiles
Используем DataTransfer для обновления списка файлов в input
Для удобства отображаем список выбранных файлов
Добавляем возможность удаления отдельных файлов
Для отправки формы нужно использовать FormData:
const formData = new FormData(); allFiles.forEach(file => formData.append('files_to[]', file));
Если нужна поддержка старых браузеров, можно использовать полифил для DataTransfer
Максимальное количество файлов ограничено настройками сервера (post_max_size, upload_max_filesize в PHP)
Это решение позволяет:
Добавлять файлы в несколько приемов
Видеть все выбранные файлы
Удалять ненужные файлы перед отправкой
Корректно отправлять все файлы на сервер
Отличный вопрос! Этот синтаксис называется spread operator (оператор расширения) в JavaScript. Давайте разберём его подробно.
...allFiles, ...newFiles?Это современный синтаксис ES6, который можно "развернуть" так:
// Было: allFiles = [...allFiles, ...newFiles]; // Эквивалентно: allFiles = allFiles.concat(newFiles); // Или по-старому: allFiles = [].concat(allFiles, newFiles);
...allFiles - "разворачивает" массив allFiles на отдельные элементы
...newFiles - аналогично разворачивает массив newFiles
[...allFiles, ...newFiles] - создаёт новый массив, куда сначала помещает все элементы из allFiles, затем все элементы из newFiles
const fruits = ['яблоко', 'груша']; const newFruits = ['апельсин', 'банан']; const allFruits = [...fruits, ...newFruits]; // Результат: ['яблоко', 'груша', 'апельсин', 'банан']
concat()?Spread operator:
Читается более естественно (видно, что происходит)
Может использоваться не только для массивов
Часто работает быстрее в современных браузерах
Копирование массивов:
const copy = [...original];
Передача элементов в функцию:
const numbers = [1, 2, 3]; Math.max(...numbers); // вместо Math.max.apply(null, numbers)
Объединение объектов:
const obj1 = { a: 1 }; const obj2 = { b: 2 }; const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2 }
Spread operator создаёт новый массив/объект, а не изменяет существующий
Работает во всех современных браузерах (кроме IE11)
Может использоваться с любыми итерируемыми объектами (массивы, строки, Set и т.д.)
Этот синтаксис делает код чище и понятнее, поэтому я рекомендую его использовать!
Проблема с пустым $_FILES возникает из-за неправильной подготовки FormData для отправки. Вот как это исправить:
// Массив для хранения файлов let allFiles = []; // При изменении input file $('#files_to').on('change', function(e) { const newFiles = Array.from(e.target.files); allFiles = [...allFiles, ...newFiles]; $(this).val(''); // Очищаем input }); // Отправка формы $('#myForm').on('submit', function(e) { e.preventDefault(); const formData = new FormData(this); // Удаляем стандартное поле files_to (оно пустое) formData.delete('files_to[]'); // Добавляем все накопленные файлы allFiles.forEach((file, index) => { formData.append(`files_to[${index}]`, file); }); // Отправка на сервер $.ajax({ url: 'upload.php', type: 'POST', data: formData, processData: false, contentType: false, success: function(response) { console.log('Файлы отправлены!'); allFiles = []; // Очищаем массив после отправки }, error: function(xhr) { console.error('Ошибка:', xhr.responseText); } }); });
FormData должна создаваться из формы (new FormData(this))
Удаляем пустое поле files_to[] (которое создается из input)
Добавляем файлы вручную с правильными именами (files_to[0], files_to[1] и т.д.)
Обязательные параметры для AJAX:
processData: false, // Не обрабатывать данные contentType: false // Не устанавливать Content-Type
<?php
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!empty($_FILES['files_to'])) {
$uploadedFiles = [];
foreach ($_FILES['files_to']['name'] as $index => $name) {
if ($_FILES['files_to']['error'][$index] === UPLOAD_ERR_OK) {
$tmpName = $_FILES['files_to']['tmp_name'][$index];
$newName = 'uploads/' . uniqid() . '_' . $name;
if (move_uploaded_file($tmpName, $newName)) {
$uploadedFiles[] = $newName;
}
}
}
echo json_encode(['success' => true, 'files' => $uploadedFiles]);
} else {
echo json_encode(['error' => 'No files uploaded']);
}
}
?>Лимит загрузки в php.ini:
upload_max_filesize = 20M post_max_size = 21M
Права на папку uploads:
chmod 755 uploads
Проверка MIME-типов (если нужно):
$allowedTypes = ['image/jpeg', 'image/png']; if (!in_array($_FILES['files_to']['type'][$index], $allowedTypes)) { // Запретить загрузку }
Теперь файлы будут корректно передаваться на сервер без потери данных! 🚀
Чтобы очистить массив allFiles после успешной отправки на сервер, нужно добавить сброс массива в обработчик успешной отправки. Вот как это правильно сделать:
let allFiles = []; // Массив для хранения файлов // Обработчик отправки формы $('#myForm').on('submit', function(e) { e.preventDefault(); const formData = new FormData(this); // Добавляем все файлы в FormData allFiles.forEach((file, index) => { formData.append(`files_to[${index}]`, file); }); $.ajax({ url: 'upload.php', type: 'POST', data: formData, processData: false, contentType: false, success: function(response) { // 1. Очищаем массив файлов allFiles = []; // 2. Очищаем список отображения файлов (если есть) $('#file-list').empty(); // 3. Сбрасываем input file $('#files_to').val(''); console.log('Файлы отправлены и список очищен!'); }, error: function(xhr) { console.error('Ошибка отправки:', xhr.responseText); } }); });
allFiles = [] - полностью пересоздает массив
Очистка визуального списка - если вы показываете файлы пользователю
Сброс input - позволяет заново выбирать те же файлы
Очистка при закрытии модального окна:
$('#modal').on('hidden.bs.modal', function() { allFiles = []; });
Кнопка "Очистить":
$('#clearFiles').click(function() { allFiles = []; $('#file-list').empty(); $('#files_to').val(''); });
При новом выборе файлов (если нужно сохранять только последние):
$('#files_to').on('change', function() { allFiles = Array.from(this.files); // Заменяем а не добавляем });
Всегда проверяйте успешность отправки перед очисткой
Для сложных случаев используйте allFiles.length = 0 (менее читаемо, но быстрее для больших массивов)
Если используете Vue/React - очищайте через методы состояния
Теперь ваш файловый менеджер будет корректно очищаться после каждой отправки! 🚀
Чтобы форма всегда отправляла поле files_to (даже пустое), но при этом корректно работала с файлами, можно реализовать следующую проверку:
$('#myForm').on('submit', function(e) { e.preventDefault(); const formData = new FormData(this); // Всегда удаляем стандартное поле (оно может быть пустым) formData.delete('files_to[]'); // Если есть файлы - добавляем их, если нет - добавляем пустое поле if (allFiles.length > 0) { allFiles.forEach(file => formData.append('files_to[]', file)); } else { formData.append('files_to[]', ''); // Пустое поле } $.ajax({ url: 'upload.php', type: 'POST', data: formData, processData: false, contentType: false, success: function(response) { allFiles = []; // Очищаем после отправки } }); });
// В обработчике submit: const hasFiles = allFiles.some(file => file instanceof File); if (hasFiles) { allFiles.forEach(file => formData.append('files_to[]', file)); } else { formData.append('files_to[]', new File([], '')); // Пустой файл }
if (isset($_FILES['files_to'])) { if ($_FILES['files_to']['error'][0] == UPLOAD_ERR_NO_FILE) { // Обработка случая, когда файлов нет echo "Получен пустой files_to"; } else { // Обработка загруженных файлов foreach ($_FILES['files_to']['tmp_name'] as $index => $tmpName) { if ($_FILES['files_to']['error'][$index] === UPLOAD_ERR_OK) { move_uploaded_file($tmpName, 'uploads/' . $_FILES['files_to']['name'][$index]); } } } }
Гарантированная отправка поля - поле files_to всегда присутствует в запросе
Гибкая проверка - учитываются как пустые отправки, так и реальные файлы
Совместимость - работает с любой серверной логикой
Чистый код - понятная и поддерживаемая структура
Это решение обеспечит корректную работу формы как с файлами, так и без них, удовлетворяя требованиям серверной части.