mirror of
https://github.com/lifegpc/bookdownload.git
synced 2026-07-08 01:31:31 +08:00
Add settings page
This commit is contained in:
48
build.js
48
build.js
@@ -7,13 +7,13 @@ const is_dev = process.argv.includes('--dev');
|
||||
const is_dbg = process.argv.includes('--debug');
|
||||
const no_source_map = process.argv.includes('--no-sourcemap');
|
||||
|
||||
function sourcemap() {
|
||||
function sourcemap(is_content_script = false) {
|
||||
if (no_source_map) return false;
|
||||
if (is_dev) return "inline";
|
||||
if (is_dev && is_content_script) return "inline";
|
||||
return true;
|
||||
}
|
||||
|
||||
async function build(name) {
|
||||
async function build(name, is_content_script = true) {
|
||||
return await esbuild.build({
|
||||
entryPoints: [`src/${name}.ts`],
|
||||
bundle: true,
|
||||
@@ -21,42 +21,36 @@ async function build(name) {
|
||||
outfile: `dist/${name}.js`,
|
||||
platform: 'browser',
|
||||
target: ['chrome100'],
|
||||
sourcemap: sourcemap(),
|
||||
sourcemap: sourcemap(is_content_script),
|
||||
})
|
||||
}
|
||||
|
||||
async function buildTsx(name) {
|
||||
async function buildTsx(names) {
|
||||
const entryPoints = [];
|
||||
for (const name of names) {
|
||||
entryPoints.push(`src/${name}.tsx`);
|
||||
}
|
||||
await esbuild.build({
|
||||
entryPoints: [`src/${name}.tsx`],
|
||||
entryPoints: entryPoints,
|
||||
bundle: true,
|
||||
minify: !is_dbg,
|
||||
outfile: `dist/${name}.js`,
|
||||
outdir: 'dist',
|
||||
platform: 'browser',
|
||||
target: ['chrome100'],
|
||||
sourcemap: sourcemap(),
|
||||
jsx: 'automatic',
|
||||
loader: { '.css': 'global-css', '.module.css': 'local-css' },
|
||||
splitting: true,
|
||||
format: 'esm',
|
||||
});
|
||||
|
||||
// 确保输出目录存在并写入同名 HTML
|
||||
fs.mkdirSync(path.dirname(`dist/${name}.html`), { recursive: true });
|
||||
const cssFile = `dist/${name}.css`;
|
||||
const cssLink = fs.existsSync(cssFile) ? `<link rel="stylesheet" href="./${name}.css"/>` : '';
|
||||
const html = `<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<title>${name}</title>
|
||||
${cssLink}
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./${name}.js"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
fs.writeFileSync(`dist/${name}.html`, html, 'utf8');
|
||||
for (const name of names) {
|
||||
const srcHtmlPath = path.join('src', `${name}.html`);
|
||||
const distHtmlPath = path.join('dist', `${name}.html`);
|
||||
fs.copyFileSync(srcHtmlPath, distHtmlPath);
|
||||
}
|
||||
}
|
||||
|
||||
fs.rmSync('dist', { recursive: true, force: true });
|
||||
fs.mkdirSync('dist', { recursive: true });
|
||||
await build('qdchapter');
|
||||
await buildTsx('popup');
|
||||
await buildTsx(['popup', 'settings']);
|
||||
|
||||
@@ -31,5 +31,9 @@
|
||||
],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
]
|
||||
],
|
||||
"options_ui": {
|
||||
"open_in_tab": true,
|
||||
"page": "dist/settings.html"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "bookdownload",
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.1.0",
|
||||
"@types/chrome": "^0.1.36",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
|
||||
30
src/config.ts
Normal file
30
src/config.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { loadConfig, saveConfig } from "./utils";
|
||||
|
||||
type QdConfigData = {
|
||||
AutoSaveChapter?: boolean;
|
||||
}
|
||||
|
||||
export class QdConfig {
|
||||
static STORAGE_KEY = 'qd_config';
|
||||
config?: QdConfigData;
|
||||
constructor() {
|
||||
}
|
||||
async init() {
|
||||
this.config = await loadConfig<QdConfigData>(QdConfig.STORAGE_KEY, {});
|
||||
}
|
||||
async save() {
|
||||
if (!this.config) {
|
||||
throw new Error('Config not initialized');
|
||||
}
|
||||
await saveConfig(QdConfig.STORAGE_KEY, this.config);
|
||||
}
|
||||
get AutoSaveChapter(): boolean {
|
||||
return this.config?.AutoSaveChapter ?? false;
|
||||
}
|
||||
set AutoSaveChapter(value: boolean) {
|
||||
if (!this.config) {
|
||||
throw new Error('Config not initialized');
|
||||
}
|
||||
this.config.AutoSaveChapter = value;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { Descriptions, Card, Typography, Tag, Space, Button } from 'antd';
|
||||
import type { BookInfo, ChapterInfo } from '../qdtypes';
|
||||
import { get_chapter_content, saveAsFile } from '../utils';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
|
||||
interface QdChapterInfoProps {
|
||||
bookInfo: BookInfo;
|
||||
@@ -127,7 +127,7 @@ export default function QdChapterInfo({ bookInfo, chapterInfo }: QdChapterInfoPr
|
||||
{/* 章节内容 */}
|
||||
<Card title="章节内容" size="small">
|
||||
{chapters.map((paragraph, index) => (
|
||||
<p key={index}>{paragraph}</p>
|
||||
<Paragraph key={index}>{paragraph}</Paragraph>
|
||||
))}
|
||||
</Card>
|
||||
</Space>
|
||||
|
||||
13
src/popup.html
Normal file
13
src/popup.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<title>扩展弹出页</title>
|
||||
<link rel="stylesheet" href="./popup.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./popup.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
src/settings.html
Normal file
13
src/settings.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<title>扩展设置</title>
|
||||
<!--<link rel="stylesheet" href="./settings.css"/>-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./settings.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
23
src/settings.tsx
Normal file
23
src/settings.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { Typography, Tabs } from "antd";
|
||||
import type { TabsProps } from 'antd';
|
||||
import QdSettings from "./settings/QdSettings";
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
const items: TabsProps['items'] = [
|
||||
{
|
||||
'key': '1',
|
||||
'label': `起点设置`,
|
||||
'children': <QdSettings />,
|
||||
}
|
||||
];
|
||||
|
||||
function Settings() {
|
||||
return (<>
|
||||
<Title >扩展设置</Title>
|
||||
<Tabs defaultActiveKey="1" items={items} />
|
||||
</>);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<Settings />);
|
||||
18
src/settings/QdSettings.tsx
Normal file
18
src/settings/QdSettings.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Typography, Switch, FloatButton, Affix, Button } from "antd";
|
||||
import { SaveTwoTone, SaveOutlined } from "@ant-design/icons";
|
||||
import { useState } from "react";
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
export default function QdSettings() {
|
||||
const [container, setContainer] = useState<HTMLElement | null>(null);
|
||||
return (
|
||||
<div ref={setContainer}>
|
||||
<Title level={2}>起点设置</Title>
|
||||
<Affix target={() => container}>
|
||||
<Button type="primary" icon={<SaveOutlined />}>保存设置</Button>
|
||||
</Affix>
|
||||
<FloatButton icon={<SaveTwoTone />} tooltip="保存设置" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
14
src/utils.ts
14
src/utils.ts
@@ -1,4 +1,4 @@
|
||||
import { UrlParams, SendMessage, Message } from "./types";
|
||||
import type { UrlParams, SendMessage, Message } from "./types";
|
||||
|
||||
export const QD_CHAPTER_URLPATH_REGEX = /^\/chapter\/(\d+)\/(\d+)\/?$/;
|
||||
|
||||
@@ -58,3 +58,15 @@ export function saveAsFile(filename: string, content: string, mimeType: string =
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export async function saveConfig(key: string, config: any): Promise<void> {
|
||||
return await chrome.storage.managed.set({ [key]: config });
|
||||
}
|
||||
|
||||
export async function loadConfig<T>(key: string, defaultValue: T): Promise<T> {
|
||||
const r = await chrome.storage.managed.get<{[key]: T | undefined}>(key);
|
||||
if (r[key] === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
return r[key];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user