tidying up!

This commit is contained in:
daniel-j 2016-08-23 22:04:38 +02:00
parent 3e4d08f646
commit 4ae5731d14
3 changed files with 118 additions and 71 deletions

View file

@ -6,7 +6,7 @@
z-index: 1001; z-index: 1001;
} }
#epubDialogContainer .drop-down-pop-up-container .drop-down-pop-up { #epubDialogContainer .drop-down-pop-up-container .drop-down-pop-up {
width: 500px; width: 600px;
} }
#epubDialogContainer .drop-down-pop-up-container .drop-down-pop-up h1 { #epubDialogContainer .drop-down-pop-up-container .drop-down-pop-up h1 {
cursor: move; cursor: move;
@ -16,10 +16,21 @@
} }
#epubDialogContainer table.properties textarea {
line-height: 1em;
font-size: 1em;
resize: vertical;
}
#epubDialogContainer table.properties td.label { #epubDialogContainer table.properties td.label {
white-space: nowrap; white-space: nowrap;
} }
#epubDialogContainer table.properties input:invalid {
background-repeat: no-repeat;
background-position: 8px center;
}
#epubDialogContainer div.rating_container { #epubDialogContainer div.rating_container {
padding-top: 4px; padding-top: 4px;
} }

View file

@ -21,7 +21,7 @@ import { containerXml } from './constants'
const entities = new XmlEntities() const entities = new XmlEntities()
module.exports = class FimFic2Epub extends Emitter { class FimFic2Epub extends Emitter {
static getStoryId (id) { static getStoryId (id) {
if (isNaN(id)) { if (isNaN(id)) {
@ -113,8 +113,10 @@ module.exports = class FimFic2Epub extends Emitter {
this.storyInfo = null this.storyInfo = null
this.description = '' this.description = ''
this.subjects = []
this.chapters = [] this.chapters = []
this.remoteResources = new Map() this.remoteResources = new Map()
this.coverUrl = ''
this.coverImage = null this.coverImage = null
this.coverFilename = '' this.coverFilename = ''
this.coverType = '' this.coverType = ''
@ -138,63 +140,54 @@ module.exports = class FimFic2Epub extends Emitter {
this.coverImageDimensions = sizeOf(new Buffer(buffer)) this.coverImageDimensions = sizeOf(new Buffer(buffer))
} }
fetchMetadata () {
this.storyInfo = null
this.description = ''
this.subjects.length = 0
return FimFic2Epub.fetchStoryInfo(this.storyId).then((storyInfo) => {
this.storyInfo = storyInfo
this.storyInfo.uuid = 'urn:fimfiction:' + this.storyInfo.id
this.filename = FimFic2Epub.getFilename(this.storyInfo)
this.progress(0, 0.3)
})
.then(this.fetchTitlePage.bind(this))
.then(() => cleanMarkup(this.description)).then((html) => {
this.storyInfo.description = html
this.findRemoteResources('description', 'description', html)
})
}
fetch () { fetch () {
if (this.fetchPromise) { if (this.fetchPromise) {
return this.fetchPromise return this.fetchPromise
} }
this.storyInfo = null
this.description = ''
this.chapters.length = 0 this.chapters.length = 0
this.remoteResources.clear() this.remoteResources.clear()
this.progress(0, 0, 'Fetching metadata...') this.progress(0, 0, 'Fetching...')
let p = this.fetchPromise = Promise.resolve()
FimFic2Epub.fetchStoryInfo(this.storyId).then((storyInfo) => { if (!this.storyInfo) {
this.storyInfo = storyInfo this.fetchPromise = this.fetchPromise.then(this.fetchMetadata.bind(this))
this.storyInfo.uuid = 'urn:fimfiction:' + this.storyInfo.id }
this.filename = FimFic2Epub.getFilename(this.storyInfo) this.fetchPromise = this.fetchPromise
this.progress(0, 0.3)
})
.then(this.fetchTitlePage.bind(this))
.then(() => cleanMarkup(this.description)).then((html) => {
this.storyInfo.description = html
this.findRemoteResources('description', 'description', html)
})
.then(this.fetchCoverImage.bind(this)) .then(this.fetchCoverImage.bind(this))
.then(this.fetchChapters.bind(this)) .then(this.fetchChapters.bind(this))
// .then(this.processChapters.bind(this))
.then(this.fetchRemoteFiles.bind(this)) .then(this.fetchRemoteFiles.bind(this))
.then(() => { .then(() => {
this.fetchPromise = null this.fetchPromise = null
}) })
this.fetchPromise = p return this.fetchPromise
return p
} }
build () { build () {
this.cachedFile = null this.cachedFile = null
this.zip = null this.zip = null
this.remoteResources.forEach((r, url) => { this.replaceRemoteResources()
let dest = '../' + r.dest
if (r.dest && r.originalUrl && r.where) {
let ourl = new RegExp(escapeStringRegexp(r.originalUrl), 'g')
for (var i = 0; i < r.where.length; i++) {
let w = r.where[i]
if (typeof w === 'number') {
this.chapters[w] = this.chapters[w].replace(ourl, dest)
} else if (w === 'description') {
this.storyInfo.description = this.storyInfo.description.replace(ourl, dest)
} else if (w === 'tags') {
this.tags.byImage[r.originalUrl].image = dest
}
}
}
})
this.zip = new JSZip() this.zip = new JSZip()
@ -226,8 +219,6 @@ module.exports = class FimFic2Epub extends Emitter {
this.remoteResources.forEach((r) => { this.remoteResources.forEach((r) => {
this.zip.file('OEBPS/' + r.dest, r.data) this.zip.file('OEBPS/' + r.dest, r.data)
}) })
this.progress(6, 0, 'Complete!')
} }
// for node, resolve a Buffer, in browser resolve a Blob // for node, resolve a Buffer, in browser resolve a Blob
@ -238,6 +229,7 @@ module.exports = class FimFic2Epub extends Emitter {
if (this.cachedFile) { if (this.cachedFile) {
return Promise.resolve(this.cachedFile) return Promise.resolve(this.cachedFile)
} }
this.progress(6, 0, 'Compressing...')
return this.zip return this.zip
.generateAsync({ .generateAsync({
@ -247,6 +239,7 @@ module.exports = class FimFic2Epub extends Emitter {
compressionOptions: {level: 9} compressionOptions: {level: 9}
}) })
.then((file) => { .then((file) => {
this.progress(6, 0.3, 'Complete!')
this.cachedFile = file this.cachedFile = file
return file return file
}) })
@ -269,7 +262,7 @@ module.exports = class FimFic2Epub extends Emitter {
// Internal/private methods // Internal/private methods
progress (part, percent, status) { progress (part, percent, status) {
let parts = 6 let parts = 6.3
let partsize = 1 / parts let partsize = 1 / parts
percent = (part / parts) + percent * partsize percent = (part / parts) + percent * partsize
this.trigger('progress', percent, status) this.trigger('progress', percent, status)
@ -305,7 +298,7 @@ module.exports = class FimFic2Epub extends Emitter {
return this.coverImage return this.coverImage
} }
this.coverImage = null this.coverImage = null
let url = this.storyInfo.full_image let url = this.coverUrl || this.storyInfo.full_image
if (!url) { if (!url) {
return null return null
} }
@ -357,13 +350,16 @@ module.exports = class FimFic2Epub extends Emitter {
html = html.substring(endCatsPos + 6) html = html.substring(endCatsPos + 6)
let categories = [] let categories = []
this.subjects.push('Fimfiction')
let matchCategory = /<a href="(.*?)" class="(.*?)">(.*?)<\/a>/g let matchCategory = /<a href="(.*?)" class="(.*?)">(.*?)<\/a>/g
for (let c; (c = matchCategory.exec(catsHtml));) { for (let c; (c = matchCategory.exec(catsHtml));) {
categories.push({ let cat = {
url: 'http://www.fimfiction.net' + c[1], url: 'http://www.fimfiction.net' + c[1],
className: c[2], className: c[2],
name: entities.decode(c[3]) name: entities.decode(c[3])
}) }
categories.push(cat)
this.subjects.push(cat.name)
} }
this.categories = categories this.categories = categories
@ -500,4 +496,25 @@ module.exports = class FimFic2Epub extends Emitter {
recursive() recursive()
}) })
} }
replaceRemoteResources () {
this.remoteResources.forEach((r, url) => {
let dest = '../' + r.dest
if (r.dest && r.originalUrl && r.where) {
let ourl = new RegExp(escapeStringRegexp(r.originalUrl), 'g')
for (var i = 0; i < r.where.length; i++) {
let w = r.where[i]
if (typeof w === 'number') {
this.chapters[w] = this.chapters[w].replace(ourl, dest)
} else if (w === 'description') {
this.storyInfo.description = this.storyInfo.description.replace(ourl, dest)
} else if (w === 'tags') {
this.tags.byImage[r.originalUrl].image = dest
}
}
}
})
}
} }
module.exports = FimFic2Epub

View file

@ -5,10 +5,12 @@ import FimFic2Epub from './FimFic2Epub'
import m from 'mithril' import m from 'mithril'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
function blobToDataURL (blob, callback) { function blobToDataURL (blob) {
let fr = new FileReader() return new Promise((resolve, reject) => {
fr.onloadend = function (e) { callback(fr.result) } let fr = new FileReader()
fr.readAsDataURL(blob) fr.onloadend = function (e) { resolve(fr.result) }
fr.readAsDataURL(blob)
})
} }
function blobToArrayBuffer (blob) { function blobToArrayBuffer (blob) {
@ -33,9 +35,9 @@ document.body.appendChild(dialogContainer)
let checkbox = { let checkbox = {
view: function (ctrl, args, text) { view: function (ctrl, args, text) {
return m('label.toggleable-switch', [ return m('label.toggleable-switch', {style: 'white-space: nowrap;'}, [
m('input', {type: 'checkbox', name: args.name, checked: args.checked}), m('input', {type: 'checkbox', name: args.name, checked: args.checked, onchange: args.onchange}),
m('a'), m('a', {style: 'margin-right: 10px'}),
text text
]) ])
} }
@ -46,11 +48,15 @@ let ffcStatus = m.prop('')
let dialog = { let dialog = {
controller (args) { controller (args) {
this.isLoading = m.prop(true)
this.dragging = m.prop(false) this.dragging = m.prop(false)
this.xpos = m.prop(0) this.xpos = m.prop(0)
this.ypos = m.prop(0) this.ypos = m.prop(0)
this.el = m.prop(null) this.el = m.prop(null)
this.coverFile = m.prop(null) this.coverFile = m.prop(null)
this.coverUrl = m.prop('')
this.checkboxCoverUrl = m.prop(false)
this.subjects = m.prop(ffc.subjects)
this.setCoverFile = (e) => { this.setCoverFile = (e) => {
this.coverFile(e.target.files ? e.target.files[0] : null) this.coverFile(e.target.files ? e.target.files[0] : null)
@ -76,26 +82,40 @@ let dialog = {
window.addEventListener('mousemove', onmove, false) window.addEventListener('mousemove', onmove, false)
window.addEventListener('mouseup', onup, false) window.addEventListener('mouseup', onup, false)
} }
this.onOpen = function (el, first) { this.onOpen = function (el, isInitialized) {
if (!first) { if (!isInitialized) {
this.el(el) this.el(el)
let rect = this.el().firstChild.getBoundingClientRect() this.center()
this.xpos((window.innerWidth / 2) - (rect.width / 2) + document.body.scrollLeft) this.isLoading(true)
this.ypos((window.innerHeight / 2) - (rect.height / 2) + document.body.scrollTop) ffc.fetchMetadata().then(() => {
this.move() this.isLoading(false)
m.redraw(true)
this.center()
})
} }
} }
this.move = () => { this.move = () => {
this.el().style.left = this.xpos() + 'px' this.el().style.left = this.xpos() + 'px'
this.el().style.top = this.ypos() + 'px' this.el().style.top = this.ypos() + 'px'
} }
this.center = () => {
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.createEpub = (e) => { this.createEpub = (e) => {
ffcProgress(0) ffcProgress(0)
ffcStatus('') ffcStatus('')
e.target.disabled = true e.target.disabled = true
let chain = Promise.resolve() let chain = Promise.resolve()
if (this.coverFile()) { ffc.coverUrl = ''
chain = blobToArrayBuffer(this.coverFile()).then(ffc.setCoverImage.bind(ffc)) ffc.coverImage = null
if (this.checkboxCoverUrl()) {
ffc.coverUrl = this.coverUrl()
} else if (this.coverFile()) {
chain = chain.then(blobToArrayBuffer.bind(null, this.coverFile())).then(ffc.setCoverImage.bind(ffc))
} }
m.redraw() m.redraw()
@ -105,7 +125,7 @@ let dialog = {
.then(ffc.getFile.bind(ffc)).then((file) => { .then(ffc.getFile.bind(ffc)).then((file) => {
console.log('Saving file...') console.log('Saving file...')
if (typeof safari !== 'undefined') { if (typeof safari !== 'undefined') {
blobToDataURL(file, (dataurl) => { blobToDataURL(file).then((dataurl) => {
document.location.href = dataurl document.location.href = dataurl
alert('Add .epub to the filename of the downloaded file') alert('Add .epub to the filename of the downloaded file')
}) })
@ -119,28 +139,27 @@ let dialog = {
view (ctrl, args, extras) { view (ctrl, args, extras) {
return m('.drop-down-pop-up-container', {config: ctrl.onOpen.bind(ctrl)}, m('.drop-down-pop-up', [ 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('h1', {onmousedown: ctrl.ondown}, m('i.fa.fa-book'), 'Export to EPUB', m('a.close_button', {onclick: closeDialog})),
m('.drop-down-pop-up-content', [ ctrl.isLoading() ? m('div', {style: 'text-align:center;'}, m('i.fa.fa-spin.fa-spinner', {style: 'font-size:50px; margin:20px; color:#777;'})) : m('.drop-down-pop-up-content', [
m('table.properties', [ m('table.properties', [
m('tr', m('td.label', 'Custom cover image'), m('td', m('tr', m('td.label', 'Custom cover image'), m('td',
// m(checkbox, {name: '', checked: true}, ' Custom cover'), ctrl.checkboxCoverUrl() ? m('input', {type: 'url', placeholder: 'Image URL', onchange: m.withAttr('value', ctrl.coverUrl)}) : m('input', {type: 'file', accept: 'image/*', onchange: ctrl.setCoverFile})
// m('input', {type: 'url', placeholder: 'Image URL'}), ), m('td', m(checkbox, {checked: ctrl.checkboxCoverUrl(), onchange: m.withAttr('checked', ctrl.checkboxCoverUrl)}, 'Use image URL'))),
// '- or -', m('tr', m('td.section_header', {colspan: 3}, m('b', 'Metadata'))),
m('form', [ m('tr', m('td.label', 'Categories'), m('td', {colspan: 2},
m('input', {type: 'file', accept: 'image/*', onchange: ctrl.setCoverFile}), m('textarea', {rows: 5}, ctrl.subjects().join('\n')),
m('button', {type: 'reset'}, 'Reset') m(checkbox, {checked: false}, 'Join categories into one (for iBooks)')
])
)) ))
// m('tr', m('td.label', 'Chapter headings'), m('td', m(checkbox, {checked: true}))) // m('tr', m('td.label', 'Chapter headings'), m('td', m(checkbox, {checked: true})))
]), ]),
m('.drop-down-pop-up-footer', [ m('.drop-down-pop-up-footer', [
m('button.styled_button', {onclick: ctrl.createEpub, disabled: ffcProgress() >= 0 && ffcProgress() < 1}, 'Create EPUB'), m('button.styled_button', {onclick: ctrl.createEpub, disabled: ffcProgress() >= 0 && ffcProgress() < 1}, 'Create EPUB'),
ffcProgress() >= 0 ? m('.rating_container', m('.rating_container',
m('.bars_container', m('.bar_container', m('.bar_dislike', m('.bar.bar_like', {style: {width: ffcProgress() * 100 + '%'}})))), m('.bars_container', m('.bar_container', m('.bar_dislike', m('.bar.bar_like', {style: {width: Math.max(0, ffcProgress()) * 100 + '%'}})))),
' ', ' ',
ffcProgress() < 1 ? m('i.fa.fa-spin.fa-spinner') : null, ffcProgress() >= 0 && ffcProgress() < 1 ? m('i.fa.fa-spin.fa-spinner') : null,
' ', ' ',
ffcStatus() ffcStatus()
) : null )
]) ])
]) ])
])) ]))