diff --git a/extension/inject.css b/extension/inject.css index e3bd63b..0c40338 100644 --- a/extension/inject.css +++ b/extension/inject.css @@ -1,6 +1,6 @@ #epubDialogContainer .drop-down-pop-up-container { - position: fixed; + position: absolute; top: 0; left: 0; z-index: 1001; @@ -11,3 +11,11 @@ #epubDialogContainer .drop-down-pop-up-container .drop-down-pop-up h1 { cursor: move; } + +#epubDialogContainer table.properties { + +} + +#epubDialogContainer table.properties td.label { + white-space: nowrap; +} diff --git a/src/FimFic2Epub.js b/src/FimFic2Epub.js index 47e98b2..c8f1434 100644 --- a/src/FimFic2Epub.js +++ b/src/FimFic2Epub.js @@ -80,67 +80,58 @@ module.exports = class FimFic2Epub { this.storyId = FimFic2Epub.getStoryId(storyId) this.hasDownloaded = false - this.isDownloading = false - this.zip = null + this.downloadPromise = null + + this.storyInfo = null + this.titlePage = null this.chapterContent = [] this.remoteResources = new Map() - this.storyInfo = null - this.isDownloading = false + this.cachedFile = null this.hasCoverImage = false this.coverImageDimensions = {width: 0, height: 0} this.includeTitlePage = true this.categories = [] this.tags = [] + + this.zip = new JSZip() } download () { - return new Promise((resolve, reject) => { - if (this.isDownloading) { - reject('Already downloading') - return - } - if (this.hasDownloaded) { - resolve() - return - } - this.build().then(resolve).catch(reject) - }) - } + if (this.downloadPromise) { + return this.downloadPromise + } - build () { - return new Promise((resolve, reject) => { - this.isDownloading = true + console.log('Fetching story metadata...') - this.zip = new JSZip() - this.zip.file('mimetype', 'application/epub+zip') - this.zip.file('META-INF/container.xml', containerXml) - - console.log('Fetching story metadata...') - - FimFic2Epub.fetchStoryInfo(this.storyId).then((storyInfo) => { + let p = FimFic2Epub.fetchStoryInfo(this.storyId) + .then((storyInfo) => { this.storyInfo = storyInfo this.storyInfo.uuid = 'urn:fimfiction:' + this.storyInfo.id this.filename = FimFic2Epub.getFilename(this.storyInfo) + }) + .then(this.fetchTitlePage.bind(this)) + .then(() => { + this.downloadPromise = null + }) - this.zip.file('OEBPS/Styles/style.css', styleCss) - this.zip.file('OEBPS/Styles/coverstyle.css', coverstyleCss) - if (this.includeTitlePage) { - this.zip.file('OEBPS/Styles/titlestyle.css', titlestyleCss) - } - - this.zip.file('OEBPS/toc.ncx', template.createNcx(this)) - - this.fetchTitlePage(resolve, reject) - }).catch(reject) - }) + this.downloadPromise = p + return p } - fetchTitlePage (resolve, reject) { + build () { + return this.extractTitlePageInfo(this.titlePage) + .then(this.checkCoverImage.bind(this)) + } + + fetchTitlePage () { console.log('Fetching index page...') - fetchRemote(this.storyInfo.url, (raw, type) => { - this.extractTitlePageInfo(raw).then(() => this.checkCoverImage(resolve, reject)) + return new Promise((resolve, reject) => { + fetchRemote(this.storyInfo.url, (raw, type) => { + this.titlePage = raw + resolve(raw) + }) }) } @@ -219,45 +210,48 @@ module.exports = class FimFic2Epub { }) } - checkCoverImage (resolve, reject) { - this.hasCoverImage = !!this.storyInfo.full_image + checkCoverImage () { + return new Promise((resolve, reject) => { + this.hasCoverImage = !!this.storyInfo.full_image - if (this.hasCoverImage) { - this.remoteResources.set(this.storyInfo.full_image, {filename: 'cover', where: ['cover']}) + if (this.hasCoverImage) { + this.remoteResources.set(this.storyInfo.full_image, {filename: 'cover', where: ['cover']}) - if (!isNode) { - let coverImage = new Image() - coverImage.src = this.storyInfo.full_image + if (!isNode) { + let coverImage = new Image() + coverImage.src = this.storyInfo.full_image - coverImage.addEventListener('load', () => { - this.coverImageDimensions.width = coverImage.width - this.coverImageDimensions.height = coverImage.height + coverImage.addEventListener('load', () => { + this.coverImageDimensions.width = coverImage.width + this.coverImageDimensions.height = coverImage.height + this.processStory(resolve, reject) + }, false) + coverImage.addEventListener('error', () => { + console.warn('Unable to fetch cover image, skipping...') + this.hasCoverImage = false + this.processStory(resolve, reject) + }) + } else { this.processStory(resolve, reject) - }, false) - coverImage.addEventListener('error', () => { - console.warn('Unable to fetch cover image, skipping...') - this.hasCoverImage = false - this.processStory(resolve, reject) - }) + } } else { this.processStory(resolve, reject) } - } else { - this.processStory(resolve, reject) - } + }) } processStory (resolve, reject) { console.log('Fetching chapters...') - this.fetchChapters(() => { - this.zip.file('OEBPS/Text/nav.xhtml', template.createNav(this)) - + this.fetchChapters().then(() => { console.log('Fetching remote files...') this.fetchRemoteFiles(() => { console.log('Finishing build...') + this.zip.file('mimetype', 'application/epub+zip') + this.zip.file('META-INF/container.xml', containerXml) + let coverFilename = '' this.remoteResources.forEach((r, url) => { let dest = '../' + r.dest @@ -287,6 +281,8 @@ module.exports = class FimFic2Epub { this.chapterContent.length = 0 + this.zip.file('OEBPS/content.opf', template.createOpf(this)) + if (this.hasCoverImage) { this.zip.file('OEBPS/Text/cover.xhtml', template.createCoverPage(coverFilename, this.coverImageDimensions.width, this.coverImageDimensions.height)) } else { @@ -297,9 +293,15 @@ module.exports = class FimFic2Epub { this.zip.file('OEBPS/Text/title.xhtml', template.createTitlePage(this)) } - this.zip.file('OEBPS/content.opf', template.createOpf(this)) + this.zip.file('OEBPS/Text/nav.xhtml', template.createNav(this)) + this.zip.file('OEBPS/toc.ncx', template.createNcx(this)) + + this.zip.file('OEBPS/Styles/style.css', styleCss) + this.zip.file('OEBPS/Styles/coverstyle.css', coverstyleCss) + if (this.includeTitlePage) { + this.zip.file('OEBPS/Styles/titlestyle.css', titlestyleCss) + } - this.isDownloading = false this.hasDownloaded = true resolve() }) @@ -353,44 +355,46 @@ module.exports = class FimFic2Epub { recursive() } - fetchChapters (cb) { - let chapters = this.storyInfo.chapters - let chapterCount = this.storyInfo.chapters.length - let currentChapter = 0 - let completeCount = 0 + fetchChapters () { + return new Promise((resolve, reject) => { + let chapters = this.storyInfo.chapters + let chapterCount = this.storyInfo.chapters.length + let currentChapter = 0 + let completeCount = 0 - if (chapterCount === 0) { - cb() - return - } - - let recursive = () => { - let index = currentChapter++ - let ch = chapters[index] - if (!ch) { + if (chapterCount === 0) { + resolve() return } - console.log('Fetching chapter ' + (index + 1) + ' of ' + chapters.length + ': ' + ch.title) - let url = ch.link.replace('http', 'https') - fetchRemote(url, (html) => { - template.createChapter(ch, html, (html) => { - this.findRemoteResources('ch_' + zeroFill(3, index + 1), index, html) - this.chapterContent[index] = html - completeCount++ - if (completeCount < chapterCount) { - recursive() - } else { - cb() - } - }) - }) - } - // concurrent downloads! - recursive() - recursive() - recursive() - recursive() + let recursive = () => { + let index = currentChapter++ + let ch = chapters[index] + if (!ch) { + return + } + console.log('Fetching chapter ' + (index + 1) + ' of ' + chapters.length + ': ' + ch.title) + let url = ch.link.replace('http', 'https') + fetchRemote(url, (html) => { + template.createChapter(ch, html, (html) => { + this.findRemoteResources('ch_' + zeroFill(3, index + 1), index, html) + this.chapterContent[index] = html + completeCount++ + if (completeCount < chapterCount) { + recursive() + } else { + resolve() + } + }) + }) + } + + // concurrent downloads! + recursive() + recursive() + recursive() + recursive() + }) } findRemoteResources (prefix, where, html) { diff --git a/src/main.js b/src/main.js index 99a785c..db7f6b8 100644 --- a/src/main.js +++ b/src/main.js @@ -36,22 +36,20 @@ let checkbox = { let dialog = { controller (args) { this.dragging = m.prop(false) - this.xpos = m.prop(100) - this.ypos = m.prop(100) + this.xpos = m.prop(0) + this.ypos = m.prop(0) this.el = m.prop(null) + this.progress = m.prop(0) this.ondown = (e) => { - let el = this.el().firstChild - let rect = el.getBoundingClientRect() - let offset = {x: e.pageX - rect.left, y: e.pageY - rect.top} + let rect = this.el().firstChild.getBoundingClientRect() + let offset = {x: e.pageX - rect.left - document.body.scrollLeft, y: e.pageY - rect.top - document.body.scrollTop} this.dragging(true) let onmove = (e) => { e.preventDefault() if (this.dragging()) { - let rect = el.getBoundingClientRect() - this.xpos(Math.max(0, Math.min(e.pageX - offset.x, window.innerWidth - rect.width))) - this.ypos(Math.max(0, Math.min(e.pageY - offset.y, window.innerHeight - rect.height))) - // console.log(e.pageX, e.pageY) - m.redraw() + this.xpos(Math.max(0, e.pageX - offset.x)) + this.ypos(Math.max(0, e.pageY - offset.y)) + this.move() } } let onup = () => { @@ -62,12 +60,48 @@ let dialog = { window.addEventListener('mousemove', onmove, false) window.addEventListener('mouseup', onup, false) } + this.onOpen = function (el, first) { + if (!first) { + this.el(el) + let rect = this.el().firstChild.getBoundingClientRect() + this.xpos((window.innerWidth / 2) - (rect.width / 2) + document.body.scrollLeft) + this.ypos((window.innerHeight / 2) - (rect.height / 2) + document.body.scrollTop) + this.move() + } + } + this.move = () => { + this.el().style.left = this.xpos() + 'px' + this.el().style.top = this.ypos() + 'px' + } + this.createEpub = (e) => { + e.target.disabled = true + ffc.download() + .then(ffc.build.bind(ffc)) + .then(ffc.getFile.bind(ffc)).then((file) => { + console.log('Saving file...') + if (typeof safari !== 'undefined') { + blobToDataURL(file, (dataurl) => { + document.location.href = dataurl + alert('Add .epub to the filename of the downloaded file') + }) + } else { + saveAs(file, ffc.filename) + } + }) + } }, view (ctrl, args, extras) { - return m('.drop-down-pop-up-container', {config: ctrl.el, style: {left: ctrl.xpos() + 'px', top: ctrl.ypos() + 'px'}}, m('.drop-down-pop-up', [ - m('h1', {onmousedown: ctrl.ondown}, m('i.fa.fa-book'), 'Export EPUB', m('a.close_button', {onclick: closeDialog})), + return m('.drop-down-pop-up-container', {config: ctrl.onOpen.bind(ctrl)}, m('.drop-down-pop-up', [ + m('h1', {onmousedown: ctrl.ondown}, m('i.fa.fa-book'), 'Export to EPUB', m('a.close_button', {onclick: closeDialog})), m('.drop-down-pop-up-content', [ - m(checkbox, {name: 'toggle-chapter-headings'}, 'Toggle chapter headings') + m('table.properties', [ + m('tr', m('td.label', 'Cover image'), m('td', 'some config')), + m('tr', m('td.label', 'Chapter headings'), m('td', m(checkbox, {name: 'toggle-chapter-headings'}))) + ]), + m('.drop-down-pop-up-footer', [ + m('button.styled_button', {onclick: ctrl.createEpub}, 'Create EPUB'), + ctrl.progress() > 0 ? m('.rating_container', m('.bars_container', m('.bar_container', m('.bar_dislike', m('.bar.bar_like', {style: {width: ctrl.progress() * 100 + '%'}}))))) : null + ]) ]) ])) } @@ -85,20 +119,6 @@ function clickButton () { if (!ffc) ffc = new FimFic2Epub(STORY_ID) openDialog() - - return - - ffc.download().then(ffc.getFile.bind(ffc)).then((file) => { - console.log('Saving file...') - if (typeof safari !== 'undefined') { - blobToDataURL(file, (dataurl) => { - document.location.href = dataurl - alert('Add .epub to the filename of the downloaded file') - }) - } else { - saveAs(file, ffc.filename) - } - }) } if (epubButton) {