Ваша конфиденциальность очень важна для нас на TTOK.com. В этом документе о политике в области конфиденциальности описывается, что получают данные TTOK.com и как мы храним, управляем и защищаем их.
Журналы
TTOK.com использует файлы журнала, которые являются файлами, которые регистрируют информацию, включая запрашиваемые ресурсы веб-страницы, IP-адресы, типы браузеров и временные метки. Эта информация используется TTOK.com для выявления неисправностей, сервисного анализа и обслуживания сайта. Эта информация не может быть непосредственно связана с персонально идентифицируемой информацией. Она также не связана с видео- или аудио. В большинстве случаев IP-адрес используется для вычисления до 3 записей, если они были сделаны, но не какие. Срок действия истекает через один час.
Журнальные файлы и любые содержащиеся в них данные не передаются какой-либо третьей стороне.
Собранная информация
TTOK.com получает и хранит информацию, которую вы вводите на веб-сайте. Это включает в себя запрашиваемые ресурсы, запросы поиска и настройки сайта. Эта информация хранится для мониторинга и анализа сервисной деятельности. Эта информация не может быть связана с персональной идентифицируемой информацией.
Вся введенная вами информация носит добровольный характер. Ни одна из этих данных не передается какой-либо третьей стороне.
TTOK.com не соберут никакой личной информации о вас, за исключением тех случаев, когда вы представляете такую информацию специально и сознательно.
Печенье
TTOK.com храните свои предпочтения в печенье. В то время как эти печенье улучшают ваш опыт, вспоминая ваши настройки, они полностью факультативны. Все параметры и услуги TTOK.com работают без печенья.
Печенье может храниться третьими сторонами через контент третьих сторон, встроенный в TTOK.com, например, мультимедийные игроки и анализы. TTOK.com не имеют контроля над печеньями, хранящимися третьими сторонами.
Вопросы
Если у вас есть какие-либо вопросы об этой политике конфиденциальности, не стесняйтесь обращаться к нам {
const el = input[0];
if (!el) return;
el.focus({ preventScroll: false });
const original = el.placeholder;
const isMac = /Mac|iPhone|iPad|iPod/.test(navigator.platform || '');
el.placeholder = isMac ? 'Press ⌘V to paste' : 'Press Ctrl+V to paste';
setTimeout(() => { el.placeholder = original; }, 4000);
};
try {
if (navigator.clipboard && navigator.clipboard.readText) {
const clipboardText = await navigator.clipboard.readText();
if (clipboardText) {
input.val(clipboardText).trigger("change");
} else {
focusForManualPaste();
}
} else {
focusForManualPaste();
}
} catch (err) {
focusForManualPaste();
}
});
function initClipSlider(info, duration) {
var sliderEl = info.find('.clip-slider')[0];
if (sliderEl.noUiSlider) return;
info.find('[data-input=clip-start]').val(0);
info.find('[data-input=clip-end]').val(duration);
info.find('[data-input=clip-start-display]').val(utils.formatDuration(0));
info.find('[data-input=clip-end-display]').val(utils.formatDuration(duration));
noUiSlider.create(sliderEl, {
start: [0, duration],
connect: true,
step: 0.1,
range: {'min': 0, 'max': duration},
behaviour: 'tap-drag'
});
sliderEl.noUiSlider.on('update', function (values, handle) {
var sec = parseFloat(values[handle]);
if (handle === 0) {
info.find('[data-input=clip-start]').val(sec);
info.find('[data-input=clip-start-display]').val(utils.formatDuration(sec));
} else {
info.find('[data-input=clip-end]').val(sec);
info.find('[data-input=clip-end-display]').val(utils.formatDuration(sec));
}
});
sliderEl.noUiSlider.on('change', function () {
resetDownloadButton(info);
});
info.find('[data-input=clip-start-display]').on('change', function () {
var sec = utils.parseDuration($(this).val());
var maxVal = parseFloat(info.find('[data-input=clip-end]').val());
sec = Math.max(0, Math.min(sec, maxVal));
sliderEl.noUiSlider.set([sec, null]);
$(this).val(utils.formatDuration(sec));
});
info.find('[data-input=clip-end-display]').on('change', function () {
var sec = utils.parseDuration($(this).val());
var minVal = parseFloat(info.find('[data-input=clip-start]').val());
sec = Math.max(minVal, Math.min(sec, duration));
sliderEl.noUiSlider.set([null, sec]);
$(this).val(utils.formatDuration(sec));
});
}
container.on("change", "[data-input=clip-toggle]", function () {
var info = $(this).closest('.result-item-info');
var clipInputs = info.find('.clip-time-inputs');
if (this.checked) {
if (!hasPro) {
this.checked = false;
var modal = new bootstrap.Modal(document.getElementById('upgradeModal'));
modal.show();
return;
}
clipInputs.show();
var cachedDuration = info.data('duration');
if (cachedDuration) {
info.find('.clip-fields').show();
info.find('.clip-loading').hide();
initClipSlider(info, cachedDuration);
} else {
var attrDuration = info.attr('data-duration');
if (attrDuration && parseFloat(attrDuration) > 0) {
var dur = parseFloat(attrDuration);
info.data('duration', dur);
info.find('.clip-fields').show();
info.find('.clip-loading').hide();
initClipSlider(info, dur);
} else {
info.find('.clip-loading').show();
info.find('.clip-fields').hide();
info.find('.clip-error').hide();
var itemPageUrl = info.attr('data-item-page-url') || '';
var pageUrl = decodeURIComponent(info.find('.download-button').data('page-url') || '');
var directUrl = decodeURIComponent(info.find('.download-button').data('url') || '');
var fetchUrl = itemPageUrl || pageUrl || directUrl;
$.ajax({
url: '/api/duration/',
type: 'POST',
data: {
url: fetchUrl,
csrfmiddlewaretoken: '7J9v3K2f8IL3lfVo8sJPPFoERPlwG0lyRbFCpWsFZxqKyEBfYihUeeWB7tlY0q2k'
},
success: function (data) {
if (data.duration) {
info.data('duration', data.duration);
info.find('.clip-loading').hide();
info.find('.clip-fields').show();
initClipSlider(info, data.duration);
} else {
info.find('.clip-loading').hide();
info.find('.clip-error').text('Could not get duration').show();
}
},
error: function () {
info.find('.clip-loading').hide();
info.find('.clip-error').text('Could not get duration').show();
}
});
}
}
} else {
clipInputs.hide();
}
resetDownloadButton(info);
});
container.on("click", ".download-button", function (e) {
var target = $(this);
var info = target.closest('.result-item-info');
var itemType = target.data('type') || '';
var format = info.find('[data-input=format]:checked').val() || '';
var quality = info.find('[data-input=quality]').val() || '';
var h264 = info.find('input[name=h264]').is(':checked');
// Image conversion (Pro only)
if (itemType === 'image' && GO_API_URL) {
var imgFormat = info.find('[data-input=img-format]:checked').val() || '';
var imgWidth = info.find('[data-input=img-width]').val() || '';
var imgHeight = info.find('[data-input=img-height]').val() || '';
var imgAspectRatio = info.find('[data-input=img-aspect-ratio]').val() || '';
var needsImageConversion = imgFormat !== '' || imgWidth !== '' || imgHeight !== '' || imgAspectRatio !== '';
if (needsImageConversion) {
e.preventDefault();
if (!hasPro) {
var modal = new bootstrap.Modal(document.getElementById('upgradeModal'));
modal.show();
return;
}
var sourceUrl = decodeURIComponent(target.data('url'));
var fn = target.data('fn') || 'image';
var form = document.createElement('form');
form.method = 'POST';
form.action = '/api/convert/image/';
form.style.display = 'none';
function addImgField(name, value) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = value;
form.appendChild(input);
}
addImgField('csrfmiddlewaretoken', '7J9v3K2f8IL3lfVo8sJPPFoERPlwG0lyRbFCpWsFZxqKyEBfYihUeeWB7tlY0q2k');
addImgField('url', sourceUrl);
addImgField('title', fn);
if (imgFormat) addImgField('image_format', imgFormat);
if (imgWidth) addImgField('width', imgWidth);
if (imgHeight) addImgField('height', imgHeight);
if (imgAspectRatio) addImgField('aspect_ratio', imgAspectRatio);
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
onDownloadButton.call(target[0]);
return;
}
}
var needsConversion = (format === 'wav') || (quality !== '') || h264;
if (needsConversion && !hasPro) {
e.preventDefault();
var modal = new bootstrap.Modal(document.getElementById('upgradeModal'));
modal.show();
return;
}
if (needsConversion && GO_API_URL) {
e.preventDefault();
var pageUrl = decodeURIComponent(target.data('page-url') || '');
var fn = target.data('fn') || 'download';
// For TikTok: use proxy URL as source (yt-dlp can't extract TikTok)
var sourceUrl = pageUrl;
if (pageUrl.indexOf('tiktok.com') !== -1) {
var proxyType = (itemType === 'audio') ? 'audio' : 'video';
sourceUrl = GO_API_URL + '/api/proxy?url=' + encodeURIComponent(pageUrl) + '&type=' + proxyType + '&fn=' + encodeURIComponent(fn);
}
var fmt;
if (format === 'wav') {
fmt = 'wav';
} else if (itemType === 'audio') {
fmt = 'mp3';
} else {
fmt = 'mp4';
}
// POST via hidden form to Django proxy (same-origin, streams from Go binary)
var form = document.createElement('form');
form.method = 'POST';
form.action = '/api/convert/' + fmt + '/';
form.style.display = 'none';
function addField(name, value) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = value;
form.appendChild(input);
}
addField('csrfmiddlewaretoken', '7J9v3K2f8IL3lfVo8sJPPFoERPlwG0lyRbFCpWsFZxqKyEBfYihUeeWB7tlY0q2k');
addField('url', sourceUrl);
addField('title', fn);
if (fmt === 'mp3' && quality) addField('audio_quality', quality + 'k');
if (fmt === 'mp4' && quality) addField('video_quality', quality);
if (fmt === 'mp4' && h264) addField('h264', 'true');
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
onDownloadButton.call(this);
} else {
onDownloadButton.call(this);
}
});
container.on("change", "[data-input=format]", function () {
var info = $(this).closest('.result-item-info');
var row = info.find('.quality-row');
if ($(this).val() === 'wav') {
row.addClass("d-none");
} else {
row.removeClass("d-none");
}
resetDownloadButton(info);
});
container.on("change input", "[data-input=quality]", function () {
resetDownloadButton($(this).closest('.result-item-info'));
});
container.on("change", "input[name=h264]", function () {
resetDownloadButton($(this).closest('.result-item-info'));
});
onChangeURLValue();
function onDownloadAll() {
var target = $(this);
target.find(".spinner-border").removeClass("d-none");
target.prop("disabled", true);
var filter = container.find('input[name="downloadFilter"]:checked').val() || 'all';
var downloadLinks = [];
var downloadButtons = container.find(".download-button");
downloadButtons.each(function (index, item) {
var type = $(item).data("type") || "";
if (filter === "all" || type === filter || (filter === "video" && (type === "video" || type === "video_watermark"))) {
downloadLinks.push($(item).data("url"));
$(item).addClass("downloaded");
}
});
if (downloadLinks.length === 0) {
target.find(".spinner-border").addClass("d-none");
target.prop("disabled", false);
return;
}
var batchDownloadI18n = {
intro: "We detected {count} files. To keep things reliable we will download them in {n} batches of up to {size}. Each batch is saved separately as it finishes.",
progress: "Batch {current} of {total}",
retrying: "Batch {batch} failed, retrying in {sec}s…",
failed: "Batch {batch} failed after retries. Press Resume to try again.",
done_status: "All batches downloaded.",
resume_prompt: "You already downloaded {done} of {total} batches for this list. Continue from there?",
cancel_confirm: "Cancel the remaining batches?"
};
batchDownload.show({
urls: downloadLinks,
baseName: "TTOK",
csrfToken: "7J9v3K2f8IL3lfVo8sJPPFoERPlwG0lyRbFCpWsFZxqKyEBfYihUeeWB7tlY0q2k",
goApiUrl: GO_API_URL || "",
i18n: batchDownloadI18n,
onAllDone: function () {
setTimeout(function () {
target.find(".spinner-border").addClass("d-none");
target.text('Поделиться TToK.com');
target.removeClass('btn-primary').addClass('btn-success');
target.prop("disabled", false);
launchConfetti(target, 150, 2200);
}, 100);
},
onError: function (err) {
alert(err && err.message ? err.message : err);
target.find(".spinner-border").addClass("d-none");
target.prop("disabled", false);
},
onCancel: function () {
target.find(".spinner-border").addClass("d-none");
target.prop("disabled", false);
}
});
}
var individualDownloadCount = 0;
function onDownloadButton() {
var target = $(this);
if (!target.data('original-html')) {
target.data('original-html', target.html());
target.data('original-class', target.attr('class'));
}
target.find(".spinner-border").removeClass("d-none");
setTimeout(function () {
target.find(".spinner-border").addClass("d-none");
target.text('Поделиться TToK.com');
target.removeClass('btn-outline-primary').addClass('btn-success');
launchConfetti(target, 150, 2200);
}, 100)
if (!hasPro) {
individualDownloadCount++;
var banner = $('#batchUpsellBanner');
if (banner.length && individualDownloadCount >= 3) {
$('#batchUpsellCount').text(individualDownloadCount);
banner.removeClass('d-none');
}
}
}
function resetDownloadButton(info) {
var btn = info.find('.download-button');
var originalHtml = btn.data('original-html');
if (originalHtml) {
btn.html(originalHtml);
btn.attr('class', btn.data('original-class'));
}
}
// Multi-select for batch download
container.on('change', '.select-item-checkbox', function () {
var checked = container.find('.select-item-checkbox:checked');
var count = checked.length;
if (!hasPro && count > 1) {
$(this).prop('checked', false);
var modal = new bootstrap.Modal(document.getElementById('upgradeModal'));
modal.show();
return;
}
$('#selectedCount').text(count);
if (count > 1) {
$('#downloadSelectedBtn').removeClass('d-none');
} else {
$('#downloadSelectedBtn').addClass('d-none');
}
});
$('#downloadSelectedBtn').on('click', function () {
var target = $(this);
target.find('.spinner-border').removeClass('d-none');
target.prop('disabled', true);
var selectedUrls = [];
container.find('.select-item-checkbox:checked').each(function () {
selectedUrls.push($(this).data('url'));
});
if (selectedUrls.length === 0) {
target.find('.spinner-border').addClass('d-none');
target.prop('disabled', false);
return;
}
var batchDownloadI18n = {
intro: "We detected {count} files. To keep things reliable we will download them in {n} batches of up to {size}. Each batch is saved separately as it finishes.",
progress: "Batch {current} of {total}",
retrying: "Batch {batch} failed, retrying in {sec}s…",
failed: "Batch {batch} failed after retries. Press Resume to try again.",
done_status: "All batches downloaded.",
resume_prompt: "You already downloaded {done} of {total} batches for this list. Continue from there?",
cancel_confirm: "Cancel the remaining batches?"
};
batchDownload.show({
urls: selectedUrls,
baseName: "TTOK-selected",
csrfToken: "7J9v3K2f8IL3lfVo8sJPPFoERPlwG0lyRbFCpWsFZxqKyEBfYihUeeWB7tlY0q2k",
goApiUrl: GO_API_URL || "",
i18n: batchDownloadI18n,
onAllDone: function () {
target.find('.spinner-border').addClass('d-none');
target.prop('disabled', false);
launchConfetti(target, 150, 2200);
},
onError: function (err) {
alert(err && err.message ? err.message : 'An error occurred');
target.find('.spinner-border').addClass('d-none');
target.prop('disabled', false);
},
onCancel: function () {
target.find('.spinner-border').addClass('d-none');
target.prop('disabled', false);
}
});
});
function launchConfetti($btn, amount, lifetime) {
var colors = ['#FF4757', '#2ED573', '#1E90FF', '#FFA502', '#A55EEA', '#2ECC71', '#FF6B81'];
var offset = $btn.offset();
var btnWidth = $btn.outerWidth();
var btnHeight = $btn.outerHeight();
for (var i = 0; i < amount; i++) {
var $c = $('');
var size = 3 + Math.random() * 4;
var dur = 0.8 + Math.random() * 0.8;
var delay = Math.random() * 0.2;
var color = colors[Math.floor(Math.random() * colors.length)];
var rotateStart = Math.floor(Math.random() * 360);
var startLeft = offset.left + Math.random() * btnWidth;
var startTop = offset.top + Math.random() * btnHeight;
$c.css({
position: 'absolute',
left: startLeft + 'px',
top: startTop + 'px',
background: color,
width: size + 'px',
height: (size + 2) + 'px',
transform: 'rotate(' + rotateStart + 'deg)',
animation: 'fall linear forwards',
animationDuration: dur + 's',
animationDelay: delay + 's',
zIndex: 9999,
pointerEvents: 'none',
opacity: 0.9
});
$('body').append($c);
(function (el) {
setTimeout(function () {
el.remove();
}, lifetime || 2000);
})($c);
}
}
function onChangeURLValue() {
const inputValue = $('#url').val();
if (!inputValue) {
return
}
const button = $('#copyPasteText');
const pasteText = button.data('paste');
const clearText = button.data('clear');
if (inputValue.trim() === '') {
button.text(pasteText);
} else {
button.text(clearText);
}
}
function onLoadMore() {
var wrapper = $(this).closest(".job-wrapper");
container.find("#loadMoreEntries").prop("disabled", true);
container.find("#loadMoreEntries").find(".spinner-border").removeClass("d-none");
var target = container.find("#form");
var formData = target.serializeArray();
var nextPageToken = wrapper.find("[name=next_page_token]").val();
formData.push({name: 'page', value: page});
formData.push({name: 'next_page_token', value: nextPageToken});
$.ajax({
url: `/api/extract/`,
type: 'POST',
data: formData,
success: function (data) {
if (data.error === true || data.success === false) {
$('.m-progress').removeClass('m-progress').removeAttr('disabled');
container.find("#loadMoreEntries .spinner-border").addClass("d-none");
container.find("#loadMoreEntries").prop("disabled", false);
if (data.html) {
wrapper.find(".job-result").append(data.html);
}
return;
}
wrapper.find("#actionsWrapper").remove();
wrapper.find(`.job-result #postListWrapper`).append(data.html);
wrapper.find(`[name=next_page_token]`).val(data.next_page_token);
if (!data.next_page_token) {
container.find("#loadMoreEntries").remove();
} else {
container.find("#loadMoreEntries .spinner-border").addClass("d-none");
container.find("#loadMoreEntries").removeClass('m-progress').removeAttr('disabled');
}
initializeLazyLoad();
page += 1;
},
error: function (xhr, status, error) {
container.find("#loadMoreEntries .spinner-border").addClass("d-none");
container.find("#loadMoreEntries").prop("disabled", false);
try {
var response = JSON.parse(xhr.responseText);
if (response.html) {
wrapper.find(".job-result").append(response.html);
}
} catch (e) {
alert(error);
}
}
});
}
async function resolveShortUrl(url) {
// Try to resolve TikTok short URLs client-side
// This works because fetch from user's browser isn't rate-limited like server
try {
const response = await fetch(url, {
method: 'HEAD',
redirect: 'follow'
});
// If we got here, response.url contains the final URL
if (response.url && response.url.includes('/video/') || response.url.includes('/photo/')) {
return response.url;
}
} catch (e) {
// CORS error - expected, but browser still followed redirect
// Try alternative: open in hidden iframe approach won't work
// Fall back to letting server handle it
}
return url; // Return original if can't resolve
}
function isTikTokShortUrl(url) {
return url.match(/^https?:\/\/(vt|vm)\.tiktok\.com\//i);
}
async function onSubmit(e) {
page = 1;
var target = $(this);
container.find("#result").html("");
container.find("#heading").html("");
e.preventDefault();
var urlInput = target.find("#url").val();
if (!isValidURL(urlInput)) {
alert("Введите действительный URL-адрес.");
return;
}
// Try to resolve TikTok short URLs client-side to avoid server rate limits
if (isTikTokShortUrl(urlInput)) {
target.find("button").addClass("m-progress").attr('disabled', 'disabled');
try {
var resolvedUrl = await resolveShortUrl(urlInput);
if (resolvedUrl !== urlInput) {
target.find("#url").val(resolvedUrl);
urlInput = resolvedUrl;
}
} catch (e) {
// Continue with original URL
}
target.find("button").removeClass("m-progress").removeAttr('disabled');
}
target.find("button").addClass("m-progress").attr('disabled', 'disabled');
// Show loader
var wrapperId = 'extract-' + Date.now();
var wrapperHtml = "";
container.find("#result").append(wrapperHtml);
container.find("#" + wrapperId + " .job-result").append($(loader));
$.ajax({
url: `/api/extract/`,
type: 'POST',
data: target.serialize(),
success: function (data) {
if (data.error === true || data.success === false) {
$('.m-progress').removeClass('m-progress').removeAttr('disabled');
if (data.html) {
container.find("#" + wrapperId + " .job-result").html(data.html);
} else {
container.find("#" + wrapperId + " .job-result").html(
'Произошла неизвестная ошибка.
'
);
}
return;
}
container.find("#heading").html(data.heading);
container.find("#" + wrapperId + " .job-result").html(data.html);
container.find("#" + wrapperId + " [name=next_page_token]").val(data.next_page_token);
if (!data.next_page_token) {
container.find("#loadMoreEntries").remove();
}
$('.m-progress').removeClass('m-progress').removeAttr('disabled');
initializeLazyLoad();
page += 1;
},
error: function (xhr, status, error) {
$('.m-progress').removeClass('m-progress').removeAttr('disabled');
try {
var errorJSON = JSON.parse(xhr.responseText);
// Rate limit error
if (errorJSON.rate_limit || errorJSON.no_credits) {
container.find("#" + wrapperId).remove();
container.find("#rateModal .exceeded-wrapper").html(errorJSON.error);
container.find('#rateModal').modal('show');
if (errorJSON.until) {
var rateInterval;
var totalSeconds = errorJSON.until;
container.find('.time-exceeded').text(utils.formatDuration(totalSeconds));
rateInterval = setInterval(
function () {
totalSeconds--;
container.find('.time-exceeded').text(utils.formatDuration(totalSeconds));
if (totalSeconds < 0) {
clearInterval(rateInterval);
window.location.reload();
}
}, 1000
);
}
return;
}
// Bulk URL error
if (errorJSON.bulk === false) {
container.find("#" + wrapperId).remove();
container.find("#rateModal .exceeded-wrapper").html(errorJSON.error);
container.find('#rateModal').modal('show');
return;
}
// Other error with HTML
if (errorJSON.html) {
container.find("#" + wrapperId + " .job-result").html(errorJSON.html);
} else if (errorJSON.error) {
container.find("#" + wrapperId + " .job-result").html(
'' + errorJSON.error + '
'
);
}
} catch (e) {
container.find("#" + wrapperId + " .job-result").html(
'При обработке запроса произошла ошибка.
'
);
}
}
});
}
function isValidURL(string) {
try {
// Extraer solo la parte antes de los parámetros (?)
let baseUrl = string.split("?")[0];
// Intentar crear un objeto URL para validar
let url = new URL(baseUrl);
// Asegurar que el hostname (dominio) es válido
return !!(url.hostname && url.hostname.includes("."));
} catch (e) {
return false; // Si la URL no es válida, devolver false
}
}
function getResults(jobId, $wrapper, onComplete, next_page_token) {
$.ajax({
url: `/api/result/`,
type: 'GET',
data: {
job_id: jobId,
nextPageToken: next_page_token,
},
success: function (data) {
if (data.loading) {
setTimeout(function () {
getResults(jobId, $wrapper, onComplete, next_page_token);
}, 4000);
return;
}
// Check if response indicates failure (even with status 200)
// This happens when job completes but with errors
if (data.error === true || data.success === false) {
$('.m-progress').removeClass('m-progress').removeAttr('disabled');
if (data.html) {
container.find(`#${jobId} .job-result`).html(data.html);
} else {
container.find(`#${jobId} .job-result`).html(
'Произошла неизвестная ошибка.
'
);
}
if (onComplete) onComplete();
return;
}
container.find("#heading").html(data.heading);
if (page > 1) {
$wrapper.find("#actionsWrapper").remove();
$wrapper.find(`.job-result #postListWrapper`).append(data.html);
$wrapper.find(`[name=next_page_token]`).val(data.next_page_token);
} else {
container.find(`#${jobId} .job-result`).html(data.html);
container.find(`#${jobId} [name=next_page_token]`).val(data.next_page_token);
}
if (!data.next_page_token) {
container.find("#loadMoreEntries").remove();
} else {
container.find("#loadMoreEntries .spinner-border").addClass("d-none");
container.find("#loadMoreEntries").removeClass('m-progress').removeAttr('disabled');
}
$('.m-progress').removeClass('m-progress').removeAttr('disabled');
initializeLazyLoad();
page += 1;
if (onComplete) onComplete();
},
error: function (xhr, status, error) {
$('.m-progress').removeClass('m-progress').removeAttr('disabled');
try {
const response = JSON.parse(xhr.responseText);
if (response.html) {
container.find(`#${jobId} .job-result`).html(response.html);
} else {
container.find(`#${jobId} .job-result`).html(
'Произошла неизвестная ошибка.
'
);
}
} catch (e) {
container.find(`#${jobId} .job-result`).html(
'При обработке запроса произошла ошибка.
'
);
}
if (onComplete) onComplete();
}
});
}
function initializeLazyLoad() {
const lazyImages = document.querySelectorAll('img.lazyload[data-src]');
if (!('IntersectionObserver' in window)) {
// Fallback simple para navegadores antiguos
lazyImages.forEach(img => {
img.src = img.getAttribute('data-src');
img.removeAttribute('data-src');
img.classList.remove('lazyload');
});
return;
}
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
if (!img.getAttribute('data-src')) return;
img.src = img.getAttribute('data-src');
img.removeAttribute('data-src');
img.onload = () => {
const parent = img.closest('.thumbnail-container');
const loader = parent ? parent.querySelector('.loader-gif') : null;
if (loader) {
loader.style.display = 'none';
}
};
img.classList.remove('lazyload');
observer.unobserve(img);
}
});
});
lazyImages.forEach(img => observer.observe(img));
// Initialize Bootstrap tooltips on newly loaded content
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function (el) {
if (!bootstrap.Tooltip.getInstance(el)) {
new bootstrap.Tooltip(el);
}
});
}
}
);
}());
// Exit-intent popup for non-PRO users
(function () {
var exitShown = false;
document.addEventListener('mouseout', function (e) {
if (e.clientY > 0 || exitShown || sessionStorage.getItem('exitShown')) return;
if (e.relatedTarget || e.toElement) return;
exitShown = true;
sessionStorage.setItem('exitShown', '1');
var exitModal = document.getElementById('exitIntentModal');
if (exitModal) {
var modal = new bootstrap.Modal(exitModal);
modal.show();
}
});
})();