mirror of
https://github.com/yeban8090/book-smith.git
synced 2026-07-22 05:46:23 +00:00
新增初始化毛坯导出功能
This commit is contained in:
parent
193d4c9045
commit
1383da9881
6 changed files with 788 additions and 39 deletions
188
package-lock.json
generated
188
package-lock.json
generated
|
|
@ -10,6 +10,7 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"docx": "^9.5.0",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -1028,6 +1029,12 @@
|
|||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
|
|
@ -1097,6 +1104,32 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/docx": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/docx/-/docx-9.5.0.tgz",
|
||||
"integrity": "sha512-WZggg9vVujFcTyyzfIVBBIxlCk51QvhLWl87wtI2zuBdz8C8C0mpRhEVwA2DZd7dXyY0AVejcEVDT9vn7Xm9FA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "^22.7.5",
|
||||
"hash.js": "^1.1.7",
|
||||
"jszip": "^3.10.1",
|
||||
"nanoid": "^5.1.3",
|
||||
"xml": "^1.0.1",
|
||||
"xml-js": "^1.6.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/docx/node_modules/@types/node": {
|
||||
"version": "22.15.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.27.tgz",
|
||||
"integrity": "sha512-5fF+eu5mwihV2BeVtX5vijhdaZOfkQTATrePEaXTcKqI16LhJ7gi2/Vhd9OZM0UojcdmiOCVg5rrax+i1MdoQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.17.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz",
|
||||
|
|
@ -1622,6 +1655,16 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/hash.js": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
||||
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
|
|
@ -1632,6 +1675,12 @@
|
|||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
|
|
@ -1678,9 +1727,7 @@
|
|||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
|
|
@ -1726,6 +1773,12 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
|
|
@ -1772,6 +1825,18 @@
|
|||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/jszip": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
||||
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
|
||||
"license": "(MIT OR GPL-3.0-or-later)",
|
||||
"dependencies": {
|
||||
"lie": "~3.3.0",
|
||||
"pako": "~1.0.2",
|
||||
"readable-stream": "~2.3.6",
|
||||
"setimmediate": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
|
@ -1798,6 +1863,15 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
|
|
@ -1847,6 +1921,12 @@
|
|||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
|
|
@ -1878,6 +1958,24 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz",
|
||||
"integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || >=20"
|
||||
}
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
|
|
@ -1965,6 +2063,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
|
|
@ -2046,6 +2150,12 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
|
@ -2078,6 +2188,21 @@
|
|||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/regexpp": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
|
||||
|
|
@ -2155,6 +2280,18 @@
|
|||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
|
|
@ -2168,6 +2305,12 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
|
@ -2203,6 +2346,15 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
|
|
@ -2346,6 +2498,12 @@
|
|||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
|
|
@ -2357,6 +2515,12 @@
|
|||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||
|
|
@ -2414,6 +2578,24 @@
|
|||
"license": "ISC",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/xml": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
||||
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xml-js": {
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
|
||||
"integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"bin": {
|
||||
"xml-js": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"docx": "^9.5.0",
|
||||
"uuid": "^11.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -260,8 +260,8 @@ export class ChapterTree {
|
|||
// 只有当文件不排除之外时,才显示"标记完成章节"选项
|
||||
if (!node.exclude) {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(node.default_status === 'done' ?
|
||||
i18n.t('MARK_AS_DRAFT') :
|
||||
item.setTitle(node.default_status === 'done' ?
|
||||
i18n.t('MARK_AS_DRAFT') :
|
||||
i18n.t('MARK_AS_COMPLETE'));
|
||||
item.setIcon(node.default_status === 'done' ? "x-circle" : "check-circle");
|
||||
item.onClick(async () => {
|
||||
|
|
@ -284,16 +284,30 @@ export class ChapterTree {
|
|||
|
||||
// 添加排除选项
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(node.exclude ?
|
||||
i18n.t('INCLUDE_IN_STATS') :
|
||||
item.setTitle(node.exclude ?
|
||||
i18n.t('INCLUDE_IN_STATS') :
|
||||
i18n.t('EXCLUDE_FROM_STATS'));
|
||||
item.setIcon(node.exclude ? "plus-circle" : "minus-circle");
|
||||
// 修改排除选项的onClick处理函数
|
||||
item.onClick(async () => {
|
||||
node.exclude = !node.exclude;
|
||||
|
||||
// 无论是包含还是排除,都设置为草稿状态
|
||||
node.default_status = 'draft';
|
||||
|
||||
// 如果是文件夹,并且被排除,则递归排除所有子节点
|
||||
if (node.type === 'group' && node.exclude && node.children?.length) {
|
||||
this.recursiveExclude(node.children, true);
|
||||
}
|
||||
|
||||
// 如果是文件夹,并且被包含,则递归包含所有子节点
|
||||
if (node.type === 'group' && !node.exclude && node.children?.length) {
|
||||
this.recursiveExclude(node.children, false);
|
||||
}
|
||||
|
||||
// 更新父目录的排除状态
|
||||
this.updateParentExcludeStatus(node);
|
||||
|
||||
// 计算新的章节进度
|
||||
const progress = this.calculateChapterProgress(this.book.structure.tree);
|
||||
// 更新节点的状态并保存
|
||||
|
|
@ -409,8 +423,8 @@ export class ChapterTree {
|
|||
item.setTitle(i18n.t('DELETE'));
|
||||
item.setIcon("trash");
|
||||
item.onClick(() => {
|
||||
const title = node.type === 'file' ?
|
||||
i18n.t('DELETE_FILE_TITLE') :
|
||||
const title = node.type === 'file' ?
|
||||
i18n.t('DELETE_FILE_TITLE') :
|
||||
i18n.t('DELETE_FOLDER_TITLE');
|
||||
const message = node.type === 'file'
|
||||
? i18n.t('DELETE_FILE_DESC', { title: node.title })
|
||||
|
|
@ -439,6 +453,49 @@ export class ChapterTree {
|
|||
});
|
||||
}
|
||||
|
||||
// 添加一个新方法来更新父目录的排除状态
|
||||
private updateParentExcludeStatus(node: ChapterNode) {
|
||||
// 找到当前节点的父节点
|
||||
const findParentNode = (searchNode: ChapterNode, nodes: ChapterNode[]): ChapterNode | null => {
|
||||
for (const n of nodes) {
|
||||
if (n.children?.some(child => child.id === searchNode.id)) {
|
||||
return n;
|
||||
}
|
||||
if (n.children?.length) {
|
||||
const parent = findParentNode(searchNode, n.children);
|
||||
if (parent) return parent;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const parent = findParentNode(node, this.book.structure.tree);
|
||||
if (parent && parent.type === 'group') {
|
||||
// 检查父目录的所有子节点是否都被排除
|
||||
const allExcluded = parent.children?.every(child =>
|
||||
child.exclude || (child.type === 'group' && this.isFolderExcluded(child))
|
||||
);
|
||||
|
||||
// 更新父目录的排除状态
|
||||
if (allExcluded !== parent.exclude) {
|
||||
parent.exclude = allExcluded;
|
||||
|
||||
// 递归更新上层目录
|
||||
this.updateParentExcludeStatus(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加递归排除/包含子节点的方法
|
||||
private recursiveExclude(nodes: ChapterNode[], exclude: boolean) {
|
||||
for (const node of nodes) {
|
||||
node.exclude = exclude;
|
||||
if (node.type === 'group' && node.children?.length) {
|
||||
this.recursiveExclude(node.children, exclude);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === 拖拽相关 ===
|
||||
private setupDragAndDrop(header: HTMLElement, item: HTMLElement, node: ChapterNode) {
|
||||
header.setAttribute('draggable', 'true');
|
||||
|
|
|
|||
|
|
@ -1,8 +1,27 @@
|
|||
import { App, Notice } from "obsidian";
|
||||
import { BaseModal } from "./BaseModal";
|
||||
import { BookManager } from "../services/BookManager";
|
||||
import { Book } from "../types/book";
|
||||
import { ExportService } from "../services/ExportService";
|
||||
import { i18n } from "../i18n/i18n";
|
||||
|
||||
export class EbookModal extends BaseModal {
|
||||
constructor(container: HTMLElement) {
|
||||
private bookManager: BookManager;
|
||||
private exportService: ExportService;
|
||||
private books: Book[] = [];
|
||||
private selectedBook: Book | null = null;
|
||||
private selectedFormat: string = 'txt';
|
||||
|
||||
constructor(container: HTMLElement, private app: App, private settings: any) {
|
||||
super(container, '生成电子书');
|
||||
this.bookManager = new BookManager(app, settings);
|
||||
this.exportService = new ExportService(app, settings);
|
||||
}
|
||||
|
||||
async open() {
|
||||
// 加载所有书籍
|
||||
this.books = await this.bookManager.getAllBooks();
|
||||
super.open();
|
||||
}
|
||||
|
||||
protected createContent() {
|
||||
|
|
@ -11,50 +30,106 @@ export class EbookModal extends BaseModal {
|
|||
// 电子书信息表单
|
||||
const form = content.createDiv({ cls: 'ebook-form' });
|
||||
|
||||
// 书名
|
||||
this.createFormField(form, '书名', 'title', 'text', '请输入书名');
|
||||
|
||||
// 作者
|
||||
this.createFormField(form, '作者', 'author', 'text', '请输入作者名');
|
||||
|
||||
// 封面
|
||||
const coverField = this.createFormField(form, '封面', 'cover', 'file');
|
||||
const fileInput = coverField.querySelector('input');
|
||||
if (fileInput) {
|
||||
const fileButton = coverField.createDiv({ cls: 'file-select-button', text: '选择文件' });
|
||||
const fileLabel = coverField.createDiv({ cls: 'file-label', text: '未选择任何文件' });
|
||||
|
||||
fileButton.addEventListener('click', () => {
|
||||
fileInput.click();
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', () => {
|
||||
if (fileInput.files && fileInput.files.length > 0) {
|
||||
fileLabel.setText(fileInput.files[0].name);
|
||||
} else {
|
||||
fileLabel.setText('未选择任何文件');
|
||||
}
|
||||
});
|
||||
}
|
||||
// 书籍选择
|
||||
this.createBookSelector(form);
|
||||
|
||||
// 格式选择
|
||||
const formatField = this.createFormField(form, '格式', 'format', 'select');
|
||||
const formatSelect = formatField.querySelector('select');
|
||||
if (formatSelect) {
|
||||
this.createSelectOption(formatSelect, 'pdf', 'PDF');
|
||||
// 添加支持的格式
|
||||
this.createSelectOption(formatSelect, 'txt', 'TXT');
|
||||
this.createSelectOption(formatSelect, 'docx', 'DOCX');
|
||||
this.createSelectOption(formatSelect, 'pdf', 'PDF');
|
||||
this.createSelectOption(formatSelect, 'epub', 'EPUB');
|
||||
this.createSelectOption(formatSelect, 'mobi', 'MOBI');
|
||||
|
||||
formatSelect.addEventListener('change', (e) => {
|
||||
this.selectedFormat = (e.target as HTMLSelectElement).value;
|
||||
});
|
||||
}
|
||||
|
||||
// 生成按钮
|
||||
const generateBtn = content.createDiv({ cls: 'generate-btn', text: '生成电子书' });
|
||||
generateBtn.addEventListener('click', () => {
|
||||
this.showNotice('电子书生成功能开发中,敬请期待!');
|
||||
this.exportBook();
|
||||
});
|
||||
}
|
||||
|
||||
private createBookSelector(container: HTMLElement) {
|
||||
const field = this.createFormField(container, '选择书籍', 'book', 'select');
|
||||
const select = field.querySelector('select');
|
||||
|
||||
if (select && this.books.length > 0) {
|
||||
// 添加书籍选项
|
||||
for (const book of this.books) {
|
||||
this.createSelectOption(select, book.basic.uuid, book.basic.title);
|
||||
}
|
||||
|
||||
// 默认选择第一本书
|
||||
this.selectedBook = this.books[0];
|
||||
|
||||
// 监听选择变化
|
||||
select.addEventListener('change', (e) => {
|
||||
const uuid = (e.target as HTMLSelectElement).value;
|
||||
this.selectedBook = this.books.find(book => book.basic.uuid === uuid) || null;
|
||||
});
|
||||
} else if (select) {
|
||||
// 没有书籍时显示提示
|
||||
this.createSelectOption(select, '', '没有可用的书籍');
|
||||
select.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async exportBook() {
|
||||
if (!this.selectedBook) {
|
||||
this.showNotice('请先选择一本书籍');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用导出服务生成内容
|
||||
const content = await this.exportService.exportBook(
|
||||
this.selectedFormat,
|
||||
this.selectedBook
|
||||
);
|
||||
|
||||
// 如果导出功能尚未实现,显示提示
|
||||
if (content.content.startsWith(`${this.selectedFormat.toUpperCase()}导出功能开发中`)) {
|
||||
this.showNotice(`${this.selectedFormat.toUpperCase()}导出功能尚在开发中,敬请期待!`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据格式处理下载
|
||||
if (this.selectedFormat === 'docx') {
|
||||
// 对于DOCX,内容是base64字符串
|
||||
const dataUrl = content.content;
|
||||
const a = document.createElement('a');
|
||||
a.href = dataUrl;
|
||||
a.download = content.fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
} else {
|
||||
// 对于其他格式(如TXT),使用Blob
|
||||
const blob = new Blob([content.content], { type: 'text/plain' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = content.fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
this.showNotice(`导出成功!`);
|
||||
this.close();
|
||||
} catch (error) {
|
||||
console.error('导出错误:', error);
|
||||
this.showNotice(`导出失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private createFormField(container: HTMLElement, label: string, id: string, type: string, placeholder?: string) {
|
||||
const field = container.createDiv({ cls: 'form-field' });
|
||||
field.createDiv({ cls: 'field-label', text: label });
|
||||
|
|
|
|||
434
src/services/ExportService.ts
Normal file
434
src/services/ExportService.ts
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
import { App, TFile, TFolder } from 'obsidian';
|
||||
import { Book, ChapterNode } from '../types/book';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
// 导出格式接口
|
||||
export interface ExportStrategy {
|
||||
export(app: App, book: Book, selectedChapters?: ChapterNode[]): Promise<string>;
|
||||
}
|
||||
|
||||
// TXT导出策略 - 实际实现
|
||||
export class TxtExportStrategy implements ExportStrategy {
|
||||
private rootPath: string;
|
||||
constructor(rootPath: string) {
|
||||
this.rootPath = rootPath;
|
||||
}
|
||||
async export(app: App, book: Book, selectedChapters?: ChapterNode[]): Promise<string> {
|
||||
try {
|
||||
const content = await this.generateContent(app, book, selectedChapters);
|
||||
return content;
|
||||
} catch (error) {
|
||||
console.error('TXT导出错误:', error);
|
||||
throw new Error(`TXT导出失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async generateContent(app: App, book: Book, selectedChapters?: ChapterNode[]): Promise<string> {
|
||||
let content = `${book.basic.title}\n`;
|
||||
if (book.basic.subtitle) {
|
||||
content += `${book.basic.subtitle}\n`;
|
||||
}
|
||||
content += `作者: ${book.basic.author.join(', ')}\n\n`;
|
||||
|
||||
if (book.basic.desc) {
|
||||
content += `${book.basic.desc}\n\n`;
|
||||
}
|
||||
|
||||
const chapters = selectedChapters || book.structure.tree;
|
||||
content += await this.processChapters(app, book, chapters);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private async processChapters(app: App, book: Book, chapters: ChapterNode[], level: number = 0): Promise<string> {
|
||||
let content = '';
|
||||
for (const chapter of chapters) {
|
||||
if (chapter.exclude) continue;
|
||||
|
||||
// 添加章节标题
|
||||
content += `${chapter.title}\n\n`;
|
||||
if (chapter.type === 'file') {
|
||||
// 读取文件内容
|
||||
const filePath = `${this.rootPath}/${book?.basic.title}/${chapter.path}`;
|
||||
const file = app.vault.getAbstractFileByPath(filePath);
|
||||
if (file instanceof TFile) {
|
||||
const fileContent = await app.vault.read(file);
|
||||
// 处理Markdown语法,转换为纯文本
|
||||
const plainTextContent = this.convertMarkdownToPlainText(fileContent);
|
||||
content += `${plainTextContent}\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理子章节
|
||||
if (chapter.children && chapter.children.length > 0) {
|
||||
content += await this.processChapters(app, book, chapter.children, level + 1);
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Markdown格式转换为纯文本
|
||||
* @param markdown Markdown格式的文本
|
||||
* @returns 转换后的纯文本
|
||||
*/
|
||||
private convertMarkdownToPlainText(markdown: string): string {
|
||||
let plainText = markdown;
|
||||
|
||||
// 移除标题格式 (# 标题)
|
||||
plainText = plainText.replace(/^#{1,6}\s+(.+)$/gm, '$1');
|
||||
|
||||
// 移除加粗和斜体 (**文本** 或 *文本*)
|
||||
plainText = plainText.replace(/\*\*(.+?)\*\*/g, '$1');
|
||||
plainText = plainText.replace(/\*(.+?)\*/g, '$1');
|
||||
plainText = plainText.replace(/__(.+?)__/g, '$1');
|
||||
plainText = plainText.replace(/_(.+?)_/g, '$1');
|
||||
|
||||
// 移除双链接 [[链接]] 或 [[链接|显示文本]]
|
||||
plainText = plainText.replace(/\[\[([^\|\]]+)\|([^\]]+)\]\]/g, '$2');
|
||||
plainText = plainText.replace(/\[\[([^\]]+)\]\]/g, '$1');
|
||||
|
||||
// 移除普通链接 [显示文本](链接)
|
||||
plainText = plainText.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1');
|
||||
|
||||
// 移除代码块
|
||||
plainText = plainText.replace(/```[\s\S]*?```/g, '');
|
||||
|
||||
// 移除行内代码
|
||||
plainText = plainText.replace(/`([^`]+)`/g, '$1');
|
||||
|
||||
// 移除引用块
|
||||
plainText = plainText.replace(/^>\s+(.+)$/gm, '$1');
|
||||
|
||||
// 移除水平分割线
|
||||
plainText = plainText.replace(/^-{3,}|^\*{3,}|^_{3,}/gm, '');
|
||||
|
||||
// 移除任务列表标记 [x] 或 [ ]
|
||||
plainText = plainText.replace(/^\s*- \[[x\s]\]\s+(.+)$/gm, '- $1');
|
||||
|
||||
// 处理HTML标签
|
||||
plainText = plainText.replace(/<[^>]+>/g, '');
|
||||
|
||||
// 处理转义字符
|
||||
plainText = plainText.replace(/\\([\*_\[\]\(\)\`\#\+\-\.\!\\])/g, '$1');
|
||||
|
||||
// 移除多余的空行(连续两个以上的换行符替换为两个)
|
||||
plainText = plainText.replace(/\n{3,}/g, '\n\n');
|
||||
|
||||
return plainText;
|
||||
}
|
||||
}
|
||||
|
||||
// 其他导出策略 - 返回提示信息
|
||||
// DOCX导出策略 - 实际实现
|
||||
export class DocxExportStrategy implements ExportStrategy {
|
||||
private rootPath: string;
|
||||
constructor(rootPath: string) {
|
||||
this.rootPath = rootPath;
|
||||
}
|
||||
|
||||
async export(app: App, book: Book, selectedChapters?: ChapterNode[]): Promise<string> {
|
||||
try {
|
||||
const docxBlob = await this.generateDocx(app, book, selectedChapters);
|
||||
// 将Blob转换为base64字符串
|
||||
return await this.blobToBase64(docxBlob);
|
||||
} catch (error) {
|
||||
console.error('DOCX导出错误:', error);
|
||||
throw new Error(`DOCX导出失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async generateDocx(app: App, book: Book, selectedChapters?: ChapterNode[]): Promise<Blob> {
|
||||
// 导入docx库
|
||||
const { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType, BorderStyle } = await import('docx');
|
||||
|
||||
// 文档内容
|
||||
const children = [];
|
||||
|
||||
// 添加标题
|
||||
children.push(
|
||||
new Paragraph({
|
||||
text: book.basic.title,
|
||||
heading: HeadingLevel.TITLE,
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { after: 200 }
|
||||
})
|
||||
);
|
||||
|
||||
// 添加副标题
|
||||
if (book.basic.subtitle) {
|
||||
children.push(
|
||||
new Paragraph({
|
||||
text: book.basic.subtitle,
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { after: 200 }
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// 添加作者
|
||||
children.push(
|
||||
new Paragraph({
|
||||
text: `作者: ${book.basic.author.join(', ')}`,
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { after: 400 }
|
||||
})
|
||||
);
|
||||
|
||||
// 添加描述
|
||||
if (book.basic.desc) {
|
||||
children.push(
|
||||
new Paragraph({
|
||||
text: book.basic.desc,
|
||||
alignment: AlignmentType.JUSTIFIED,
|
||||
spacing: { after: 400 }
|
||||
})
|
||||
);
|
||||
}
|
||||
// 处理章节
|
||||
const chapters = selectedChapters || book.structure.tree;
|
||||
const chapterParagraphs = await this.processChapters(app, book, chapters);
|
||||
children.push(...chapterParagraphs);
|
||||
|
||||
// 创建文档并设置内容
|
||||
const doc = new Document({
|
||||
title: book.basic.title,
|
||||
description: book.basic.desc || '',
|
||||
creator: book.basic.author.join(', '),
|
||||
sections: [
|
||||
{
|
||||
children: children
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 生成DOCX文件
|
||||
return await Packer.toBlob(doc);
|
||||
}
|
||||
|
||||
private async processChapters(app: App, book: Book, chapters: ChapterNode[], level: number = 1): Promise<any[]> {
|
||||
const { Paragraph, TextRun, HeadingLevel } = await import('docx');
|
||||
const paragraphs = [];
|
||||
|
||||
for (const chapter of chapters) {
|
||||
if (chapter.exclude) continue;
|
||||
|
||||
// 添加章节标题
|
||||
const headingLevel = level <= 6 ? level : 6; // 最多支持6级标题
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
text: chapter.title,
|
||||
heading: HeadingLevel[`HEADING_${headingLevel}` as keyof typeof HeadingLevel],
|
||||
spacing: { before: 300, after: 200 }
|
||||
})
|
||||
);
|
||||
|
||||
if (chapter.type === 'file') {
|
||||
// 读取文件内容
|
||||
const filePath = `${this.rootPath}/${book?.basic.title}/${chapter.path}`;
|
||||
const file = app.vault.getAbstractFileByPath(filePath);
|
||||
if (file instanceof TFile) {
|
||||
const fileContent = await app.vault.read(file);
|
||||
// 将Markdown转换为DOCX段落
|
||||
const contentParagraphs = this.convertMarkdownToDocx(fileContent);
|
||||
// 等待Promise解析后再添加段落
|
||||
const resolvedParagraphs = await Promise.resolve(contentParagraphs);
|
||||
paragraphs.push(...resolvedParagraphs);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理子章节
|
||||
if (chapter.children && chapter.children.length > 0) {
|
||||
const childParagraphs = await this.processChapters(app, book, chapter.children, level + 1);
|
||||
paragraphs.push(...childParagraphs);
|
||||
}
|
||||
}
|
||||
|
||||
return paragraphs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Markdown格式转换为DOCX段落
|
||||
* @param markdown Markdown格式的文本
|
||||
* @returns DOCX段落数组
|
||||
*/
|
||||
private async convertMarkdownToDocx(markdown: string): Promise<any[]> {
|
||||
const { Paragraph, TextRun, HeadingLevel, AlignmentType } = await import('docx');
|
||||
const paragraphs = [];
|
||||
|
||||
// 按行分割Markdown
|
||||
const lines = markdown.split('\n');
|
||||
let inCodeBlock = false;
|
||||
let codeBlockContent = '';
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
// 处理代码块
|
||||
if (line.startsWith('```')) {
|
||||
if (inCodeBlock) {
|
||||
// 结束代码块
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
text: codeBlockContent,
|
||||
spacing: { before: 200, after: 200 },
|
||||
indent: { left: 720 }, // 缩进
|
||||
})
|
||||
);
|
||||
codeBlockContent = '';
|
||||
inCodeBlock = false;
|
||||
} else {
|
||||
// 开始代码块
|
||||
inCodeBlock = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inCodeBlock) {
|
||||
codeBlockContent += line + '\n';
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理标题
|
||||
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
||||
if (headingMatch) {
|
||||
const level = headingMatch[1].length;
|
||||
const headingText = headingMatch[2];
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
text: headingText,
|
||||
heading: HeadingLevel[`HEADING_${level}` as keyof typeof HeadingLevel],
|
||||
spacing: { before: 200, after: 120 }
|
||||
})
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理普通段落
|
||||
if (line.trim() !== '') {
|
||||
// 处理加粗、斜体等格式
|
||||
let processedLine = line;
|
||||
const textRuns = [];
|
||||
|
||||
// 简单处理加粗和斜体
|
||||
// 注意:这是一个简化的处理,实际上需要更复杂的解析
|
||||
const boldItalicRegex = /\*\*\*(.+?)\*\*\*/g;
|
||||
const boldRegex = /\*\*(.+?)\*\*/g;
|
||||
const italicRegex = /\*(.+?)\*/g;
|
||||
|
||||
// 替换加粗斜体
|
||||
processedLine = processedLine.replace(boldItalicRegex, (match, p1) => {
|
||||
textRuns.push(new TextRun({ text: p1, bold: true, italics: true }));
|
||||
return '';
|
||||
});
|
||||
|
||||
// 替换加粗
|
||||
processedLine = processedLine.replace(boldRegex, (match, p1) => {
|
||||
textRuns.push(new TextRun({ text: p1, bold: true }));
|
||||
return '';
|
||||
});
|
||||
|
||||
// 替换斜体
|
||||
processedLine = processedLine.replace(italicRegex, (match, p1) => {
|
||||
textRuns.push(new TextRun({ text: p1, italics: true }));
|
||||
return '';
|
||||
});
|
||||
|
||||
// 如果有剩余文本,添加为普通文本
|
||||
if (processedLine.trim() !== '') {
|
||||
textRuns.push(new TextRun({ text: processedLine }));
|
||||
}
|
||||
|
||||
// 如果没有特殊格式,直接添加为普通段落
|
||||
if (textRuns.length === 0) {
|
||||
paragraphs.push(new Paragraph({ text: line }));
|
||||
} else {
|
||||
paragraphs.push(new Paragraph({ children: textRuns }));
|
||||
}
|
||||
} else if (i > 0 && lines[i - 1].trim() !== '') {
|
||||
// 空行,但前一行不是空行,添加段落间距
|
||||
paragraphs.push(new Paragraph({ text: '', spacing: { after: 120 } }));
|
||||
}
|
||||
}
|
||||
|
||||
return paragraphs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Blob转换为base64字符串
|
||||
* @param blob Blob对象
|
||||
* @returns base64字符串
|
||||
*/
|
||||
private async blobToBase64(blob: Blob): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
if (typeof reader.result === 'string') {
|
||||
resolve(reader.result);
|
||||
} else {
|
||||
reject(new Error('转换失败'));
|
||||
}
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class PdfExportStrategy implements ExportStrategy {
|
||||
private rootPath: string;
|
||||
constructor(rootPath: string) {
|
||||
this.rootPath = rootPath;
|
||||
}
|
||||
|
||||
async export(app: App, book: Book, selectedChapters?: ChapterNode[]): Promise<string> {
|
||||
return `PDF导出功能开发中 - ${book.basic.title}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class EpubExportStrategy implements ExportStrategy {
|
||||
private rootPath: string;
|
||||
constructor(rootPath: string) {
|
||||
this.rootPath = rootPath;
|
||||
}
|
||||
|
||||
async export(app: App, book: Book, selectedChapters?: ChapterNode[]): Promise<string> {
|
||||
return `EPUB导出功能开发中 - ${book.basic.title}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出服务类
|
||||
export class ExportService {
|
||||
|
||||
private rootPath: string;
|
||||
|
||||
private strategies: Record<string, ExportStrategy>;
|
||||
|
||||
constructor(private app: App, settings: any) {
|
||||
this.rootPath = settings.defaultBookPath;
|
||||
this.strategies = {
|
||||
'txt': new TxtExportStrategy(this.rootPath),
|
||||
'docx': new DocxExportStrategy(this.rootPath),
|
||||
'pdf': new PdfExportStrategy(this.rootPath),
|
||||
'epub': new EpubExportStrategy(this.rootPath)
|
||||
};
|
||||
}
|
||||
|
||||
async exportBook(format: string, book: Book, selectedChapters?: ChapterNode[]): Promise<{ content: string, fileName: string }> {
|
||||
const strategy = this.strategies[format];
|
||||
if (!strategy) {
|
||||
throw new Error(`不支持的导出格式: ${format}`);
|
||||
}
|
||||
|
||||
const content = await strategy.export(this.app, book, selectedChapters);
|
||||
return {
|
||||
content: content,
|
||||
fileName: `${book.basic.title}.${format}`
|
||||
};
|
||||
}
|
||||
|
||||
getSupportedFormats(): string[] {
|
||||
return Object.keys(this.strategies);
|
||||
}
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ export class ToolView extends ItemView {
|
|||
{
|
||||
icon: 'book',
|
||||
text: i18n.t('GENERATE_EBOOK'),
|
||||
onClick: () => new EbookModal(this.containerEl).open()
|
||||
onClick: () => new EbookModal(this.containerEl, this.app, this.plugin.settings).open()
|
||||
},
|
||||
{
|
||||
icon: 'clock',
|
||||
|
|
|
|||
Loading…
Reference in a new issue