diff --git a/README.md b/README.md index c88fc24..10bf810 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,9 @@ const ebook = await Epubook.create({ description: 'something' }) -await ebook.cover('./assets/cover.jpg') +const cover = await ebook.cover('./assets/cover.jpg') + +ebook.toc(cover) await ebook.writeFile('./output.epub') ``` diff --git a/packages/core/src/xhtml/builder.ts b/packages/core/src/xhtml/builder.ts index 0190dae..a3a06a7 100644 --- a/packages/core/src/xhtml/builder.ts +++ b/packages/core/src/xhtml/builder.ts @@ -14,33 +14,41 @@ const builder = new XMLBuilder({ unpairedTags: ['link'] }); -interface HTMLMeta { +export interface HTMLMeta { language: string; title: string; } export class XHTML extends Item { - private meta: HTMLMeta; + private _meta: HTMLMeta; - private content: string; + private _content: string; public constructor(file: string, meta: HTMLMeta, content: string) { super(file, TextXHTML); - this.meta = meta; - this.content = content; + this._meta = meta; + this._content = content; + } + + public meta() { + return this._meta; } public title() { - return this.meta.title; + return this._meta.title; } public language() { - return this.meta.language; + return this._meta.language; + } + + public content() { + return this._content; } public async bundle(): Promise { // TODO: check encode format - return strToU8(this.content); + return strToU8(this._content); } } diff --git a/packages/core/test/bundle.test.ts b/packages/core/test/bundle.test.ts index c5addae..c03628b 100644 --- a/packages/core/test/bundle.test.ts +++ b/packages/core/test/bundle.test.ts @@ -150,20 +150,20 @@ describe('XHTML Builder', () => { const res = builder.build(); expect(res).toMatchInlineSnapshot(` XHTML { - "_properties": undefined, - "content": " + "_content": " a.xhtml ", - "file": "a.xhtml", - "mediaType": "application/xhtml+xml", - "meta": { + "_meta": { "language": "en", "title": "a.xhtml", }, + "_properties": undefined, + "file": "a.xhtml", + "mediaType": "application/xhtml+xml", } `); }); @@ -173,8 +173,7 @@ describe('XHTML Builder', () => { const res = builder.title('with style').style('123').style('456').build(); expect(res).toMatchInlineSnapshot(` XHTML { - "_properties": undefined, - "content": " + "_content": " with style @@ -183,12 +182,13 @@ describe('XHTML Builder', () => { ", - "file": "a.xhtml", - "mediaType": "application/xhtml+xml", - "meta": { + "_meta": { "language": "en", "title": "with style", }, + "_properties": undefined, + "file": "a.xhtml", + "mediaType": "application/xhtml+xml", } `); }); @@ -208,8 +208,7 @@ describe('XHTML Builder', () => { expect(res).toMatchInlineSnapshot(` XHTML { - "_properties": undefined, - "content": " + "_content": " Nav @@ -236,12 +235,13 @@ describe('XHTML Builder', () => { ", - "file": "nav.xhtml", - "mediaType": "application/xhtml+xml", - "meta": { + "_meta": { "language": "en", "title": "Nav", }, + "_properties": undefined, + "file": "nav.xhtml", + "mediaType": "application/xhtml+xml", } `); }); diff --git a/packages/epubook/README.md b/packages/epubook/README.md index c88fc24..10bf810 100644 --- a/packages/epubook/README.md +++ b/packages/epubook/README.md @@ -27,7 +27,9 @@ const ebook = await Epubook.create({ description: 'something' }) -await ebook.cover('./assets/cover.jpg') +const cover = await ebook.cover('./assets/cover.jpg') + +ebook.toc(cover) await ebook.writeFile('./output.epub') ``` diff --git a/packages/epubook/src/epubook.ts b/packages/epubook/src/epubook.ts index 4bf3efa..cdcd354 100644 --- a/packages/epubook/src/epubook.ts +++ b/packages/epubook/src/epubook.ts @@ -13,6 +13,9 @@ import type { DefaultTheme } from '@epubook/theme-default'; import * as path from 'node:path'; +import { Cover } from './pages'; +import { EpubookError } from './error'; + export interface EpubookOption = Awaited>> { title: string; @@ -35,7 +38,13 @@ export class Epubook

= {}> { private theme!: Theme

; - private content: Array = []; + private _toc: XHTML | undefined; + + private _cover: XHTML | undefined; + + private _pages: Array = []; + + private _spine: Array = []; private counter: Record = { image: 1 @@ -107,9 +116,9 @@ export class Epubook

= {}> { return image; } - public async cover(img: string): Promise; - public async cover(img: Uint8Array, ext: ImageExtension): Promise; - public async cover(img: string | Uint8Array, ext?: ImageExtension) { + public async cover(img: string): Promise; + public async cover(img: Uint8Array, ext: ImageExtension): Promise; + public async cover(img: string | Uint8Array, ext?: ImageExtension): Promise { if (typeof img === 'string') { ext = path.extname(img).slice(1) as ImageExtension; } @@ -120,10 +129,11 @@ export class Epubook

= {}> { if (image) { image.update({ properties: 'cover-image' }); const page = this.page('cover', { image }, { file: `cover.xhtml` }); - this.content.unshift(page); - return image; + this._cover = page; + this._pages.unshift(page); + return Cover.from(image, page); } else { - return undefined; + throw new EpubookError('Can not load image'); } } @@ -145,17 +155,27 @@ export class Epubook

= {}> { return xhtml; } - public toc() { - return this; + public toc(...items: Array) { + this.container.toc( + items.map((i) => + i instanceof XHTML + ? { title: i.title(), page: i } + : { title: i.title, list: i.list.map((i) => ({ title: i.title(), page: i })) } + ) + ); + + const spine = items.flatMap((i) => (i instanceof XHTML ? [i] : i.list)); + this.spine(...spine); } - public spine() { + public spine(...items: Array) { + this._spine.splice(0, this._spine.length, ...items); return this; } private async preBundle() { - this.container.toc(this.content.map((c) => ({ title: c.title(), page: c }))); - this.container.spine(...this.content); + this.container.toc(this._pages.map((c) => ({ title: c.title(), page: c }))); + this.container.spine(...this._spine); } public async bundle() { diff --git a/packages/epubook/src/error.ts b/packages/epubook/src/error.ts new file mode 100644 index 0000000..1e652f1 --- /dev/null +++ b/packages/epubook/src/error.ts @@ -0,0 +1,5 @@ +export class EpubookError extends Error { + constructor(msg: string) { + super(msg); + } +} diff --git a/packages/epubook/src/pages/cover.ts b/packages/epubook/src/pages/cover.ts new file mode 100644 index 0000000..c4d6e3a --- /dev/null +++ b/packages/epubook/src/pages/cover.ts @@ -0,0 +1,15 @@ +import { Image, XHTML } from '@epubook/core'; + +export class Cover extends XHTML { + private _image!: Image; + + public static from(image: Image, xhtml: XHTML) { + const cover = new Cover(xhtml.filename(), xhtml.meta(), xhtml.content()); + cover._image = image; + return cover; + } + + public image() { + return this._image; + } +} diff --git a/packages/epubook/src/pages/index.ts b/packages/epubook/src/pages/index.ts new file mode 100644 index 0000000..e97c111 --- /dev/null +++ b/packages/epubook/src/pages/index.ts @@ -0,0 +1 @@ +export * from './cover'; diff --git a/packages/epubook/test/index.test.ts b/packages/epubook/test/index.test.ts index 626f12d..47190e5 100644 --- a/packages/epubook/test/index.test.ts +++ b/packages/epubook/test/index.test.ts @@ -9,7 +9,8 @@ describe('epubook', () => { description: 'This is generated for testing cover image' }); - await book.cover('../../assets/cover.jpg'); + const cover = await book.cover('../../assets/cover.jpg'); + book.toc(cover); await book.writeFile('../../.output/test-cover.epub'); });