Fix empty chapters may saved when auto save enabled

This commit is contained in:
2026-02-27 13:58:42 +08:00
parent 69d8e8ec9f
commit c276073627
6 changed files with 71 additions and 19 deletions

View File

@@ -6,7 +6,7 @@ import { useDb } from "../dbProvider";
import type { QdChapterInfo } from "../../types";
import type { Volume } from "../../qdtypes";
import { useBookInfo } from "./BookInfoProvider";
import { get_new_volumes } from "../../utils/qd";
import { ChapterShowMode, get_new_volumes } from "../../utils/qd";
import VolumesList from "./VolumesList";
import styles from './BookChapter.module.css';
import ChapterEditor from "./ChapterEditor";
@@ -79,8 +79,8 @@ export default function BookChapter() {
}
let vols: Volume[] = bookInfo.volumes;
if (bookStatus.chapterLists) {
vols = get_new_volumes(bookStatus.chapterLists, bookInfo.volumes, !bookStatus.showSavedOnly);
} else if (bookStatus.showSavedOnly) {
vols = get_new_volumes(bookStatus.chapterLists, bookInfo.volumes, bookStatus.chapterShowMode);
} else if (bookStatus.chapterShowMode != ChapterShowMode.All) {
vols = [];
}
return (<>
@@ -90,8 +90,8 @@ export default function BookChapter() {
}, 1);
}}>
<Splitter.Panel min='20%' max='40%' defaultSize='30%' collapsible className={styles.chs}>
{bookStatus.showSavedOnly && listErr && <Result status="error" title="加载章节列表失败" subTitle={listErr} extra={<Button type="primary" onClick={handle_list_load}></Button>} />}
{bookStatus.showSavedOnly && !bookStatus.chapterLists && !listErr && <Skeleton active />}
{bookStatus.chapterShowMode != ChapterShowMode.All && listErr && <Result status="error" title="加载章节列表失败" subTitle={listErr} extra={<Button type="primary" onClick={handle_list_load}></Button>} />}
{bookStatus.chapterShowMode != ChapterShowMode.All && !bookStatus.chapterLists && !listErr && <Skeleton active />}
{vols.length > 0 && <VolumesList bookId={bookInfo.id} volumes={vols} oneLine />}
</Splitter.Panel>
<Splitter.Panel className={styles.chc}>

View File

@@ -1,4 +1,4 @@
import { Affix, Flex, Space, Tag, Typography, Switch, Skeleton, Result, Button } from "antd";
import { Affix, Flex, Space, Tag, Typography, Skeleton, Result, Button } from "antd";
import { useBookInfo } from "./BookInfoProvider";
import styles from './BookIndex.module.css';
import { loadChapterListsIfNeeded, useBookContext, useBookStatus } from "./BookStatusProvider";
@@ -6,7 +6,8 @@ import VolumesList from "./VolumesList";
import { useEffect, useState } from "react";
import { useDb } from "../dbProvider";
import type { Volume } from "../../qdtypes";
import { get_new_volumes } from "../../utils/qd";
import { ChapterShowMode, get_new_volumes } from "../../utils/qd";
import ShowMode from "./ShowMode";
const { Paragraph, Link } = Typography;
@@ -18,8 +19,8 @@ export default function BookIndex() {
const [bookStatus, setBookStatus] = useBookStatus();
const setItems = useBookContext();
const [err, setErr] = useState<string | null>(null);
function setShowSavedOnly(showSavedOnly: boolean) {
setBookStatus({ ...bookStatus, showSavedOnly });
function setChapterShowMode(chapterShowMode: ChapterShowMode) {
setBookStatus({ ...bookStatus, chapterShowMode });
}
function handle() {
if (err) {
@@ -36,8 +37,8 @@ export default function BookIndex() {
setItems([]);
let vols: Volume[] = bookInfo.volumes;
if (bookStatus.chapterLists) {
vols = get_new_volumes(bookStatus.chapterLists, bookInfo.volumes, !bookStatus.showSavedOnly);
} else if (bookStatus.showSavedOnly) {
vols = get_new_volumes(bookStatus.chapterLists, bookInfo.volumes, bookStatus.chapterShowMode);
} else if (bookStatus.chapterShowMode != ChapterShowMode.All) {
vols = [];
}
return (
@@ -64,11 +65,11 @@ export default function BookIndex() {
</Flex>
<Affix offsetTop={10}>
<Flex justify="flex-end" className={styles.affix}>
<Switch checked={bookStatus.showSavedOnly} onChange={setShowSavedOnly} checkedChildren="仅显示已保存章节" unCheckedChildren="显示所有章节" />
<ShowMode mode={bookStatus.chapterShowMode} onChange={setChapterShowMode} />
</Flex>
</Affix>
{bookStatus.showSavedOnly && err && <Result status="error" title="加载章节列表失败" subTitle={err} extra={<Button type="primary" onClick={handle}></Button>} />}
{bookStatus.showSavedOnly && !bookStatus.chapterLists && !err && <Skeleton active />}
{bookStatus.chapterShowMode != ChapterShowMode.All && err && <Result status="error" title="加载章节列表失败" subTitle={err} extra={<Button type="primary" onClick={handle}></Button>} />}
{bookStatus.chapterShowMode != ChapterShowMode.All && !bookStatus.chapterLists && !err && <Skeleton active />}
{vols.length > 0 && <VolumesList bookId={bookInfo.id} volumes={vols} />}
</div>
);

View File

@@ -3,15 +3,16 @@ import { QdChapterSimpleInfo } from "../../types";
import { Db } from "../../db/interfaces";
import { useOutletContext } from "react-router";
import type { ItemType } from "antd/es/breadcrumb/Breadcrumb";
import { ChapterShowMode } from "../../utils/qd";
export type BookStatus = {
showSavedOnly: boolean;
chapterShowMode: ChapterShowMode;
chapterLists?: QdChapterSimpleInfo[];
}
export function createBookStatus(): BookStatus {
return {
showSavedOnly: false,
chapterShowMode: ChapterShowMode.All,
}
}

View File

@@ -0,0 +1,28 @@
import { Select } from "antd";
import { ChapterShowMode } from "../../utils/qd"
const TEXTS = {
[ChapterShowMode.All]: "显示所有章节",
[ChapterShowMode.SavedOnly]: "仅显示已保存章节",
[ChapterShowMode.UnsavedOnly]: "仅显示未保存章节",
}
export interface ShowModeProps {
mode: ChapterShowMode;
onChange: (mode: ChapterShowMode) => void;
}
export default function ShowMode({ mode, onChange }: ShowModeProps) {
return (
<Select
value={mode}
onChange={onChange}
options={
Object.values(ChapterShowMode).filter(value => typeof value === 'number').map(value => ({
label: TEXTS[value as ChapterShowMode],
value,
}))
}
/>
);
}

View File

@@ -41,6 +41,10 @@ async function load() {
} else {
contents = get_chapter_content(chapterInfo.content);
}
if (contents.length === 0) {
setTimeout(load, 1000);
return;
}
// Clear encrypted content to reduce size.
chapterInfo.content = '';
const msg: SendMessage = {

View File

@@ -3,6 +3,13 @@ import type { QdChapterInfo, QdChapterSimpleInfo } from "../types";
import { get_chapter_content, toHex } from "../utils";
import type { Chapter, Volume } from "../qdtypes";
export enum ChapterShowMode {
All,
SavedOnly,
UnsavedOnly,
}
export function hash_qdchapter_info(info: QdChapterInfo): string {
const encoder = new TextEncoder();
const h = new SHA256();
@@ -29,9 +36,9 @@ export function hash_qdchapter_info(info: QdChapterInfo): string {
return toHex(hash);
}
export function get_new_volumes(chapterLists: QdChapterSimpleInfo[], volumes: Volume[], keep=true): Volume[] {
export function get_new_volumes(chapterLists: QdChapterSimpleInfo[], volumes: Volume[], keepMode: ChapterShowMode): Volume[] {
const vols: Volume[] = [];
if (keep) {
if (keepMode == ChapterShowMode.All) {
const volMap: Map<number, Chapter> = new Map();
for (const vo of volumes) {
const vol = structuredClone(vo);
@@ -61,7 +68,7 @@ export function get_new_volumes(chapterLists: QdChapterSimpleInfo[], volumes: Vo
isVip: false,
});
}
} else {
} else if (keepMode == ChapterShowMode.SavedOnly) {
const chIds = new Set(chapterLists.map(ch => ch.id));
for (const vol of volumes) {
const newChs = vol.chapters.filter(ch => chIds.has(ch.id));
@@ -72,6 +79,17 @@ export function get_new_volumes(chapterLists: QdChapterSimpleInfo[], volumes: Vo
});
}
}
} else if (keepMode == ChapterShowMode.UnsavedOnly) {
const chIds = new Set(chapterLists.map(ch => ch.id));
for (const vol of volumes) {
const newChs = vol.chapters.filter(ch => !chIds.has(ch.id));
if (newChs.length > 0) {
vols.push({
...vol,
chapters: newChs,
});
}
}
}
return vols;
}