Make Sure Editor Resize

This commit is contained in:
2026-03-03 17:09:54 +08:00
parent 7e60e2ecbb
commit e898f898f5
11 changed files with 223 additions and 5 deletions

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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(),

View File

@@ -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',

View 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 />
}
],
}

View File

@@ -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}>

View 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 />}
</>);
}

View File

@@ -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}

View File

@@ -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;

View File

@@ -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"