feat(reader): autonomous Wikipedia import and reading with media, tables and settings
This commit is contained in:
@@ -230,6 +230,26 @@ export const api = {
|
||||
detail = errData.detail || detail;
|
||||
} catch (_) {}
|
||||
throw new Error(detail);
|
||||
},
|
||||
async fetchWikipedia({ query = '', lang = 'simple', time_budget = '10-15', topic = 'random', force = false } = {}) {
|
||||
const resp = await fetch('/api/books/fetch-wikipedia', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: query,
|
||||
lang: lang,
|
||||
time_budget: time_budget,
|
||||
topic: topic,
|
||||
force: force
|
||||
})
|
||||
});
|
||||
if (resp.ok) return await resp.json();
|
||||
let detail = 'Не удалось загрузить статью из Wikipedia';
|
||||
try {
|
||||
const errData = await resp.json();
|
||||
detail = errData.detail || detail;
|
||||
} catch (_) {}
|
||||
throw new Error(detail);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
+359
-14
@@ -42,7 +42,10 @@ export function getCurrentChapterIndex() {
|
||||
function createBookCard(b) {
|
||||
const card = document.createElement('div');
|
||||
const isRfc = (b.source_type === 'rfc');
|
||||
card.className = isRfc ? 'book-card rfc-card' : 'book-card';
|
||||
const isWiki = (b.source_type === 'wikipedia');
|
||||
if (isWiki) card.className = 'book-card wiki-card';
|
||||
else if (isRfc) card.className = 'book-card rfc-card';
|
||||
else card.className = 'book-card';
|
||||
|
||||
const totalCh = Math.max(1, b.chapters_count || 1);
|
||||
const chIndex = Math.max(0, Math.min(totalCh - 1, b.current_chapter || 0));
|
||||
@@ -56,22 +59,42 @@ function createBookCard(b) {
|
||||
? `<span>1 глава • ${totalPct}%</span><span>${totalPct}%</span>`
|
||||
: `<span>Глава ${chIndex + 1} из ${totalCh} (${chPct}%)</span><span>${totalPct}%</span>`;
|
||||
|
||||
let coverIcon = '📄';
|
||||
if (b.source_type === 'epub') coverIcon = '📕';
|
||||
else if (b.source_type === 'pdf') coverIcon = '📑';
|
||||
else if (b.source_type === 'rfc') coverIcon = '🌐';
|
||||
let coverHtml = '';
|
||||
if (b.cover_url) {
|
||||
coverHtml = `<div class="book-cover-img-wrap"><img src="${escapeHtml(b.cover_url)}" class="book-cover-img" alt="Cover" loading="lazy" onerror="this.parentElement.innerHTML='<div class=\\\'book-cover-icon ${isWiki ? 'wiki-cover-icon' : (isRfc ? 'rfc-cover-icon' : '')}\\\'>${isWiki ? '🌐' : (isRfc ? '📜' : '📄')}</div>';"/></div>`;
|
||||
} else {
|
||||
let coverIcon = '📄';
|
||||
if (b.source_type === 'epub') coverIcon = '📕';
|
||||
else if (b.source_type === 'pdf') coverIcon = '📑';
|
||||
else if (isRfc) coverIcon = '📜';
|
||||
else if (isWiki) coverIcon = '🌐';
|
||||
const coverIconClass = isWiki ? 'wiki-cover-icon' : (isRfc ? 'rfc-cover-icon' : '');
|
||||
coverHtml = `<div class="book-cover-icon ${coverIconClass}">${coverIcon}</div>`;
|
||||
}
|
||||
|
||||
const badgeClass = isRfc ? 'badge-format badge-rfc' : 'badge-format';
|
||||
let badgeClass = 'badge-format';
|
||||
if (isWiki) badgeClass = 'badge-format badge-wiki';
|
||||
else if (isRfc) badgeClass = 'badge-format badge-rfc';
|
||||
|
||||
const readMinutes = Math.max(1, Math.round((b.total_words || 800) / 180));
|
||||
const timeBadge = (b.total_words && b.total_words > 0)
|
||||
? `<span class="badge-time" title="Примерное время чтения: ~${readMinutes} минут">⏱️ ~${readMinutes} мин</span>`
|
||||
: '';
|
||||
const progressFillClass = isWiki ? 'wiki-progress-fill' : (isRfc ? 'rfc-progress-fill' : '');
|
||||
|
||||
const authorDisplay = b.author || (isWiki ? 'Wikipedia' : (isRfc ? 'IETF' : 'Неизвестный автор'));
|
||||
const sourceLabel = isWiki ? 'WIKIPEDIA' : (isRfc ? 'RFC' : (b.source_type || 'text').toUpperCase());
|
||||
|
||||
card.innerHTML = `
|
||||
<div>
|
||||
<div class="book-card-top">
|
||||
<div class="book-cover-icon ${isRfc ? 'rfc-cover-icon' : ''}">${coverIcon}</div>
|
||||
${coverHtml}
|
||||
<div class="book-card-info">
|
||||
<h4 class="book-card-title" title="${escapeHtml(b.title)}">${escapeHtml(b.title)}</h4>
|
||||
<p class="book-card-author">${escapeHtml(b.author || (isRfc ? 'IETF' : 'Неизвестный автор'))}</p>
|
||||
<p class="book-card-author">${escapeHtml(authorDisplay)}</p>
|
||||
<div class="book-card-badges">
|
||||
<span class="${badgeClass}">${escapeHtml((b.source_type || 'text').toUpperCase())}</span>
|
||||
<span class="${badgeClass}">${escapeHtml(sourceLabel)}</span>
|
||||
${timeBadge}
|
||||
<span class="badge-words">${b.total_words ? b.total_words.toLocaleString() + ' слов' : ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -81,14 +104,14 @@ function createBookCard(b) {
|
||||
${progressLabels}
|
||||
</div>
|
||||
<div class="book-progress-bar-bg">
|
||||
<div class="book-progress-bar-fill ${isRfc ? 'rfc-progress-fill' : ''}" style="width: ${totalPct}%"></div>
|
||||
<div class="book-progress-bar-fill ${progressFillClass}" style="width: ${totalPct}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="book-card-actions">
|
||||
<button class="btn btn-primary btn-sm read-book-btn" data-id="${b.id}">📖 Читать</button>
|
||||
<button class="btn btn-outline btn-xs train-book-btn" data-title="${escapeHtml(b.title)}" title="Тренировать слова из этой книги">🎓 Слова книги</button>
|
||||
<button class="icon-btn delete-book-btn" data-id="${b.id}" title="Удалить книгу">🗑️</button>
|
||||
<button class="icon-btn delete-book-btn" data-id="${b.id}" title="Удалить из библиотеки">🗑️</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -100,7 +123,7 @@ function createBookCard(b) {
|
||||
window.location.hash = `#srs/${encodeURIComponent(bookTag)}`;
|
||||
});
|
||||
card.querySelector('.delete-book-btn').addEventListener('click', async () => {
|
||||
const typeName = isRfc ? 'спецификацию' : 'книгу';
|
||||
const typeName = isWiki ? 'статью Википедии' : (isRfc ? 'спецификацию' : 'книгу');
|
||||
if (confirm(`Удалить ${typeName} "${b.title}" из библиотеки?`)) {
|
||||
await api.books.delete(b.id);
|
||||
loadBookshelf();
|
||||
@@ -117,6 +140,8 @@ export async function loadBookshelf() {
|
||||
const bookshelfEmpty = document.getElementById('bookshelf-empty');
|
||||
const rfcGrid = document.getElementById('rfc-grid');
|
||||
const rfcEmptyState = document.getElementById('rfc-empty-state');
|
||||
const wikiGrid = document.getElementById('wiki-grid');
|
||||
const wikiEmptyState = document.getElementById('wiki-empty-state');
|
||||
|
||||
if (!bookshelfContainer) return;
|
||||
if (!window.location.hash.includes('/book/')) {
|
||||
@@ -125,8 +150,10 @@ export async function loadBookshelf() {
|
||||
}
|
||||
if (bookshelfGrid) bookshelfGrid.innerHTML = '';
|
||||
if (rfcGrid) rfcGrid.innerHTML = '';
|
||||
if (wikiGrid) wikiGrid.innerHTML = '';
|
||||
if (bookshelfEmpty) bookshelfEmpty.classList.add('hidden');
|
||||
if (rfcEmptyState) rfcEmptyState.classList.add('hidden');
|
||||
if (wikiEmptyState) wikiEmptyState.classList.add('hidden');
|
||||
|
||||
try {
|
||||
const books = await api.books.getAll();
|
||||
@@ -141,12 +168,16 @@ export function renderBookshelf(books) {
|
||||
const bookshelfEmpty = document.getElementById('bookshelf-empty');
|
||||
const rfcGrid = document.getElementById('rfc-grid');
|
||||
const rfcEmptyState = document.getElementById('rfc-empty-state');
|
||||
const wikiGrid = document.getElementById('wiki-grid');
|
||||
const wikiEmptyState = document.getElementById('wiki-empty-state');
|
||||
|
||||
if (bookshelfGrid) bookshelfGrid.innerHTML = '';
|
||||
if (rfcGrid) rfcGrid.innerHTML = '';
|
||||
if (wikiGrid) wikiGrid.innerHTML = '';
|
||||
|
||||
const generalBooks = (books || []).filter(b => b.source_type !== 'rfc');
|
||||
const generalBooks = (books || []).filter(b => b.source_type !== 'rfc' && b.source_type !== 'wikipedia');
|
||||
const rfcBooks = (books || []).filter(b => b.source_type === 'rfc');
|
||||
const wikiBooks = (books || []).filter(b => b.source_type === 'wikipedia');
|
||||
|
||||
if (bookshelfEmpty) {
|
||||
bookshelfEmpty.classList.toggle('hidden', generalBooks.length > 0);
|
||||
@@ -157,6 +188,15 @@ export function renderBookshelf(books) {
|
||||
});
|
||||
}
|
||||
|
||||
if (wikiEmptyState) {
|
||||
wikiEmptyState.classList.toggle('hidden', wikiBooks.length > 0);
|
||||
}
|
||||
if (wikiGrid) {
|
||||
wikiBooks.forEach(b => {
|
||||
wikiGrid.appendChild(createBookCard(b));
|
||||
});
|
||||
}
|
||||
|
||||
if (rfcEmptyState) {
|
||||
rfcEmptyState.classList.toggle('hidden', rfcBooks.length > 0);
|
||||
}
|
||||
@@ -375,6 +415,24 @@ export function buildChapterDOM(chapterIdx, knownWordsMap, isContinuous = false)
|
||||
|
||||
blockEl.appendChild(header);
|
||||
blockEl.appendChild(pre);
|
||||
} else if (block.type === 'figure') {
|
||||
blockEl = document.createElement('figure');
|
||||
blockEl.className = 'reader-figure';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.className = 'reader-figure-img';
|
||||
img.src = block.src;
|
||||
img.alt = block.alt || '';
|
||||
img.loading = 'lazy';
|
||||
img.onerror = () => { blockEl.style.display = 'none'; };
|
||||
blockEl.appendChild(img);
|
||||
|
||||
if (block.caption) {
|
||||
const figcaption = document.createElement('figcaption');
|
||||
figcaption.className = 'reader-figcaption';
|
||||
renderInteractiveSpans(figcaption, block.caption, block.caption, knownWordsMap);
|
||||
blockEl.appendChild(figcaption);
|
||||
}
|
||||
} else if (block.type === 'table') {
|
||||
blockEl = document.createElement('div');
|
||||
blockEl.className = 'reader-table-wrap';
|
||||
@@ -804,6 +862,86 @@ export function parseContentBlocks(content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2a. HTML Figure Block: <figure ...> ... </figure>
|
||||
if (/^<figure\b/i.test(trimmed)) {
|
||||
const figLines = [];
|
||||
while (i < lines.length) {
|
||||
figLines.push(lines[i]);
|
||||
if (/<\/figure>/i.test(lines[i])) {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
const figHtml = figLines.join('\n');
|
||||
const imgMatch = figHtml.match(/<img\b([^>]*)>/i);
|
||||
let src = '';
|
||||
let alt = '';
|
||||
if (imgMatch) {
|
||||
const srcM = imgMatch[1].match(/src=["']([^"']+)["']/i);
|
||||
const altM = imgMatch[1].match(/alt=["']([^"']*)["']/i);
|
||||
if (srcM) src = srcM[1];
|
||||
if (altM) alt = altM[1];
|
||||
}
|
||||
let caption = '';
|
||||
const capMatch = figHtml.match(/<figcaption\b[^>]*>([\s\S]*?)<\/figcaption>/i);
|
||||
if (capMatch) {
|
||||
caption = capMatch[1].replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
if (src) {
|
||||
blocks.push({
|
||||
type: 'figure',
|
||||
src: src,
|
||||
alt: alt,
|
||||
caption: caption
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 2b. HTML Data Table: <div class="reader-table-wrap"><table ...> or <table ...>
|
||||
if (/^(?:<div[^>]*class=["'][^"']*reader-table-wrap[^"']*["'][^>]*>\s*)?<table\b/i.test(trimmed)) {
|
||||
const tableLines = [];
|
||||
while (i < lines.length) {
|
||||
tableLines.push(lines[i]);
|
||||
if (/<\/table>(?:\s*<\/div>)?/i.test(lines[i])) {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
const fullTableHtml = tableLines.join('\n');
|
||||
const tpl = document.createElement('template');
|
||||
tpl.innerHTML = fullTableHtml;
|
||||
const tableEl = tpl.content.querySelector('table');
|
||||
if (tableEl) {
|
||||
const headers = [];
|
||||
const ths = tableEl.querySelectorAll('thead th, tr:first-child th');
|
||||
ths.forEach(th => headers.push(th.textContent.trim()));
|
||||
|
||||
const rows = [];
|
||||
const trs = tableEl.querySelectorAll('tbody tr, tr');
|
||||
trs.forEach((tr) => {
|
||||
const tds = tr.querySelectorAll('td');
|
||||
if (tds.length > 0) {
|
||||
const rowCells = [];
|
||||
tds.forEach(td => rowCells.push(td.textContent.trim()));
|
||||
rows.push(rowCells);
|
||||
}
|
||||
});
|
||||
|
||||
if (headers.length > 0 || rows.length > 0) {
|
||||
blocks.push({
|
||||
type: 'table',
|
||||
headers: headers,
|
||||
alignments: headers.map(() => 'left'),
|
||||
rows: rows
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Markdown Table: lines starting with | and ending with |
|
||||
if (trimmed.startsWith('|') && trimmed.endsWith('|')) {
|
||||
const tableLines = [];
|
||||
@@ -950,6 +1088,9 @@ export function parseContentBlocks(content) {
|
||||
/^[-*+]\s+/.test(curTrim) ||
|
||||
/^\d+\.\s+/.test(curTrim) ||
|
||||
/^(\-{3,}|\*{3,}|\_{3,})$/.test(curTrim) ||
|
||||
/^<figure\b/i.test(curTrim) ||
|
||||
/^<table\b/i.test(curTrim) ||
|
||||
curTrim.includes('reader-table-wrap') ||
|
||||
/^<(?:p|div)[^>]*align=["']?center/i.test(curTrim)) {
|
||||
break;
|
||||
}
|
||||
@@ -1899,7 +2040,7 @@ export function initReader() {
|
||||
if (wordSpan) {
|
||||
e.stopPropagation();
|
||||
const word = wordSpan.dataset.word;
|
||||
const context = wordSpan.dataset.context || wordSpan.closest('p, div, li, td, th, blockquote')?.textContent || word;
|
||||
const context = wordSpan.dataset.context || wordSpan.closest('p, div, li, td, th, blockquote, figcaption')?.textContent || word;
|
||||
handleWordClick(word, context, wordSpan);
|
||||
}
|
||||
});
|
||||
@@ -2448,4 +2589,208 @@ export function initReader() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Wikipedia Autonomous Import & Settings
|
||||
// -----------------------------------------------------------------------
|
||||
const WIKI_SETTINGS_KEY = 'notes_wiki_settings';
|
||||
const wikiReadBtn = document.getElementById('wiki-read-btn');
|
||||
const wikiSettingsBtn = document.getElementById('wiki-settings-btn');
|
||||
const wikiSettingsModal = document.getElementById('wiki-settings-modal');
|
||||
const wikiSettingsCloseBtn = document.getElementById('wiki-settings-close-btn');
|
||||
const wikiSettingsSaveBtn = document.getElementById('wiki-settings-save-btn');
|
||||
const wikiFetchStatus = document.getElementById('wiki-fetch-status');
|
||||
const wikiCustomForm = document.getElementById('wiki-custom-form');
|
||||
const wikiCustomInput = document.getElementById('wiki-custom-input');
|
||||
const wikiCustomFetchBtn = document.getElementById('wiki-custom-fetch-btn');
|
||||
|
||||
const wikiActiveLangBadge = document.getElementById('wiki-active-lang-badge');
|
||||
const wikiActiveTimeBadge = document.getElementById('wiki-active-time-badge');
|
||||
const wikiActiveTopicBadge = document.getElementById('wiki-active-topic-badge');
|
||||
|
||||
const TOPIC_LABELS = {
|
||||
'random': '🎲 Любая тема',
|
||||
'science': '🚀 Наука и космос',
|
||||
'tech': '💻 Технологии и IT',
|
||||
'history': '🏛️ История',
|
||||
'nature': '🌿 Природа',
|
||||
'psychology': '🧠 Психология',
|
||||
'featured': '⭐ Статья дня'
|
||||
};
|
||||
|
||||
const TIME_LABELS = {
|
||||
'5': '⏱️ ~5 мин',
|
||||
'10-15': '⏱️ ~10-15 мин',
|
||||
'20+': '⏱️ 20+ мин',
|
||||
'any': '⏱️ Любой объем'
|
||||
};
|
||||
|
||||
function getStoredWikiSettings() {
|
||||
try {
|
||||
const raw = localStorage.getItem(WIKI_SETTINGS_KEY);
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
return {
|
||||
lang: parsed.lang || 'simple',
|
||||
time_budget: parsed.time_budget || '10-15',
|
||||
topic: parsed.topic || 'random'
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
return { lang: 'simple', time_budget: '10-15', topic: 'random' };
|
||||
}
|
||||
|
||||
function saveStoredWikiSettings(settings) {
|
||||
try {
|
||||
localStorage.setItem(WIKI_SETTINGS_KEY, JSON.stringify(settings));
|
||||
} catch (_) {}
|
||||
updateWikiUIBadges(settings);
|
||||
}
|
||||
|
||||
function updateWikiUIBadges(settings) {
|
||||
if (wikiActiveLangBadge) {
|
||||
wikiActiveLangBadge.textContent = settings.lang === 'en' ? 'Standard English (B2–C1)' : 'Simple English (A2–B1)';
|
||||
}
|
||||
if (wikiActiveTimeBadge) {
|
||||
wikiActiveTimeBadge.textContent = TIME_LABELS[settings.time_budget] || '⏱️ ~10-15 мин';
|
||||
}
|
||||
if (wikiActiveTopicBadge) {
|
||||
wikiActiveTopicBadge.textContent = TOPIC_LABELS[settings.topic] || '🎲 Любая тема';
|
||||
}
|
||||
}
|
||||
|
||||
function syncWikiModalInputs(settings) {
|
||||
const langRadios = document.querySelectorAll('input[name="wiki-lang"]');
|
||||
langRadios.forEach(r => {
|
||||
r.checked = (r.value === settings.lang);
|
||||
});
|
||||
const timeRadios = document.querySelectorAll('input[name="wiki-time"]');
|
||||
timeRadios.forEach(r => {
|
||||
r.checked = (r.value === settings.time_budget);
|
||||
});
|
||||
const topicSelect = document.getElementById('wiki-topic-select');
|
||||
if (topicSelect) {
|
||||
topicSelect.value = settings.topic || 'random';
|
||||
}
|
||||
}
|
||||
|
||||
function readWikiModalInputs() {
|
||||
const langRadio = document.querySelector('input[name="wiki-lang"]:checked');
|
||||
const timeRadio = document.querySelector('input[name="wiki-time"]:checked');
|
||||
const topicSelect = document.getElementById('wiki-topic-select');
|
||||
|
||||
return {
|
||||
lang: langRadio ? langRadio.value : 'simple',
|
||||
time_budget: timeRadio ? timeRadio.value : '10-15',
|
||||
topic: topicSelect ? topicSelect.value : 'random'
|
||||
};
|
||||
}
|
||||
|
||||
const currentWikiSettings = getStoredWikiSettings();
|
||||
updateWikiUIBadges(currentWikiSettings);
|
||||
|
||||
if (wikiSettingsBtn && wikiSettingsModal) {
|
||||
wikiSettingsBtn.addEventListener('click', () => {
|
||||
syncWikiModalInputs(getStoredWikiSettings());
|
||||
wikiSettingsModal.classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
if (wikiSettingsCloseBtn && wikiSettingsModal) {
|
||||
wikiSettingsCloseBtn.addEventListener('click', () => {
|
||||
wikiSettingsModal.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
if (wikiSettingsModal) {
|
||||
wikiSettingsModal.addEventListener('click', (e) => {
|
||||
if (e.target === wikiSettingsModal) {
|
||||
wikiSettingsModal.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (wikiSettingsSaveBtn && wikiSettingsModal) {
|
||||
wikiSettingsSaveBtn.addEventListener('click', () => {
|
||||
const newSettings = readWikiModalInputs();
|
||||
saveStoredWikiSettings(newSettings);
|
||||
wikiSettingsModal.classList.add('hidden');
|
||||
showToast('Настройки статей Википедии сохранены!');
|
||||
});
|
||||
}
|
||||
|
||||
async function handleWikiFetch(params) {
|
||||
const isCustom = Boolean(params.query);
|
||||
const activeBtn = isCustom ? wikiCustomFetchBtn : wikiReadBtn;
|
||||
|
||||
if (activeBtn) activeBtn.disabled = true;
|
||||
if (wikiReadBtn) wikiReadBtn.disabled = true;
|
||||
if (wikiCustomInput) wikiCustomInput.disabled = true;
|
||||
|
||||
if (wikiFetchStatus) {
|
||||
wikiFetchStatus.className = 'wiki-fetch-status wiki-status-loading';
|
||||
wikiFetchStatus.innerHTML = `<span>⏳ ${isCustom ? 'Загрузка статьи по запросу...' : 'Подбираем и загружаем статью из Википедии...'}</span>`;
|
||||
wikiFetchStatus.classList.remove('hidden');
|
||||
}
|
||||
|
||||
try {
|
||||
const book = await api.books.fetchWikipedia(params);
|
||||
if (wikiFetchStatus) {
|
||||
wikiFetchStatus.className = 'wiki-fetch-status wiki-status-success';
|
||||
const estMin = Math.max(1, Math.round((book.total_words || 800) / 180));
|
||||
wikiFetchStatus.innerHTML = `<span>✅ Статья «${escapeHtml(book.title)}» сохранена (~${estMin} мин, ${book.total_words ? book.total_words.toLocaleString() : ''} слов)!</span>`;
|
||||
}
|
||||
showToast(book.already_existed ? `Открываем ${book.title}` : `Статья Википедии сохранена офлайн!`);
|
||||
if (wikiCustomInput) wikiCustomInput.value = '';
|
||||
await loadBookshelf();
|
||||
setTimeout(() => {
|
||||
if (wikiFetchStatus) wikiFetchStatus.classList.add('hidden');
|
||||
if (wikiSettingsModal) wikiSettingsModal.classList.add('hidden');
|
||||
window.location.hash = `#reader/book/${book.id}`;
|
||||
}, 500);
|
||||
} catch (err) {
|
||||
console.error('Wikipedia fetch error:', err);
|
||||
if (wikiFetchStatus) {
|
||||
wikiFetchStatus.className = 'wiki-fetch-status wiki-status-error';
|
||||
wikiFetchStatus.innerHTML = `<span>❌ Ошибка: ${escapeHtml(err.message || 'Не удалось загрузить статью')}</span>`;
|
||||
wikiFetchStatus.classList.remove('hidden');
|
||||
}
|
||||
showToast(err.message || 'Ошибка загрузки статьи Wikipedia', 4000);
|
||||
} finally {
|
||||
if (activeBtn) activeBtn.disabled = false;
|
||||
if (wikiReadBtn) wikiReadBtn.disabled = false;
|
||||
if (wikiCustomInput) wikiCustomInput.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (wikiReadBtn) {
|
||||
wikiReadBtn.addEventListener('click', () => {
|
||||
const currentSettings = getStoredWikiSettings();
|
||||
handleWikiFetch({
|
||||
query: '',
|
||||
lang: currentSettings.lang,
|
||||
time_budget: currentSettings.time_budget,
|
||||
topic: currentSettings.topic,
|
||||
force: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (wikiCustomForm) {
|
||||
wikiCustomForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const val = wikiCustomInput ? wikiCustomInput.value.trim() : '';
|
||||
if (!val) return;
|
||||
const currentSettings = getStoredWikiSettings();
|
||||
handleWikiFetch({
|
||||
query: val,
|
||||
lang: currentSettings.lang,
|
||||
time_budget: currentSettings.time_budget,
|
||||
topic: currentSettings.topic,
|
||||
force: false
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user