mirror of
https://github.com/lifegpc/bookdownload.git
synced 2026-06-06 05:38:46 +08:00
Make Sure Editor Resize
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
"@types/chrome": "^0.1.36",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/wicg-file-system-access": "^2023.10.7",
|
||||
"@zip.js/zip.js": "^2.8.21",
|
||||
"antd": "^6.3.0",
|
||||
"colors": "^1.4.0",
|
||||
|
||||
@@ -75,7 +75,7 @@ function get_datas<T>(db: IDBDatabase, storeName: string, key?: IDBValidKey | ID
|
||||
});
|
||||
}
|
||||
|
||||
function get_data_with_convert<T, U>(db: IDBDatabase, storeName: string, key: IDBValidKey | IDBKeyRange, convert: (key: IDBValidKey, data: T) => Promise<U> | U, index?: string, direction?: IDBCursorDirection): Promise<U | undefined> {
|
||||
function get_data_with_convert<T, U>(db: IDBDatabase, storeName: string, key: IDBValidKey | IDBKeyRange | undefined, convert: (key: IDBValidKey, data: T) => Promise<U> | U, index?: string, direction?: IDBCursorDirection): Promise<U | undefined> {
|
||||
return new Promise<U | undefined>((resolve, reject) => {
|
||||
const tx = db.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
@@ -418,6 +418,20 @@ export class IndexedDb implements Db {
|
||||
return data;
|
||||
}, 'id', 'prev');
|
||||
}
|
||||
async getQdNewChapterId(): Promise<number> {
|
||||
const smallest_id = await get_data_with_convert<CompressedQdChapterInfo | QdChapterInfo, number>(this.qddb, 'chapters', undefined, async (key, data) => {
|
||||
if ('compressed' in data) {
|
||||
const decompressed = await decompress(data.compressed);
|
||||
const decoded = new TextDecoder().decode(decompressed);
|
||||
data = JSON.parse(decoded) as QdChapterInfo;
|
||||
}
|
||||
return data.id;
|
||||
}, 'id', 'prev');
|
||||
if (smallest_id === undefined || smallest_id >= 0) {
|
||||
return -1;
|
||||
}
|
||||
return smallest_id - 1;
|
||||
}
|
||||
async setAsLatestQdChapter(key: unknown): Promise<unknown> {
|
||||
const chapter = await this.getQdChapter(key);
|
||||
if (!chapter) {
|
||||
|
||||
@@ -61,6 +61,10 @@ export interface Db {
|
||||
* @param id Chapter ID
|
||||
*/
|
||||
getLatestQdChapter(id: number): Promise<QdChapterInfo | undefined>;
|
||||
/**
|
||||
* Returns a new chapter ID (<0) that is not used in the database.
|
||||
*/
|
||||
getQdNewChapterId(): Promise<number>;
|
||||
/**
|
||||
* Set the latest chapter by primary key. This function will update the time of the chapter.
|
||||
* @param key Primary key of the chapter, which is determined by the database implementation.
|
||||
|
||||
@@ -346,6 +346,18 @@ export class PocketBaseDb implements Db {
|
||||
}
|
||||
return record;
|
||||
}
|
||||
async getQdNewChapterId(): Promise<number> {
|
||||
const smallest_id = await this.client.collection(`${this.cfg.prefix}qd_chapters`).getList(1, 1, {
|
||||
filter: `chapterId < 0`,
|
||||
fields: 'chapterId',
|
||||
sort: 'chapterId',
|
||||
});
|
||||
if (smallest_id.totalItems === 0) {
|
||||
return -1;
|
||||
}
|
||||
const id = smallest_id.items[0].chapterId;
|
||||
return id - 1;
|
||||
}
|
||||
async setAsLatestQdChapter(key: unknown): Promise<unknown> {
|
||||
await this.client.collection(`${this.cfg.prefix}qd_chapters`).update(String(key), {
|
||||
time: Date.now(),
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function Book({info, options, save_type}: QdBookProps) {
|
||||
const [total, setTotal] = useState<number>(0);
|
||||
const [current, setCurrent] = useState<number>(0);
|
||||
async function save() {
|
||||
const pickerOptions = {
|
||||
const pickerOptions: SaveFilePickerOptions = {
|
||||
suggestedName: `${info.bookName}.${save_type}`,
|
||||
types: [{
|
||||
description: 'EPUB File',
|
||||
|
||||
@@ -10,6 +10,7 @@ import { DbContext } from "./manage/dbProvider";
|
||||
import QdBook from "./manage/qd/Book";
|
||||
import QdBookIndex from "./manage/qd/BookIndex";
|
||||
import QdBookChapter from "./manage/qd/BookChapter";
|
||||
import QdBookNewChapter from "./manage/qd/BookNewChapter";
|
||||
|
||||
const router = createHashRouter([
|
||||
{
|
||||
@@ -31,6 +32,10 @@ const router = createHashRouter([
|
||||
{
|
||||
path: "chapter/:chapterId",
|
||||
element: <QdBookChapter />
|
||||
},
|
||||
{
|
||||
path: "chapter/new",
|
||||
element: <QdBookNewChapter />
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import ShowMode from "./ShowMode";
|
||||
import { sendMessageToTab, waitTabLoaded } from "../../utils";
|
||||
import { QdBookDownloadOptions } from "../../types";
|
||||
import SwitchLabel from "../../components/SwitchLabel";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
const { Paragraph, Link } = Typography;
|
||||
|
||||
@@ -24,6 +25,7 @@ export default function BookIndex() {
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
const [saveChapterOpenAsEpub, setSaveChapterOpenAsEpub] = useState(false);
|
||||
const [downloadOptions, setDownloadOptions] = useState<QdBookDownloadOptions>({});
|
||||
const navigate = useNavigate();
|
||||
function setChapterShowMode(chapterShowMode: ChapterShowMode) {
|
||||
setBookStatus({ ...bookStatus, chapterShowMode });
|
||||
}
|
||||
@@ -104,6 +106,7 @@ export default function BookIndex() {
|
||||
label="跳过未保存章节"
|
||||
/>
|
||||
</Modal>
|
||||
<Button onClick={() => navigate('chapter/new')}>新章节</Button>
|
||||
</Flex>
|
||||
<Affix offsetTop={10}>
|
||||
<Flex justify="flex-end" className={styles.affix}>
|
||||
|
||||
161
src/manage/qd/BookNewChapter.tsx
Normal file
161
src/manage/qd/BookNewChapter.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import { useBookContext } from "./BookStatusProvider";
|
||||
import type { QdChapterInfo } from "../../types";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDb } from "../dbProvider";
|
||||
import { useBookInfo } from "./BookInfoProvider";
|
||||
import { Result, Skeleton } from "antd";
|
||||
|
||||
export default function BookNewChapter() {
|
||||
const setItems = useBookContext();
|
||||
const [chapter, setChapter] = useState<QdChapterInfo | null>(null);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
const bookInfo = useBookInfo();
|
||||
const db = useDb();
|
||||
setItems([
|
||||
{ title: "新章节" },
|
||||
])
|
||||
async function load() {
|
||||
const new_id = await db.getQdNewChapterId();
|
||||
console.log("new chapter id", new_id);
|
||||
const time = Date.now();
|
||||
setChapter({
|
||||
id: new_id,
|
||||
bookId: bookInfo.id,
|
||||
contents: [],
|
||||
time,
|
||||
bookInfo: {
|
||||
bookId: bookInfo.id,
|
||||
bookName: bookInfo.bookName,
|
||||
sbookid: 0,
|
||||
authorId: Number(bookInfo.bookInfo.pageJson.authorInfo.authorId),
|
||||
authorName: bookInfo.bookInfo.pageJson.authorInfo.authorName,
|
||||
cAuthorId: '',
|
||||
chanId: bookInfo.bookInfo.chanId,
|
||||
chanName: '',
|
||||
chanUrl: '',
|
||||
chanAlias: '',
|
||||
subCateId: 0,
|
||||
subCateName: '',
|
||||
unitCategoryId: 0,
|
||||
unitSubCategoryId: 0,
|
||||
isVip: bookInfo.bookInfo.pageJson.isVip,
|
||||
bookType: bookInfo.bookInfo.pageJson.bookType,
|
||||
form: 0,
|
||||
chargetype: 0,
|
||||
totalprice: 0,
|
||||
fineLayout: 0,
|
||||
isPreCollection: 0,
|
||||
bookStore: {
|
||||
member: false,
|
||||
app: false,
|
||||
story: false,
|
||||
},
|
||||
bookStatus: '',
|
||||
actionStatus: '',
|
||||
signStatus: bookInfo.bookInfo.pageJson.signStatus,
|
||||
joinTime: '',
|
||||
collect: 0,
|
||||
updChapterId: 0,
|
||||
updChapterName: '',
|
||||
updTime: 0,
|
||||
updChapterUrl: '',
|
||||
cbid: '',
|
||||
editorNickname: '',
|
||||
bookLabels: [],
|
||||
bookTag: {
|
||||
tagName: '',
|
||||
},
|
||||
updInfo: {
|
||||
desc: '',
|
||||
tag: '',
|
||||
updStatus: '',
|
||||
},
|
||||
supplierId: '',
|
||||
interact: {
|
||||
recTicketEnable: 0,
|
||||
monthTicketEnable: 0,
|
||||
donateEnable: 0,
|
||||
},
|
||||
joinTimes: 0,
|
||||
isSign: bookInfo.bookInfo.pageJson.isSign,
|
||||
noRewardMonthTic: 0,
|
||||
bookAllAuth: 0,
|
||||
},
|
||||
chapterInfo: {
|
||||
actualWords: 0,
|
||||
authorRecommend: [],
|
||||
authorSay: '',
|
||||
cbid: '',
|
||||
ccid: '',
|
||||
chapterId: new_id,
|
||||
chapterName: '',
|
||||
chapterOrder: 0,
|
||||
chapterType: 0,
|
||||
cvid: '',
|
||||
extra: {
|
||||
nextCcid: '',
|
||||
nextName: '',
|
||||
nextVipStatus: 0,
|
||||
preCcid: '',
|
||||
prevName: '',
|
||||
prevVipStatus: 0,
|
||||
volumeBody: false,
|
||||
volumeName: '',
|
||||
nextUrl: '',
|
||||
preUrl: '',
|
||||
},
|
||||
fineLayout: 0,
|
||||
freeStatus: 0,
|
||||
modifyTime: time,
|
||||
multiModal: 0,
|
||||
nextCcid: '',
|
||||
prevCcid: '',
|
||||
seq: 0,
|
||||
updateTime: new Date(time).toISOString(),
|
||||
uuid: 0,
|
||||
vipStatus: 0,
|
||||
volumeId: 0,
|
||||
wordsCount: 0,
|
||||
isFirst: 0,
|
||||
content: '',
|
||||
riskInfo: {
|
||||
banId: 0,
|
||||
banMessage: '',
|
||||
sessionKey: '',
|
||||
captchaAId: '',
|
||||
captchaURL: '',
|
||||
phoneNumber: '',
|
||||
gt: '',
|
||||
challenge: '',
|
||||
offline: 0,
|
||||
newCaptcha: 0,
|
||||
captchaType: 0,
|
||||
},
|
||||
riskbe: {
|
||||
be: 0,
|
||||
message: '',
|
||||
},
|
||||
updateTimestamp: time,
|
||||
isBuy: 0,
|
||||
limitFree: 0,
|
||||
authorWords: {
|
||||
content: '',
|
||||
},
|
||||
eFW: 0,
|
||||
cES: 0,
|
||||
guidMark: '',
|
||||
fEnS: 0,
|
||||
},
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
load().catch(e => {
|
||||
console.warn(e);
|
||||
setErr(e instanceof Error ? e.message : String(e));
|
||||
});
|
||||
}, []);
|
||||
return (<>
|
||||
{err && <Result title="加载失败" status="error" subTitle={err} />}
|
||||
{!err && !chapter && <Skeleton active />}
|
||||
</>);
|
||||
}
|
||||
@@ -37,6 +37,8 @@ export interface ChapterEditorState {
|
||||
|
||||
export default class ChapterEditor extends Component<ChapterEditorProps, ChapterEditorState> {
|
||||
ref;
|
||||
#editorContainerRef = createRef<HTMLDivElement>();
|
||||
#resizeObserver?: ResizeObserver;
|
||||
db?: Db;
|
||||
constructor(props: ChapterEditorProps) {
|
||||
super(props);
|
||||
@@ -51,6 +53,17 @@ export default class ChapterEditor extends Component<ChapterEditorProps, Chapter
|
||||
historyPanelOpen: false,
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
if (this.#editorContainerRef.current) {
|
||||
this.#resizeObserver = new ResizeObserver(() => {
|
||||
this.layout();
|
||||
});
|
||||
this.#resizeObserver.observe(this.#editorContainerRef.current);
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.#resizeObserver?.disconnect();
|
||||
}
|
||||
componentDidUpdate(prevProps: Readonly<ChapterEditorProps>, _prevState: Readonly<ChapterEditorState>, _snapshot?: unknown): void {
|
||||
if (prevProps.chapter.id !== this.props.chapter.id) {
|
||||
this.setState({
|
||||
@@ -208,7 +221,7 @@ export default class ChapterEditor extends Component<ChapterEditorProps, Chapter
|
||||
</Drawer>}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<div className={styles.editor}>
|
||||
<div ref={this.#editorContainerRef} className={styles.editor}>
|
||||
<MonacoEditor
|
||||
ref={this.ref}
|
||||
value={this.state.content}
|
||||
|
||||
@@ -128,9 +128,9 @@ export type ChapterInfo = {
|
||||
freeStatus: number;
|
||||
modifyTime: number;
|
||||
multiModal: number;
|
||||
next: number;
|
||||
next?: number;
|
||||
nextCcid: string;
|
||||
prev: number;
|
||||
prev?: number;
|
||||
prevCcid: string;
|
||||
seq: number;
|
||||
updateTime: string;
|
||||
|
||||
@@ -874,6 +874,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8"
|
||||
integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==
|
||||
|
||||
"@types/wicg-file-system-access@^2023.10.7":
|
||||
version "2023.10.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.7.tgz#fab9868dfe0743003be6cb8c37ae480c9ce8537b"
|
||||
integrity sha512-g49ijasEJvCd7ifmAY2D0wdEtt1xRjBbA33PJTiv8mKBr7DoMsPeISoJ8oQOTopSRi+FBWPpPW5ouDj2QPKtGA==
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "21.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
|
||||
|
||||
Reference in New Issue
Block a user