refactor: remove API key and status from options and popup
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
fe65aee032
commit
4bb0cf0cb2
@@ -11,11 +11,7 @@
|
||||
<h1>Outlier Saver</h1>
|
||||
<form id="form">
|
||||
<label>
|
||||
API-ключ YouTube Data API v3
|
||||
<input id="apiKey" type="password" autocomplete="off" spellcheck="false">
|
||||
</label>
|
||||
<label>
|
||||
Порог score (просмотры ÷ подписчики)
|
||||
Порог outlier score VidIQ (сохранять видео от этого значения)
|
||||
<input id="threshold" type="number" min="0.1" step="0.1" required>
|
||||
</label>
|
||||
<label>
|
||||
@@ -27,16 +23,6 @@
|
||||
<span id="saved" role="status"></span>
|
||||
</div>
|
||||
</form>
|
||||
<details>
|
||||
<summary>Как получить API-ключ</summary>
|
||||
<ol>
|
||||
<li>Откройте <a href="https://console.cloud.google.com/" target="_blank" rel="noreferrer">Google Cloud Console</a> и создайте проект.</li>
|
||||
<li>В разделе «API и сервисы → Библиотека» включите <b>YouTube Data API v3</b>.</li>
|
||||
<li>В «Учётные данные» создайте <b>API-ключ</b>.</li>
|
||||
<li>В ограничениях ключа выберите «Ограничить API → YouTube Data API v3». Ограничение по HTTP-источникам (referrer) <b>не включайте</b>: запросы идут из расширения.</li>
|
||||
<li>Вставьте ключ в поле выше и нажмите «Сохранить».</li>
|
||||
</ol>
|
||||
</details>
|
||||
</main>
|
||||
<script type="module" src="options.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { getSettings, saveSettings } from '../lib/storage.js';
|
||||
|
||||
const form = document.getElementById('form');
|
||||
const apiKey = document.getElementById('apiKey');
|
||||
const threshold = document.getElementById('threshold');
|
||||
const minViews = document.getElementById('minViews');
|
||||
const savedLabel = document.getElementById('saved');
|
||||
|
||||
function fill(settings) {
|
||||
apiKey.value = settings.apiKey;
|
||||
threshold.value = settings.threshold;
|
||||
minViews.value = settings.minViews;
|
||||
}
|
||||
@@ -16,7 +14,7 @@ fill(await getSettings());
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
fill(await saveSettings({ apiKey: apiKey.value, threshold: threshold.value, minViews: minViews.value }));
|
||||
fill(await saveSettings({ threshold: threshold.value, minViews: minViews.value }));
|
||||
savedLabel.textContent = 'Сохранено';
|
||||
setTimeout(() => {
|
||||
savedLabel.textContent = '';
|
||||
|
||||
+1
-3
@@ -10,8 +10,6 @@ header { justify-content: space-between; border-bottom: 1px solid var(--line); }
|
||||
footer { border-top: 1px solid var(--line); }
|
||||
footer button:last-child { margin-left: auto; }
|
||||
button { padding: 5px 10px; font: inherit; }
|
||||
#status { margin: 0; padding: 8px 12px; color: var(--muted); }
|
||||
#status.warn { color: #d93025; }
|
||||
#list { list-style: none; margin: 0; padding: 0; max-height: 380px; overflow-y: auto; }
|
||||
#list li { display: grid; grid-template-columns: 96px 1fr; gap: 10px; padding: 8px 12px; border-bottom: 1px solid var(--line); }
|
||||
#list img { width: 96px; height: 54px; object-fit: cover; border-radius: 4px; background: var(--line); }
|
||||
@@ -19,4 +17,4 @@ button { padding: 5px 10px; font: inherit; }
|
||||
#list a:hover { text-decoration: underline; }
|
||||
.meta { margin-top: 2px; color: var(--muted); }
|
||||
.badge { display: inline-block; margin-right: 6px; padding: 0 6px; border-radius: 8px; background: var(--accent); color: #fff; font-size: 11px; }
|
||||
.empty { padding: 24px 12px; text-align: center; color: var(--muted); }
|
||||
.empty { display: block; padding: 24px 12px; text-align: center; color: var(--muted); }
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
<strong>Outlier Saver</strong>
|
||||
<span id="count"></span>
|
||||
</header>
|
||||
<p id="status" role="status"></p>
|
||||
<ul id="list"></ul>
|
||||
<footer>
|
||||
<button id="export-json" type="button">Скачать JSON</button>
|
||||
|
||||
+4
-27
@@ -1,34 +1,15 @@
|
||||
import { countOutliers, listOutliers } from '../lib/db.js';
|
||||
import { toCsv, toJson } from '../lib/export.js';
|
||||
import { getStatus } from '../lib/storage.js';
|
||||
|
||||
const LIST_LIMIT = 50;
|
||||
const compact = new Intl.NumberFormat('ru-RU', { notation: 'compact', maximumFractionDigits: 1 });
|
||||
const format = (n) => (n === null || n === undefined ? '—' : compact.format(n));
|
||||
|
||||
const countEl = document.getElementById('count');
|
||||
const statusEl = document.getElementById('status');
|
||||
const listEl = document.getElementById('list');
|
||||
const jsonButton = document.getElementById('export-json');
|
||||
const csvButton = document.getElementById('export-csv');
|
||||
|
||||
function statusText(status) {
|
||||
switch (status.state) {
|
||||
case 'no-key':
|
||||
return { text: 'Не задан API-ключ — откройте настройки.', warn: true };
|
||||
case 'invalid-key':
|
||||
return { text: `API-ключ отклонён: ${status.message ?? 'неизвестная ошибка'}`, warn: true };
|
||||
case 'quota':
|
||||
if (Date.now() >= status.until) return { text: 'Сбор работает.', warn: false };
|
||||
return {
|
||||
text: `Квота YouTube API исчерпана, сбор возобновится после ${new Date(status.until).toLocaleString('ru-RU')}.`,
|
||||
warn: true,
|
||||
};
|
||||
default:
|
||||
return { text: 'Сбор работает.', warn: false };
|
||||
}
|
||||
}
|
||||
|
||||
function badge(text) {
|
||||
const span = document.createElement('span');
|
||||
span.className = 'badge';
|
||||
@@ -51,12 +32,11 @@ function renderRow(outlier) {
|
||||
title.rel = 'noreferrer';
|
||||
|
||||
const badges = document.createElement('div');
|
||||
if (outlier.score !== null) badges.append(badge(`×${outlier.score}`));
|
||||
if (outlier.vidiqScore !== null) badges.append(badge(`VidIQ ${outlier.vidiqScore}`));
|
||||
badges.append(badge(`${outlier.outlierScore}x`));
|
||||
|
||||
const meta = document.createElement('div');
|
||||
meta.className = 'meta';
|
||||
meta.textContent = `${outlier.channelTitle} · ${format(outlier.subscribers)} подп. · ${format(outlier.views)} просм.`;
|
||||
meta.textContent = `${outlier.channelTitle ?? '—'} · ${format(outlier.subscribers)} подп. · ${format(outlier.views)} просм.`;
|
||||
|
||||
const body = document.createElement('div');
|
||||
body.append(title, badges, meta);
|
||||
@@ -84,17 +64,14 @@ async function exportAs(kind) {
|
||||
}
|
||||
}
|
||||
|
||||
const [count, recent, status] = await Promise.all([countOutliers(), listOutliers(LIST_LIMIT), getStatus()]);
|
||||
const [count, recent] = await Promise.all([countOutliers(), listOutliers(LIST_LIMIT)]);
|
||||
|
||||
countEl.textContent = `Сохранено: ${count}`;
|
||||
const { text, warn } = statusText(status);
|
||||
statusEl.textContent = text;
|
||||
statusEl.classList.toggle('warn', warn);
|
||||
|
||||
if (recent.length === 0) {
|
||||
const empty = document.createElement('li');
|
||||
empty.className = 'empty';
|
||||
empty.textContent = 'Пока ничего не найдено. Откройте YouTube и пролистайте ленту.';
|
||||
empty.textContent = 'Пока ничего не найдено. Откройте YouTube с включённым VidIQ и пролистайте ленту.';
|
||||
listEl.append(empty);
|
||||
jsonButton.disabled = true;
|
||||
csvButton.disabled = true;
|
||||
|
||||
Reference in New Issue
Block a user