601 lines
22 KiB
JavaScript
601 lines
22 KiB
JavaScript
"use strict";
|
|
|
|
const urlInput = document.getElementById("urlInput");
|
|
const resolveButton = document.getElementById("resolveButton");
|
|
const settingsButton = document.getElementById("settingsButton");
|
|
const settingsPanel = document.getElementById("settingsPanel");
|
|
const baseFolderInput = document.getElementById("baseFolder");
|
|
const groupByProfileInput = document.getElementById("groupByProfile");
|
|
const saveAsInput = document.getElementById("saveAs");
|
|
const settingsPathPreview = document.getElementById("settingsPathPreview");
|
|
const settingsSaveButton = document.getElementById("settingsSaveButton");
|
|
const settingsResetButton = document.getElementById("settingsResetButton");
|
|
const settingsStatus = document.getElementById("settingsStatus");
|
|
const alternativeToggle = document.getElementById("alternativeToggle");
|
|
const alternativePanel = document.getElementById("alternativePanel");
|
|
const diagnosticButton = document.getElementById("diagnosticButton");
|
|
const diagnosticStatus = document.getElementById("diagnosticStatus");
|
|
const saveDestination = document.getElementById("saveDestination");
|
|
const statusBox = document.getElementById("status");
|
|
const collectionBox = document.getElementById("collection");
|
|
const collectionTitle = document.getElementById("collectionTitle");
|
|
const collectionCount = document.getElementById("collectionCount");
|
|
const collectionItems = document.getElementById("collectionItems");
|
|
const resultBox = document.getElementById("result");
|
|
const thumbnail = document.getElementById("thumbnail");
|
|
const mediaTitle = document.getElementById("mediaTitle");
|
|
const formatsBox = document.getElementById("formats");
|
|
const progressCard = document.getElementById("progressCard");
|
|
const progressLabel = document.getElementById("progressLabel");
|
|
const progressValue = document.getElementById("progressValue");
|
|
const progressBar = document.getElementById("progressBar");
|
|
|
|
let currentMedia = null;
|
|
let pollingTimer = null;
|
|
let activeTabId = null;
|
|
let selectedProfileSlug = null;
|
|
let activeTabUrl = "";
|
|
let alternativeScanned = false;
|
|
const DEFAULT_DOWNLOAD_SETTINGS = Object.freeze({
|
|
baseFolder: "RedGifs",
|
|
groupByProfile: true,
|
|
saveAs: false
|
|
});
|
|
let downloadSettings = Object.assign({}, DEFAULT_DOWNLOAD_SETTINGS);
|
|
let settingsStatusTimer = null;
|
|
let popupBlurTimer = null;
|
|
const panelAnimations = new WeakMap();
|
|
|
|
function panelIsExpanded(element) {
|
|
if (element.dataset.popupExpanded === "true") return true;
|
|
if (element.dataset.popupExpanded === "false") return false;
|
|
return !element.classList.contains("hidden");
|
|
}
|
|
|
|
function clearPanelMotionStyles(element) {
|
|
element.style.removeProperty("overflow");
|
|
element.style.removeProperty("will-change");
|
|
element.style.removeProperty("transform-origin");
|
|
}
|
|
|
|
function setPanelExpanded(element, expanded, options) {
|
|
const config = Object.assign({ duration: 260, offset: 8, scale: 0.985 }, options || {});
|
|
const previous = panelAnimations.get(element);
|
|
if (previous) previous.cancel();
|
|
clearPanelMotionStyles(element);
|
|
element.dataset.popupExpanded = String(expanded);
|
|
|
|
const reduceMotion = typeof window.matchMedia === "function"
|
|
&& window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
if (reduceMotion || typeof element.animate !== "function") {
|
|
element.classList.toggle("hidden", !expanded);
|
|
panelAnimations.delete(element);
|
|
return Promise.resolve();
|
|
}
|
|
|
|
if (expanded) element.classList.remove("hidden");
|
|
if (!expanded && element.classList.contains("hidden")) return Promise.resolve();
|
|
|
|
const fullHeight = Math.max(1, Math.ceil(element.getBoundingClientRect().height));
|
|
const computedStyle = window.getComputedStyle(element);
|
|
const expandedBox = {
|
|
marginTop: computedStyle.marginTop,
|
|
marginBottom: computedStyle.marginBottom,
|
|
paddingTop: computedStyle.paddingTop,
|
|
paddingBottom: computedStyle.paddingBottom,
|
|
borderTopWidth: computedStyle.borderTopWidth,
|
|
borderBottomWidth: computedStyle.borderBottomWidth
|
|
};
|
|
element.style.overflow = "hidden";
|
|
element.style.willChange = "height, margin, padding, border-width, opacity, transform";
|
|
element.style.transformOrigin = "top center";
|
|
|
|
const collapsedFrame = {
|
|
height: "0px",
|
|
marginTop: "0px",
|
|
marginBottom: "0px",
|
|
paddingTop: "0px",
|
|
paddingBottom: "0px",
|
|
borderTopWidth: "0px",
|
|
borderBottomWidth: "0px",
|
|
opacity: 0,
|
|
transform: `translateY(-${config.offset}px) scale(${config.scale})`
|
|
};
|
|
const expandedFrame = {
|
|
height: fullHeight + "px",
|
|
...expandedBox,
|
|
opacity: 1,
|
|
transform: "translateY(0) scale(1)"
|
|
};
|
|
const animation = element.animate(
|
|
expanded ? [collapsedFrame, expandedFrame] : [expandedFrame, collapsedFrame],
|
|
{
|
|
duration: config.duration,
|
|
easing: expanded ? "cubic-bezier(.22, 1, .36, 1)" : "cubic-bezier(.4, 0, .2, 1)"
|
|
}
|
|
);
|
|
panelAnimations.set(element, animation);
|
|
|
|
return animation.finished.catch(() => {}).then(() => {
|
|
if (panelAnimations.get(element) !== animation) return;
|
|
if (!expanded) element.classList.add("hidden");
|
|
clearPanelMotionStyles(element);
|
|
panelAnimations.delete(element);
|
|
});
|
|
}
|
|
|
|
function reportPopupDiagnostic(level, event, details) {
|
|
try {
|
|
const pending = chrome.runtime.sendMessage({
|
|
action: "diagnosticEvent",
|
|
level,
|
|
area: "popup",
|
|
event,
|
|
details: details || {}
|
|
});
|
|
if (pending && typeof pending.catch === "function") pending.catch(() => {});
|
|
} catch {
|
|
// A failed logger must not replace the original UI error.
|
|
}
|
|
}
|
|
|
|
function cleanFolderPath(value) {
|
|
return String(value || "")
|
|
.replace(/\\/g, "/")
|
|
.split("/")
|
|
.map((part) => part.replace(/[<>:"|?*\u0000-\u001f]/g, "_").replace(/[. ]+$/g, "").trim())
|
|
.filter((part) => part && part !== "." && part !== "..")
|
|
.slice(0, 8)
|
|
.join("/");
|
|
}
|
|
|
|
function profileFromUrl(value) {
|
|
try {
|
|
const match = new URL(value).pathname.match(/\/users\/([^/?#]+)/i);
|
|
return match && match[1] ? decodeURIComponent(match[1]) : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function diagnosticPageUrl(value) {
|
|
try {
|
|
const parsed = new URL(value);
|
|
parsed.username = "";
|
|
parsed.password = "";
|
|
parsed.search = "";
|
|
parsed.hash = "";
|
|
return parsed.toString();
|
|
} catch {
|
|
return String(value || "").slice(0, 300);
|
|
}
|
|
}
|
|
|
|
function renderSaveDestination() {
|
|
saveDestination.textContent = "";
|
|
if (downloadSettings.saveAs) {
|
|
saveDestination.textContent = "Для каждого видео Chrome спросит место сохранения.";
|
|
return;
|
|
}
|
|
|
|
const parts = ["Загрузки"];
|
|
const baseFolder = cleanFolderPath(downloadSettings.baseFolder);
|
|
if (baseFolder) parts.push(baseFolder);
|
|
const profile = profileFromUrl(activeTabUrl);
|
|
if (downloadSettings.groupByProfile && profile) parts.push(cleanFolderPath(profile));
|
|
|
|
saveDestination.appendChild(document.createTextNode("Видео сохраняются в: "));
|
|
const strong = document.createElement("strong");
|
|
strong.textContent = parts.join("/");
|
|
saveDestination.appendChild(strong);
|
|
}
|
|
|
|
function readSettingsForm() {
|
|
return {
|
|
baseFolder: cleanFolderPath(baseFolderInput.value),
|
|
groupByProfile: groupByProfileInput.checked,
|
|
saveAs: saveAsInput.checked
|
|
};
|
|
}
|
|
|
|
function renderSettingsPathPreview() {
|
|
const settings = readSettingsForm();
|
|
if (settings.saveAs) {
|
|
settingsPathPreview.textContent = "Chrome спросит место сохранения";
|
|
return;
|
|
}
|
|
const parts = ["Загрузки"];
|
|
if (settings.baseFolder) parts.push(settings.baseFolder);
|
|
if (settings.groupByProfile) parts.push(profileFromUrl(activeTabUrl) || "profile");
|
|
parts.push("video.mp4");
|
|
settingsPathPreview.textContent = parts.join("/");
|
|
}
|
|
|
|
function writeSettingsForm(settings) {
|
|
baseFolderInput.value = settings.baseFolder || "";
|
|
groupByProfileInput.checked = settings.groupByProfile !== false;
|
|
saveAsInput.checked = Boolean(settings.saveAs);
|
|
renderSettingsPathPreview();
|
|
}
|
|
|
|
function showSettingsStatus(text, kind) {
|
|
if (settingsStatusTimer) clearTimeout(settingsStatusTimer);
|
|
settingsStatusTimer = null;
|
|
settingsStatus.textContent = text || "";
|
|
settingsStatus.className = "settings-status" + (kind ? " " + kind : "");
|
|
if (text) {
|
|
settingsStatusTimer = setTimeout(() => {
|
|
settingsStatus.textContent = "";
|
|
settingsStatusTimer = null;
|
|
}, 2600);
|
|
}
|
|
}
|
|
|
|
async function saveDownloadSettings() {
|
|
const settings = readSettingsForm();
|
|
settingsSaveButton.disabled = true;
|
|
try {
|
|
await chrome.storage.local.set({ downloadSettings: settings });
|
|
downloadSettings = Object.assign({}, DEFAULT_DOWNLOAD_SETTINGS, settings);
|
|
writeSettingsForm(downloadSettings);
|
|
renderSaveDestination();
|
|
showSettingsStatus("Настройки сохранены.");
|
|
} catch (error) {
|
|
reportPopupDiagnostic("error", "settings-save-failed", {
|
|
name: error && error.name,
|
|
message: error && error.message
|
|
});
|
|
showSettingsStatus(error && error.message ? error.message : "Не удалось сохранить настройки.", "error");
|
|
} finally {
|
|
settingsSaveButton.disabled = false;
|
|
}
|
|
}
|
|
|
|
function setStatus(text, kind) {
|
|
statusBox.textContent = text;
|
|
statusBox.className = "status" + (kind ? " " + kind : "");
|
|
}
|
|
|
|
function setBusy(busy) {
|
|
resolveButton.disabled = busy;
|
|
resolveButton.textContent = busy ? "Сканирую…" : "Сканировать";
|
|
}
|
|
|
|
function stopPolling() {
|
|
if (pollingTimer) {
|
|
clearTimeout(pollingTimer);
|
|
pollingTimer = null;
|
|
}
|
|
}
|
|
|
|
function renderMedia(media) {
|
|
setPanelExpanded(collectionBox, false, { duration: 210, offset: 5 });
|
|
currentMedia = media;
|
|
if (media.profileSlug) selectedProfileSlug = media.profileSlug;
|
|
renderSaveDestination();
|
|
mediaTitle.textContent = media.title || media.id || "RedGifs video";
|
|
formatsBox.textContent = "";
|
|
|
|
if (media.thumbnail) {
|
|
thumbnail.src = media.thumbnail;
|
|
thumbnail.classList.remove("hidden");
|
|
resultBox.classList.remove("no-image");
|
|
} else {
|
|
thumbnail.removeAttribute("src");
|
|
thumbnail.classList.add("hidden");
|
|
resultBox.classList.add("no-image");
|
|
}
|
|
|
|
media.formats.forEach((format, index) => {
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = "format-button";
|
|
button.textContent = "Скачать · " + format.label;
|
|
button.addEventListener("click", () => downloadFormat(index, button));
|
|
formatsBox.appendChild(button);
|
|
});
|
|
setPanelExpanded(resultBox, true, { duration: 240, offset: 6 });
|
|
}
|
|
|
|
function renderCollection(collection) {
|
|
currentMedia = null;
|
|
setPanelExpanded(resultBox, false, { duration: 210, offset: 5 });
|
|
collectionItems.textContent = "";
|
|
collectionTitle.textContent = collection.title || "Видимые ролики";
|
|
collectionCount.textContent = collection.items.length + " шт.";
|
|
|
|
collection.items.forEach((item) => {
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = "collection-item";
|
|
|
|
if (item.thumbnail) {
|
|
const image = document.createElement("img");
|
|
image.className = "collection-thumb";
|
|
image.src = item.thumbnail;
|
|
image.alt = "";
|
|
button.appendChild(image);
|
|
} else {
|
|
button.classList.add("no-image");
|
|
}
|
|
|
|
const copy = document.createElement("span");
|
|
copy.className = "collection-copy";
|
|
const title = document.createElement("strong");
|
|
title.textContent = item.title || item.id || "RedGifs video";
|
|
const id = document.createElement("small");
|
|
id.textContent = item.id ? "ID: " + item.id : "Прямой источник";
|
|
copy.appendChild(title);
|
|
copy.appendChild(id);
|
|
button.appendChild(copy);
|
|
button.addEventListener("click", async () => {
|
|
const target = item.watchUrl || item.directVideoUrl;
|
|
if (!target) return;
|
|
selectedProfileSlug = item.profileSlug || null;
|
|
urlInput.value = target;
|
|
await resolveCurrentUrl();
|
|
});
|
|
collectionItems.appendChild(button);
|
|
});
|
|
setPanelExpanded(collectionBox, true, { duration: 240, offset: 6 });
|
|
}
|
|
|
|
async function resolveCurrentUrl() {
|
|
const value = urlInput.value.trim();
|
|
if (!value) {
|
|
setStatus("Вставьте ссылку на страницу ролика.", "error");
|
|
return;
|
|
}
|
|
stopPolling();
|
|
setPanelExpanded(progressCard, false, { duration: 190, offset: 4 });
|
|
setPanelExpanded(resultBox, false, { duration: 210, offset: 5 });
|
|
setPanelExpanded(collectionBox, false, { duration: 210, offset: 5 });
|
|
currentMedia = null;
|
|
setBusy(true);
|
|
setStatus("Получаю временный токен и адреса видео…");
|
|
try {
|
|
const response = await chrome.runtime.sendMessage({ action: "resolve", url: value, tabId: activeTabId });
|
|
if (!response || !response.ok) {
|
|
throw new Error(response && response.error ? response.error : "Нет ответа от расширения.");
|
|
}
|
|
if (response.media.type === "collection") {
|
|
renderCollection(response.media);
|
|
setStatus("Найдены видимые ролики. Выберите нужный.", "success");
|
|
} else {
|
|
renderMedia(response.media);
|
|
setStatus("Найдено форматов: " + response.media.formats.length, "success");
|
|
}
|
|
} catch (error) {
|
|
reportPopupDiagnostic("error", "resolve-failed", {
|
|
url: value,
|
|
name: error && error.name,
|
|
message: error && error.message
|
|
});
|
|
setStatus(error && error.message ? error.message : String(error), "error");
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}
|
|
|
|
async function downloadFormat(index, button) {
|
|
if (!currentMedia || !currentMedia.formats[index]) return;
|
|
const format = currentMedia.formats[index];
|
|
button.disabled = true;
|
|
setStatus(format.kind === "hls" ? "Запускаю локальную сборку HLS…" : "Запускаю локальную загрузку…");
|
|
try {
|
|
const response = await chrome.runtime.sendMessage({
|
|
action: "download",
|
|
format,
|
|
title: currentMedia.title,
|
|
id: currentMedia.id,
|
|
sourceUrl: currentMedia.sourceUrl,
|
|
profileSlug: selectedProfileSlug,
|
|
tabId: activeTabId
|
|
});
|
|
if (!response || !response.ok) {
|
|
throw new Error(response && response.error ? response.error : "Загрузка не запущена.");
|
|
}
|
|
if (response.result.jobId) {
|
|
setPanelExpanded(progressCard, true, { duration: 220, offset: 5 });
|
|
setStatus(response.result.mode === "hls" ? "HLS загружается и собирается локально." : "Видео загружается локально.", "success");
|
|
pollJob(response.result.jobId);
|
|
} else {
|
|
setStatus("Загрузка передана Chrome: " + response.result.filename, "success");
|
|
}
|
|
} catch (error) {
|
|
reportPopupDiagnostic("error", "download-start-failed", {
|
|
mediaId: currentMedia && currentMedia.id,
|
|
format: format && format.label,
|
|
name: error && error.name,
|
|
message: error && error.message
|
|
});
|
|
setStatus(error && error.message ? error.message : String(error), "error");
|
|
} finally {
|
|
button.disabled = false;
|
|
}
|
|
}
|
|
|
|
async function pollJob(jobId) {
|
|
stopPolling();
|
|
try {
|
|
const response = await chrome.runtime.sendMessage({ action: "getJob", jobId });
|
|
if (!response || !response.ok || !response.job) {
|
|
throw new Error("Состояние HLS-задачи потеряно.");
|
|
}
|
|
const job = response.job;
|
|
const progress = Math.max(0, Math.min(100, Math.round(Number(job.progress) || 0)));
|
|
progressLabel.textContent = job.status || "Обработка HLS…";
|
|
progressValue.textContent = progress + "%";
|
|
progressBar.style.width = progress + "%";
|
|
if (job.state === "complete") {
|
|
setStatus("Готово: " + (job.filename || "файл передан Chrome"), "success");
|
|
return;
|
|
}
|
|
if (job.state === "error") throw new Error(job.error || "Ошибка обработки HLS.");
|
|
pollingTimer = setTimeout(() => pollJob(jobId), 500);
|
|
} catch (error) {
|
|
reportPopupDiagnostic("error", "job-poll-failed", {
|
|
jobId,
|
|
name: error && error.name,
|
|
message: error && error.message
|
|
});
|
|
setStatus(error && error.message ? error.message : String(error), "error");
|
|
}
|
|
}
|
|
|
|
resolveButton.addEventListener("click", () => {
|
|
selectedProfileSlug = null;
|
|
resolveCurrentUrl();
|
|
});
|
|
urlInput.addEventListener("keydown", (event) => {
|
|
if (event.key === "Enter") {
|
|
selectedProfileSlug = null;
|
|
resolveCurrentUrl();
|
|
}
|
|
});
|
|
settingsButton.addEventListener("click", () => {
|
|
const opening = !panelIsExpanded(settingsPanel);
|
|
setPanelExpanded(settingsPanel, opening, { duration: 300, offset: 10, scale: 0.98 });
|
|
settingsButton.setAttribute("aria-expanded", String(opening));
|
|
settingsButton.setAttribute("aria-label", opening ? "Скрыть настройки" : "Открыть настройки");
|
|
settingsButton.title = opening ? "Скрыть настройки" : "Настройки";
|
|
if (opening) renderSettingsPathPreview();
|
|
});
|
|
window.addEventListener("focus", () => {
|
|
if (popupBlurTimer) clearTimeout(popupBlurTimer);
|
|
popupBlurTimer = null;
|
|
});
|
|
window.addEventListener("blur", () => {
|
|
if (popupBlurTimer) clearTimeout(popupBlurTimer);
|
|
popupBlurTimer = setTimeout(() => {
|
|
popupBlurTimer = null;
|
|
if (!document.hasFocus()) window.close();
|
|
}, 140);
|
|
});
|
|
requestAnimationFrame(() => {
|
|
try {
|
|
window.focus();
|
|
} catch {
|
|
// Browser-native popup focus remains the fallback.
|
|
}
|
|
});
|
|
document.addEventListener("keydown", (event) => {
|
|
if (event.key === "Escape") window.close();
|
|
});
|
|
baseFolderInput.addEventListener("input", renderSettingsPathPreview);
|
|
groupByProfileInput.addEventListener("change", renderSettingsPathPreview);
|
|
saveAsInput.addEventListener("change", renderSettingsPathPreview);
|
|
settingsSaveButton.addEventListener("click", saveDownloadSettings);
|
|
settingsResetButton.addEventListener("click", () => {
|
|
writeSettingsForm(DEFAULT_DOWNLOAD_SETTINGS);
|
|
showSettingsStatus("Значения сброшены — нажмите «Сохранить».");
|
|
});
|
|
diagnosticButton.addEventListener("click", async () => {
|
|
diagnosticButton.disabled = true;
|
|
diagnosticStatus.className = "diagnostic-status";
|
|
diagnosticStatus.textContent = "Собираю DOM, события и сетевые ошибки…";
|
|
try {
|
|
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
const tab = tabs && tabs[0];
|
|
if (!tab || !/^https:\/\/(?:[^/]+\.)?redgifs\.com\//i.test(tab.url || "")) {
|
|
throw new Error("Откройте вкладку RedGifs и повторите.");
|
|
}
|
|
let pageDiagnostics = null;
|
|
let pageError = null;
|
|
let runtimeDiagnostics = null;
|
|
let runtimeError = null;
|
|
try {
|
|
const response = await chrome.tabs.sendMessage(tab.id, { action: "captureExtensionDiagnostics" });
|
|
if (!response || !response.ok || !response.report) {
|
|
throw new Error(response && response.error ? response.error : "Снимок страницы не получен.");
|
|
}
|
|
pageDiagnostics = response.report;
|
|
} catch (error) {
|
|
pageError = error && error.message ? error.message : String(error);
|
|
}
|
|
try {
|
|
const response = await chrome.runtime.sendMessage({ action: "getDiagnosticLog" });
|
|
if (!response || !response.ok || !response.diagnostics) {
|
|
throw new Error(response && response.error ? response.error : "Журнал фонового процесса не получен.");
|
|
}
|
|
runtimeDiagnostics = response.diagnostics;
|
|
} catch (error) {
|
|
runtimeError = error && error.message ? error.message : String(error);
|
|
}
|
|
if (!pageDiagnostics && !runtimeDiagnostics) {
|
|
throw new Error("Диагностический лог не получен. Обновите вкладку RedGifs.");
|
|
}
|
|
const report = {
|
|
schemaVersion: 2,
|
|
generatedAt: new Date().toISOString(),
|
|
extensionVersion: chrome.runtime.getManifest ? chrome.runtime.getManifest().version : null,
|
|
activeTab: { id: tab.id, url: diagnosticPageUrl(tab.url) },
|
|
collectionErrors: { page: pageError, runtime: runtimeError },
|
|
pageDiagnostics,
|
|
runtimeDiagnostics
|
|
};
|
|
const saveResponse = await chrome.runtime.sendMessage({
|
|
action: "downloadDiagnosticReport",
|
|
report
|
|
});
|
|
if (!saveResponse || !saveResponse.ok || !saveResponse.result) {
|
|
throw new Error(saveResponse && saveResponse.error
|
|
? saveResponse.error
|
|
: "Фоновый процесс не смог сохранить диагностический лог.");
|
|
}
|
|
diagnosticStatus.className = "diagnostic-status success";
|
|
const runtimeEvents = runtimeDiagnostics && Array.isArray(runtimeDiagnostics.events)
|
|
? runtimeDiagnostics.events
|
|
: [];
|
|
const errorCount = runtimeEvents.filter((entry) => entry.level === "error").length;
|
|
diagnosticStatus.textContent = "Лог сохранён: событий — " + runtimeEvents.length + ", ошибок — " + errorCount + ".";
|
|
} catch (error) {
|
|
diagnosticStatus.className = "diagnostic-status error";
|
|
diagnosticStatus.textContent = error && error.message ? error.message : String(error);
|
|
} finally {
|
|
diagnosticButton.disabled = false;
|
|
}
|
|
});
|
|
alternativeToggle.addEventListener("click", async () => {
|
|
const opening = !panelIsExpanded(alternativePanel);
|
|
alternativeToggle.setAttribute("aria-expanded", String(opening));
|
|
if (!opening) {
|
|
stopPolling();
|
|
await setPanelExpanded(alternativePanel, false, { duration: 260, offset: 8, scale: 0.985 });
|
|
return;
|
|
}
|
|
await setPanelExpanded(alternativePanel, true, { duration: 300, offset: 10, scale: 0.98 });
|
|
if (!alternativeScanned && urlInput.value.trim()) {
|
|
alternativeScanned = true;
|
|
await resolveCurrentUrl();
|
|
}
|
|
});
|
|
|
|
(async () => {
|
|
writeSettingsForm(downloadSettings);
|
|
try {
|
|
const stored = await chrome.storage.local.get("downloadSettings");
|
|
downloadSettings = Object.assign({}, DEFAULT_DOWNLOAD_SETTINGS, stored.downloadSettings || {});
|
|
writeSettingsForm(downloadSettings);
|
|
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
const tab = tabs && tabs[0];
|
|
if (tab && /^https:\/\/(?:[^/]+\.)?redgifs\.com\//i.test(tab.url || "")) {
|
|
activeTabId = tab.id;
|
|
activeTabUrl = tab.url;
|
|
urlInput.value = tab.url;
|
|
}
|
|
renderSaveDestination();
|
|
renderSettingsPathPreview();
|
|
} catch {
|
|
writeSettingsForm(downloadSettings);
|
|
renderSaveDestination();
|
|
}
|
|
})();
|
|
|
|
chrome.storage.onChanged.addListener((changes, areaName) => {
|
|
if (areaName !== "local" || !changes.downloadSettings) return;
|
|
downloadSettings = Object.assign({}, DEFAULT_DOWNLOAD_SETTINGS, changes.downloadSettings.newValue || {});
|
|
writeSettingsForm(downloadSettings);
|
|
renderSaveDestination();
|
|
});
|