fix: 修复漫画阅读器卡死并支持章节直读
This commit is contained in:
@@ -135,6 +135,10 @@ function appEntries() {
|
|||||||
const entries = [
|
const entries = [
|
||||||
collectFile(path.join(ROOT, 'main.js'), `${app}/main.js`),
|
collectFile(path.join(ROOT, 'main.js'), `${app}/main.js`),
|
||||||
collectFile(path.join(ROOT, 'preload.js'), `${app}/preload.js`),
|
collectFile(path.join(ROOT, 'preload.js'), `${app}/preload.js`),
|
||||||
|
collectFile(
|
||||||
|
path.join(ROOT, 'manga-online-preload.js'),
|
||||||
|
`${app}/manga-online-preload.js`
|
||||||
|
),
|
||||||
...collectDir(path.join(ROOT, 'src'), `${app}/src`, (e) => e.name.startsWith('_test'))
|
...collectDir(path.join(ROOT, 'src'), `${app}/src`, (e) => e.name.startsWith('_test'))
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -273,6 +273,10 @@ async function build() {
|
|||||||
fs.mkdirSync(APP, { recursive: true });
|
fs.mkdirSync(APP, { recursive: true });
|
||||||
fs.copyFileSync(path.join(ROOT, 'main.js'), path.join(APP, 'main.js'));
|
fs.copyFileSync(path.join(ROOT, 'main.js'), path.join(APP, 'main.js'));
|
||||||
fs.copyFileSync(path.join(ROOT, 'preload.js'), path.join(APP, 'preload.js'));
|
fs.copyFileSync(path.join(ROOT, 'preload.js'), path.join(APP, 'preload.js'));
|
||||||
|
fs.copyFileSync(
|
||||||
|
path.join(ROOT, 'manga-online-preload.js'),
|
||||||
|
path.join(APP, 'manga-online-preload.js')
|
||||||
|
);
|
||||||
copyDir(path.join(ROOT, 'src'), path.join(APP, 'src'), (e) => e.name.startsWith('_test'));
|
copyDir(path.join(ROOT, 'src'), path.join(APP, 'src'), (e) => e.name.startsWith('_test'));
|
||||||
const iconDir = path.join(APP, 'icons', 'dist');
|
const iconDir = path.join(APP, 'icons', 'dist');
|
||||||
fs.mkdirSync(iconDir, { recursive: true });
|
fs.mkdirSync(iconDir, { recursive: true });
|
||||||
|
|||||||
@@ -165,6 +165,10 @@ async function build() {
|
|||||||
fs.mkdirSync(APP, { recursive: true });
|
fs.mkdirSync(APP, { recursive: true });
|
||||||
fs.copyFileSync(path.join(ROOT, 'main.js'), path.join(APP, 'main.js'));
|
fs.copyFileSync(path.join(ROOT, 'main.js'), path.join(APP, 'main.js'));
|
||||||
fs.copyFileSync(path.join(ROOT, 'preload.js'), path.join(APP, 'preload.js'));
|
fs.copyFileSync(path.join(ROOT, 'preload.js'), path.join(APP, 'preload.js'));
|
||||||
|
fs.copyFileSync(
|
||||||
|
path.join(ROOT, 'manga-online-preload.js'),
|
||||||
|
path.join(APP, 'manga-online-preload.js')
|
||||||
|
);
|
||||||
copyDir(path.join(ROOT, 'src'), path.join(APP, 'src'), (e) => e.name.startsWith('_test'));
|
copyDir(path.join(ROOT, 'src'), path.join(APP, 'src'), (e) => e.name.startsWith('_test'));
|
||||||
const iconDir = path.join(APP, 'icons', 'dist');
|
const iconDir = path.join(APP, 'icons', 'dist');
|
||||||
fs.mkdirSync(iconDir, { recursive: true });
|
fs.mkdirSync(iconDir, { recursive: true });
|
||||||
|
|||||||
+13
-2
@@ -114,7 +114,12 @@ async function verifyWindowsZip(file) {
|
|||||||
const JSZip = require('jszip');
|
const JSZip = require('jszip');
|
||||||
const zip = await JSZip.loadAsync(fs.readFileSync(file));
|
const zip = await JSZip.loadAsync(fs.readFileSync(file));
|
||||||
const names = Object.keys(zip.files).map((n) => n.replace(/^[^/]+\//, ''));
|
const names = Object.keys(zip.files).map((n) => n.replace(/^[^/]+\//, ''));
|
||||||
requireEntries(names, [`${PRODUCT}.exe`, 'resources/app/main.js', 'locales/zh-CN.pak']);
|
requireEntries(names, [
|
||||||
|
`${PRODUCT}.exe`,
|
||||||
|
'resources/app/main.js',
|
||||||
|
'resources/app/manga-online-preload.js',
|
||||||
|
'locales/zh-CN.pak'
|
||||||
|
]);
|
||||||
forbidEntries(names, [/^data\//, /(^|\/)_test\//]);
|
forbidEntries(names, [/^data\//, /(^|\/)_test\//]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +136,13 @@ async function verifyDmg(file) {
|
|||||||
async function verifyLinuxTarball(file) {
|
async function verifyLinuxTarball(file) {
|
||||||
const entries = readTarEntries(zlib.gunzipSync(fs.readFileSync(file)));
|
const entries = readTarEntries(zlib.gunzipSync(fs.readFileSync(file)));
|
||||||
const names = entries.map((e) => e.name.replace(/^[^/]+\//, ''));
|
const names = entries.map((e) => e.name.replace(/^[^/]+\//, ''));
|
||||||
requireEntries(names, [PRODUCT, `${PRODUCT}.sh`, 'resources/app/main.js', 'locales/zh-CN.pak']);
|
requireEntries(names, [
|
||||||
|
PRODUCT,
|
||||||
|
`${PRODUCT}.sh`,
|
||||||
|
'resources/app/main.js',
|
||||||
|
'resources/app/manga-online-preload.js',
|
||||||
|
'locales/zh-CN.pak'
|
||||||
|
]);
|
||||||
forbidEntries(names, [/^data\//, /(^|\/)_test\//]);
|
forbidEntries(names, [/^data\//, /(^|\/)_test\//]);
|
||||||
|
|
||||||
for (const required of [PRODUCT, `${PRODUCT}.sh`, 'chrome-sandbox']) {
|
for (const required of [PRODUCT, `${PRODUCT}.sh`, 'chrome-sandbox']) {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"main.js",
|
"main.js",
|
||||||
"preload.js",
|
"preload.js",
|
||||||
|
"manga-online-preload.js",
|
||||||
"src/**/*",
|
"src/**/*",
|
||||||
"icons/dist/*.ico",
|
"icons/dist/*.ico",
|
||||||
"icons/dist/dark/icon-32.png",
|
"icons/dist/dark/icon-32.png",
|
||||||
|
|||||||
@@ -51,14 +51,14 @@ app.whenReady().then(async () => {
|
|||||||
const mangaDex = require(path.join(ROOT, 'src', 'sources', 'mangadex'));
|
const mangaDex = require(path.join(ROOT, 'src', 'sources', 'mangadex'));
|
||||||
mangaDex.chapters = async () => ({
|
mangaDex.chapters = async () => ({
|
||||||
items: [
|
items: [
|
||||||
{ chapterId: 'online-c1', label: '在线第 1 话', pages: 1 },
|
{ chapterId: 'online-c1', label: '在线第 1 话', pages: 8 },
|
||||||
{ chapterId: 'online-c2', label: '在线第 2 话', pages: 1 }
|
{ chapterId: 'online-c2', label: '在线第 2 话', pages: 8 }
|
||||||
],
|
],
|
||||||
page: 1,
|
page: 1,
|
||||||
maxPage: 1
|
maxPage: 1
|
||||||
});
|
});
|
||||||
mangaDex.chapterImageUrls = async () => ({
|
mangaDex.chapterImageUrls = async () => ({
|
||||||
urls: [imageUrl],
|
urls: Array(8).fill(imageUrl),
|
||||||
mustReport: false,
|
mustReport: false,
|
||||||
quality: 'dataSaver'
|
quality: 'dataSaver'
|
||||||
});
|
});
|
||||||
@@ -125,6 +125,7 @@ app.whenReady().then(async () => {
|
|||||||
mangaId: 'online-manga',
|
mangaId: 'online-manga',
|
||||||
title: '在线阅读夹具',
|
title: '在线阅读夹具',
|
||||||
authors: ['测试作者'],
|
authors: ['测试作者'],
|
||||||
|
initialChapterId: 'online-c2',
|
||||||
language: 'zh',
|
language: 'zh',
|
||||||
quality: 'dataSaver'
|
quality: 'dataSaver'
|
||||||
})`);
|
})`);
|
||||||
@@ -137,30 +138,37 @@ app.whenReady().then(async () => {
|
|||||||
try {
|
try {
|
||||||
firstChapter = await waitUntil(() => onlineWindow.webContents.executeJavaScript(
|
firstChapter = await waitUntil(() => onlineWindow.webContents.executeJavaScript(
|
||||||
`document.querySelectorAll('.chapter-item').length === 2
|
`document.querySelectorAll('.chapter-item').length === 2
|
||||||
&& document.querySelector('.manga-page img')
|
&& document.getElementById('chapterTitle').textContent === '在线第 2 话'
|
||||||
&& document.querySelector('.manga-page img').naturalWidth > 0`
|
&& Array.from(document.querySelectorAll('.manga-page img'))
|
||||||
|
.filter((image) => image.naturalWidth > 0).length >= 4`
|
||||||
));
|
));
|
||||||
} catch (error) { /* 由下面的诊断状态报告 */ }
|
} catch (error) { /* 由下面的诊断状态报告 */ }
|
||||||
const onlineState = await onlineWindow.webContents.executeJavaScript(`(() => ({
|
const onlineState = await onlineWindow.webContents.executeJavaScript(`(() => ({
|
||||||
status: document.getElementById('readerStatus').textContent,
|
status: document.getElementById('readerStatus').textContent,
|
||||||
statusHidden: document.getElementById('readerStatus').classList.contains('hidden'),
|
statusHidden: document.getElementById('readerStatus').classList.contains('hidden'),
|
||||||
|
chapterTitle: document.getElementById('chapterTitle').textContent,
|
||||||
chapters: document.querySelectorAll('.chapter-item').length,
|
chapters: document.querySelectorAll('.chapter-item').length,
|
||||||
pages: document.querySelectorAll('.manga-page').length,
|
pages: document.querySelectorAll('.manga-page').length,
|
||||||
|
scrollTop: document.getElementById('pageScroller').scrollTop,
|
||||||
pageState: document.querySelector('.manga-page')?.dataset.state || '',
|
pageState: document.querySelector('.manga-page')?.dataset.state || '',
|
||||||
pageText: document.querySelector('.manga-page')?.dataset.placeholder || ''
|
pageText: document.querySelector('.manga-page')?.dataset.placeholder || ''
|
||||||
}))()`);
|
}))()`);
|
||||||
check('在线阅读器读取整部章节目录并通过主进程显示图片',
|
check('在线阅读器读取整部章节目录并通过主进程显示图片',
|
||||||
!!firstChapter, JSON.stringify(onlineState));
|
!!firstChapter, JSON.stringify(onlineState));
|
||||||
let secondChapter = false;
|
check('章节阅读入口会直接打开指定章节',
|
||||||
|
firstChapter && onlineState.chapterTitle === '在线第 2 话', JSON.stringify(onlineState));
|
||||||
|
check('漫画长图异步展开时停留在章节第一页',
|
||||||
|
firstChapter && onlineState.scrollTop === 0, JSON.stringify(onlineState));
|
||||||
|
let previousChapter = false;
|
||||||
if (firstChapter) {
|
if (firstChapter) {
|
||||||
await onlineWindow.webContents.executeJavaScript(`document.getElementById('nextChapterBtn').click()`);
|
await onlineWindow.webContents.executeJavaScript(`document.getElementById('prevChapterBtn').click()`);
|
||||||
secondChapter = await waitUntil(() => onlineWindow.webContents.executeJavaScript(`(() => {
|
previousChapter = await waitUntil(() => onlineWindow.webContents.executeJavaScript(`(() => {
|
||||||
const image = document.querySelector('.manga-page img');
|
const image = document.querySelector('.manga-page img');
|
||||||
return document.getElementById('chapterTitle').textContent === '在线第 2 话'
|
return document.getElementById('chapterTitle').textContent === '在线第 1 话'
|
||||||
&& image && image.naturalWidth > 0;
|
&& image && image.naturalWidth > 0;
|
||||||
})()`));
|
})()`));
|
||||||
}
|
}
|
||||||
check('在线阅读器可连续切换到下一章', secondChapter);
|
check('在线阅读器可连续切换到上一章', previousChapter);
|
||||||
const afterOnline = await win.webContents.executeJavaScript(
|
const afterOnline = await win.webContents.executeJavaScript(
|
||||||
`window.api.library.list().then((result) => result.data.length)`
|
`window.api.library.list().then((result) => result.data.length)`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -368,6 +368,25 @@ test('标准构建入口固定输出目录并保留便携数据', () => {
|
|||||||
assert.doesNotMatch(build, /\$\{PRODUCT\}-\$\{pkg\.version\}/);
|
assert.doesNotMatch(build, /\$\{PRODUCT\}-\$\{pkg\.version\}/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('所有构建与发布入口都包含在线漫画 preload', () => {
|
||||||
|
const root = path.join(__dirname, '..', '..');
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
||||||
|
const portable = fs.readFileSync(path.join(root, 'build-portable.js'), 'utf8');
|
||||||
|
const mac = fs.readFileSync(path.join(root, 'build-mac.js'), 'utf8');
|
||||||
|
const linux = fs.readFileSync(path.join(root, 'build-linux.js'), 'utf8');
|
||||||
|
const release = fs.readFileSync(path.join(root, 'build-release.js'), 'utf8');
|
||||||
|
|
||||||
|
assert.ok(pkg.build.files.includes('manga-online-preload.js'));
|
||||||
|
for (const [name, source] of [['Windows', portable], ['macOS', mac], ['Linux', linux]]) {
|
||||||
|
assert.match(source, /manga-online-preload\.js/, `${name} 构建漏掉在线漫画 preload`);
|
||||||
|
}
|
||||||
|
assert.strictEqual(
|
||||||
|
(release.match(/resources\/app\/manga-online-preload\.js/g) || []).length,
|
||||||
|
2,
|
||||||
|
'Windows 与 Linux 发布件回读都必须检查在线漫画 preload'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
function loadPathResolvers(platform, isPackaged) {
|
function loadPathResolvers(platform, isPackaged) {
|
||||||
return h.extractFns(
|
return h.extractFns(
|
||||||
mainFile,
|
mainFile,
|
||||||
|
|||||||
@@ -36,10 +36,12 @@ test('在线漫画会话与窗口 sender 绑定,并只授权目录里可用的
|
|||||||
const created = sessions.create(11, source(), {
|
const created = sessions.create(11, source(), {
|
||||||
mangaId: 'manga-1',
|
mangaId: 'manga-1',
|
||||||
title: '在线漫画',
|
title: '在线漫画',
|
||||||
|
initialChapterId: 'c1',
|
||||||
language: 'zh-hk',
|
language: 'zh-hk',
|
||||||
quality: 'data'
|
quality: 'data'
|
||||||
});
|
});
|
||||||
assert.strictEqual(created.title, '在线漫画');
|
assert.strictEqual(created.title, '在线漫画');
|
||||||
|
assert.strictEqual(created.initialChapterId, 'c1');
|
||||||
assert.strictEqual(created.quality, 'data');
|
assert.strictEqual(created.quality, 'data');
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
Promise.resolve().then(() => sessions.meta(12, created.sessionId)),
|
Promise.resolve().then(() => sessions.meta(12, created.sessionId)),
|
||||||
|
|||||||
@@ -272,11 +272,14 @@ test('漫画源按分类显示,MangaDex 可筛选中文章节并设置图片
|
|||||||
test('漫画详情提供不入库的整部在线阅读器,并通过独立 preload 懒加载图片', () => {
|
test('漫画详情提供不入库的整部在线阅读器,并通过独立 preload 懒加载图片', () => {
|
||||||
const html = fs.readFileSync(path.join(__dirname, '..', 'ui', 'manga-online.html'), 'utf8');
|
const html = fs.readFileSync(path.join(__dirname, '..', 'ui', 'manga-online.html'), 'utf8');
|
||||||
const script = fs.readFileSync(path.join(__dirname, '..', 'ui', 'manga-online.js'), 'utf8');
|
const script = fs.readFileSync(path.join(__dirname, '..', 'ui', 'manga-online.js'), 'utf8');
|
||||||
|
const mangaCss = fs.readFileSync(path.join(__dirname, '..', 'ui', 'manga-online.css'), 'utf8');
|
||||||
const browse = fs.readFileSync(path.join(__dirname, '..', 'ui', 'views', 'browse.js'), 'utf8');
|
const browse = fs.readFileSync(path.join(__dirname, '..', 'ui', 'views', 'browse.js'), 'utf8');
|
||||||
const preload = fs.readFileSync(path.join(__dirname, '..', '..', 'manga-online-preload.js'), 'utf8');
|
const preload = fs.readFileSync(path.join(__dirname, '..', '..', 'manga-online-preload.js'), 'utf8');
|
||||||
|
|
||||||
assert.match(browse, /id="onlineReadBtn">在线阅读整部漫画/);
|
assert.match(browse, /id="onlineReadBtn">在线阅读整部漫画/);
|
||||||
assert.match(browse, /window\.api\.sources\.readOnline\(state\.activeSourceId/);
|
assert.match(browse, /window\.api\.sources\.readOnline\(state\.activeSourceId/);
|
||||||
|
assert.match(browse, /data-chapter-read=/);
|
||||||
|
assert.match(browse, /initialChapterId: chapter && chapter\.chapterId/);
|
||||||
assert.doesNotMatch(browse.slice(browse.indexOf('async function readOnline'), browse.indexOf('async function loadChapters')), /library\.add/);
|
assert.doesNotMatch(browse.slice(browse.indexOf('async function readOnline'), browse.indexOf('async function loadChapters')), /library\.add/);
|
||||||
assert.match(html, /Content-Security-Policy" content="default-src 'self'; img-src 'self' blob: data:/);
|
assert.match(html, /Content-Security-Policy" content="default-src 'self'; img-src 'self' blob: data:/);
|
||||||
assert.match(html, /id="chapterList"/);
|
assert.match(html, /id="chapterList"/);
|
||||||
@@ -284,6 +287,8 @@ test('漫画详情提供不入库的整部在线阅读器,并通过独立 prel
|
|||||||
assert.match(script, /new IntersectionObserver/);
|
assert.match(script, /new IntersectionObserver/);
|
||||||
assert.match(script, /URL\.createObjectURL\(new Blob/);
|
assert.match(script, /URL\.createObjectURL\(new Blob/);
|
||||||
assert.match(script, /ensureNextChapter/);
|
assert.match(script, /ensureNextChapter/);
|
||||||
|
assert.match(script, /state\.meta\.initialChapterId/);
|
||||||
|
assert.match(mangaCss, /\.page-scroller\s*\{[^}]*overflow-anchor:\s*none/);
|
||||||
assert.match(preload, /mangaOnline:image/);
|
assert.match(preload, /mangaOnline:image/);
|
||||||
assert.doesNotMatch(preload, /library:|reader:bytes|shell:openPath/);
|
assert.doesNotMatch(preload, /library:|reader:bytes|shell:openPath/);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ function create(ownerId, source, input) {
|
|||||||
.map((author) => cleanText(author, 120))
|
.map((author) => cleanText(author, 120))
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.slice(0, 20),
|
.slice(0, 20),
|
||||||
|
initialChapterId: cleanText(input && input.initialChapterId, 500),
|
||||||
language,
|
language,
|
||||||
quality,
|
quality,
|
||||||
chapterPages: new Map(),
|
chapterPages: new Map(),
|
||||||
@@ -64,6 +65,7 @@ function metaOf(session) {
|
|||||||
title: session.title,
|
title: session.title,
|
||||||
cover: session.cover,
|
cover: session.cover,
|
||||||
authors: session.authors.slice(),
|
authors: session.authors.slice(),
|
||||||
|
initialChapterId: session.initialChapterId,
|
||||||
language: session.language,
|
language: session.language,
|
||||||
quality: session.quality
|
quality: session.quality
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ button, input { font: inherit; }
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
overflow-anchor: none;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -281,7 +281,16 @@ async function init() {
|
|||||||
setStatus('该漫画暂无可在线阅读的章节', true);
|
setStatus('该漫画暂无可在线阅读的章节', true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
openChapter(0);
|
let initialIndex = state.meta.initialChapterId
|
||||||
|
? state.chapters.findIndex((chapter) => chapter.chapterId === state.meta.initialChapterId)
|
||||||
|
: 0;
|
||||||
|
while (initialIndex < 0 && state.chapterPage < state.chapterMaxPage) {
|
||||||
|
if (!await loadChapterPage(state.chapterPage + 1)) return;
|
||||||
|
initialIndex = state.chapters.findIndex(
|
||||||
|
(chapter) => chapter.chapterId === state.meta.initialChapterId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
openChapter(initialIndex < 0 ? 0 : initialIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
$('tocToggleBtn').addEventListener('click', () => $('tocPane').classList.toggle('collapsed'));
|
$('tocToggleBtn').addEventListener('click', () => $('tocPane').classList.toggle('collapsed'));
|
||||||
|
|||||||
+13
-4
@@ -399,7 +399,7 @@ const Browse = (() => {
|
|||||||
|
|
||||||
$('addLibBtn').onclick = addToLibrary;
|
$('addLibBtn').onclick = addToLibrary;
|
||||||
const onlineReadBtn = $('onlineReadBtn');
|
const onlineReadBtn = $('onlineReadBtn');
|
||||||
if (onlineReadBtn) onlineReadBtn.onclick = readOnline;
|
if (onlineReadBtn) onlineReadBtn.onclick = () => readOnline();
|
||||||
const urlLink = $('detailUrlLink');
|
const urlLink = $('detailUrlLink');
|
||||||
if (urlLink) urlLink.onclick = (e) => { e.preventDefault(); window.api.openExternal(d.url); };
|
if (urlLink) urlLink.onclick = (e) => { e.preventDefault(); window.api.openExternal(d.url); };
|
||||||
if (chapterBased) {
|
if (chapterBased) {
|
||||||
@@ -416,9 +416,10 @@ const Browse = (() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readOnline() {
|
async function readOnline(chapter, chapterButton) {
|
||||||
const button = $('onlineReadBtn');
|
const button = chapterButton || $('onlineReadBtn');
|
||||||
if (!button || !state.currentDetail) return;
|
if (!button || !state.currentDetail) return;
|
||||||
|
const oldText = button.textContent;
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
button.textContent = '正在打开…';
|
button.textContent = '正在打开…';
|
||||||
const [qualitySetting, languageSetting] = await Promise.all([
|
const [qualitySetting, languageSetting] = await Promise.all([
|
||||||
@@ -436,11 +437,14 @@ const Browse = (() => {
|
|||||||
title: state.currentDetail.title,
|
title: state.currentDetail.title,
|
||||||
cover: state.currentDetail.cover,
|
cover: state.currentDetail.cover,
|
||||||
authors: state.currentDetail.authors,
|
authors: state.currentDetail.authors,
|
||||||
|
initialChapterId: chapter && chapter.chapterId,
|
||||||
language,
|
language,
|
||||||
quality
|
quality
|
||||||
});
|
});
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
button.textContent = result.ok ? '在线阅读器已打开' : '在线阅读整部漫画';
|
button.textContent = result.ok
|
||||||
|
? (chapter ? '已打开' : '在线阅读器已打开')
|
||||||
|
: oldText;
|
||||||
button.title = result.ok ? '' : result.error || '在线阅读器打开失败';
|
button.title = result.ok ? '' : result.error || '在线阅读器打开失败';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,6 +468,7 @@ const Browse = (() => {
|
|||||||
<div class="dl-file-row" data-chapter-id="${escapeHtml(c.chapterId)}">
|
<div class="dl-file-row" data-chapter-id="${escapeHtml(c.chapterId)}">
|
||||||
<span class="dl-file-name" title="${escapeHtml(c.label)}">${escapeHtml(c.label)}${hint}</span>
|
<span class="dl-file-name" title="${escapeHtml(c.label)}">${escapeHtml(c.label)}${hint}</span>
|
||||||
${meta ? `<span class="dl-fmt">${escapeHtml(meta)}</span>` : ''}
|
${meta ? `<span class="dl-fmt">${escapeHtml(meta)}</span>` : ''}
|
||||||
|
<button class="copy-btn" data-chapter-read="${escapeHtml(c.chapterId)}" ${disabled ? 'disabled' : ''}>阅读</button>
|
||||||
<button class="dl-btn" data-chapter-dl="${escapeHtml(c.chapterId)}" ${disabled ? 'disabled' : ''}>下载</button>
|
<button class="dl-btn" data-chapter-dl="${escapeHtml(c.chapterId)}" ${disabled ? 'disabled' : ''}>下载</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -505,6 +510,10 @@ const Browse = (() => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
list.innerHTML = items.map(chapterRowHtml).join('');
|
list.innerHTML = items.map(chapterRowHtml).join('');
|
||||||
|
list.querySelectorAll('.copy-btn[data-chapter-read]').forEach((btn) => {
|
||||||
|
const chapter = items.find((c) => c.chapterId === btn.dataset.chapterRead);
|
||||||
|
btn.onclick = () => readOnline(chapter, btn);
|
||||||
|
});
|
||||||
list.querySelectorAll('.dl-btn[data-chapter-dl]').forEach((btn) => {
|
list.querySelectorAll('.dl-btn[data-chapter-dl]').forEach((btn) => {
|
||||||
const chapter = items.find((c) => c.chapterId === btn.dataset.chapterDl);
|
const chapter = items.find((c) => c.chapterId === btn.dataset.chapterDl);
|
||||||
btn.onclick = () => downloadChapter(btn, chapter);
|
btn.onclick = () => downloadChapter(btn, chapter);
|
||||||
|
|||||||
Reference in New Issue
Block a user