fimfic2epub/src/main.js

274 lines
10 KiB
JavaScript
Raw Normal View History

2016-08-23 02:28:30 +12:00
/* global chrome */
'use strict'
2016-06-20 08:28:28 +12:00
import FimFic2Epub from './FimFic2Epub'
2016-08-23 02:28:30 +12:00
import m from 'mithril'
2016-08-15 21:11:20 +12:00
import { saveAs } from 'file-saver'
2016-08-24 19:09:43 +12:00
import autosize from 'autosize'
2016-08-15 21:11:20 +12:00
2016-08-24 08:04:38 +12:00
function blobToDataURL (blob) {
return new Promise((resolve, reject) => {
let fr = new FileReader()
fr.onloadend = function (e) { resolve(fr.result) }
fr.readAsDataURL(blob)
})
2016-08-24 02:32:55 +12:00
}
function blobToArrayBuffer (blob) {
return new Promise((resolve, reject) => {
let fr = new FileReader()
fr.onloadend = function (e) { resolve(fr.result) }
fr.readAsArrayBuffer(blob)
})
2016-08-15 21:11:20 +12:00
}
2016-06-20 08:28:28 +12:00
2016-08-23 02:28:30 +12:00
const isChromeExt = typeof chrome !== 'undefined'
2016-06-21 09:04:08 +12:00
const STORY_ID = document.location.pathname.match(/^\/story\/(\d*)/)[1]
2016-06-20 08:28:28 +12:00
2016-08-23 02:28:30 +12:00
let ffc
2016-06-20 08:28:28 +12:00
const epubButton = document.querySelector('.story_container ul.chapters li.bottom a[title="Download Story (.epub)"]')
2016-06-21 09:04:08 +12:00
2016-08-23 02:28:30 +12:00
const dialogContainer = document.createElement('div')
dialogContainer.id = 'epubDialogContainer'
document.body.appendChild(dialogContainer)
let checkbox = {
view: function (ctrl, args, text) {
2016-08-24 08:04:38 +12:00
return m('label.toggleable-switch', {style: 'white-space: nowrap;'}, [
m('input', {type: 'checkbox', name: args.name, checked: args.checked, onchange: args.onchange}),
m('a', {style: 'margin-right: 10px'}),
2016-08-23 02:28:30 +12:00
text
])
}
}
let ffcProgress = m.prop(0)
2016-08-24 02:32:55 +12:00
let ffcStatus = m.prop('')
2016-08-23 02:28:30 +12:00
let dialog = {
controller (args) {
2016-08-24 19:09:43 +12:00
const ctrl = this
ffcProgress(0)
2016-08-24 08:04:38 +12:00
this.isLoading = m.prop(true)
2016-08-23 02:28:30 +12:00
this.dragging = m.prop(false)
2016-08-23 07:57:19 +12:00
this.xpos = m.prop(0)
this.ypos = m.prop(0)
2016-08-23 02:28:30 +12:00
this.el = m.prop(null)
2016-08-24 02:32:55 +12:00
this.coverFile = m.prop(null)
2016-08-24 08:04:38 +12:00
this.coverUrl = m.prop('')
this.checkboxCoverUrl = m.prop(false)
2016-08-24 09:49:27 +12:00
this.title = m.prop('')
this.author = m.prop('')
this.subjects = m.prop([])
2016-08-24 09:49:27 +12:00
this.addCommentsLink = m.prop(ffc.options.addCommentsLink)
this.includeAuthorNotes = m.prop(ffc.options.includeAuthorNotes)
this.addChapterHeadings = m.prop(ffc.options.addChapterHeadings)
this.includeExternal = m.prop(ffc.options.includeExternal)
this.joinSubjects = m.prop(ffc.options.joinSubjects)
this.onOpen = function (el, isInitialized) {
if (!isInitialized) {
this.el(el)
this.center()
this.isLoading(true)
ffc.fetchMetadata().then(() => {
this.isLoading(false)
ffcProgress(-1)
this.title(ffc.storyInfo.title)
this.author(ffc.storyInfo.author.name)
this.subjects(ffc.subjects.slice(0))
m.redraw(true)
this.center()
ffc.fetchChapters().then(() => {
ffcProgress(-1)
m.redraw()
})
})
}
}
2016-08-24 02:32:55 +12:00
this.setCoverFile = (e) => {
this.coverFile(e.target.files ? e.target.files[0] : null)
}
2016-08-24 19:09:43 +12:00
this.setSubjects = function () {
// 'this' is the textarea
let set = new Set()
ctrl.subjects(this.value.split('\n').map((s) => s.trim()).filter((s) => {
if (!s) return false
if (set.has(s)) return false
set.add(s)
return true
}))
2016-08-24 19:09:43 +12:00
this.value = ctrl.subjects().join('\n')
autosize.update(this)
}
2016-08-23 02:28:30 +12:00
this.ondown = (e) => {
2016-08-23 07:57:19 +12:00
let rect = this.el().firstChild.getBoundingClientRect()
let offset = {x: e.pageX - rect.left - document.body.scrollLeft, y: e.pageY - rect.top - document.body.scrollTop}
2016-08-23 02:28:30 +12:00
this.dragging(true)
let onmove = (e) => {
e.preventDefault()
if (this.dragging()) {
2016-08-24 09:49:27 +12:00
this.move(e.pageX - offset.x, e.pageY - offset.y)
2016-08-15 21:11:20 +12:00
}
2016-08-23 02:28:30 +12:00
}
let onup = () => {
this.dragging(false)
window.removeEventListener('mousemove', onmove)
window.removeEventListener('mouseup', onup)
}
window.addEventListener('mousemove', onmove, false)
window.addEventListener('mouseup', onup, false)
}
2016-08-24 09:49:27 +12:00
this.move = (xpos, ypos) => {
let bc = document.querySelector('.body_container')
let rect = this.el().firstChild.getBoundingClientRect()
this.xpos(Math.max(0, Math.min(xpos, bc.offsetWidth - rect.width)))
this.ypos(Math.max(0, Math.min(ypos, bc.offsetHeight - rect.height)))
2016-08-23 07:57:19 +12:00
this.el().style.left = this.xpos() + 'px'
this.el().style.top = this.ypos() + 'px'
}
2016-08-24 08:04:38 +12:00
this.center = () => {
if (this.dragging()) return
2016-08-24 08:04:38 +12:00
let rect = this.el().firstChild.getBoundingClientRect()
2016-08-24 09:49:27 +12:00
this.move(
Math.max(document.body.scrollLeft, (window.innerWidth / 2) - (rect.width / 2) + document.body.scrollLeft),
Math.max(document.body.scrollTop, (window.innerHeight / 2) - (rect.height / 2) + document.body.scrollTop)
2016-08-24 09:49:27 +12:00
)
2016-08-24 08:04:38 +12:00
}
2016-08-23 07:57:19 +12:00
this.createEpub = (e) => {
2016-08-24 02:32:55 +12:00
ffcProgress(0)
ffcStatus('')
2016-08-23 07:57:19 +12:00
e.target.disabled = true
2016-08-24 02:32:55 +12:00
let chain = Promise.resolve()
2016-08-24 08:04:38 +12:00
ffc.coverUrl = ''
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))
2016-08-24 02:32:55 +12:00
}
2016-08-24 09:49:27 +12:00
ffc.setTitle(this.title())
ffc.setAuthorName(this.author())
ffc.options.addCommentsLink = this.addCommentsLink()
ffc.options.includeAuthorNotes = this.includeAuthorNotes()
ffc.options.addChapterHeadings = this.addChapterHeadings()
ffc.options.includeExternal = this.includeExternal()
ffc.subjects = this.subjects()
ffc.options.joinSubjects = this.joinSubjects()
2016-08-24 02:32:55 +12:00
m.redraw()
chain
.then(ffc.fetchAll.bind(ffc))
2016-08-23 07:57:19 +12:00
.then(ffc.build.bind(ffc))
.then(ffc.getFile.bind(ffc)).then((file) => {
console.log('Saving file...')
if (typeof safari !== 'undefined') {
2016-08-24 08:04:38 +12:00
blobToDataURL(file).then((dataurl) => {
2016-08-23 07:57:19 +12:00
document.location.href = dataurl
alert('Add .epub to the filename of the downloaded file')
})
} else {
saveAs(file, ffc.filename)
}
})
}
2016-08-23 02:28:30 +12:00
},
2016-08-24 02:32:55 +12:00
2016-08-23 02:28:30 +12:00
view (ctrl, args, extras) {
2016-08-23 07:57:19 +12:00
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', [
ctrl.isLoading() ? m('div', {style: 'text-align:center;'}, m('i.fa.fa-spin.fa-spinner', {style: 'font-size:50px; margin:20px; color:#777;'})) : m('table.properties', [
2016-08-24 09:49:27 +12:00
m('tr', m('td.section_header', {colspan: 3}, m('b', 'General settings'))),
m('tr', m('td.label', 'Title'), m('td', {colspan: 2}, m('input', {type: 'text', value: ctrl.title(), onchange: m.withAttr('value', ctrl.title)}))),
m('tr', m('td.label', 'Author'), m('td', {colspan: 2}, m('input', {type: 'text', value: ctrl.author(), onchange: m.withAttr('value', ctrl.author)}))),
m('tr', m('td.label', 'Custom cover image'),
m('td',
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('td', {style: 'width: 1px'}, m(checkbox, {checked: ctrl.checkboxCoverUrl(), onchange: m.withAttr('checked', ctrl.checkboxCoverUrl)}, 'Use image URL'))
),
m('tr', m('td.label', ''), m('td', {colspan: 2},
m(checkbox, {checked: ctrl.addChapterHeadings(), onchange: m.withAttr('checked', ctrl.addChapterHeadings)}, 'Add chapter headings'),
m(checkbox, {checked: ctrl.addCommentsLink(), onchange: m.withAttr('checked', ctrl.addCommentsLink)}, 'Add link to online comments (at the end of chapters)'),
m(checkbox, {checked: ctrl.includeAuthorNotes(), onchange: m.withAttr('checked', ctrl.includeAuthorNotes)}, 'Include author\'s notes'),
m(checkbox, {checked: ctrl.includeExternal(), onchange: m.withAttr('checked', ctrl.includeExternal)}, 'Download & include remote content (embed images)'),
m('div', {style: 'font-size: 0.9em; line-height: 1em; margin-top: 4px; margin-bottom: 6px; color: #777;'}, 'Note: Disabling this creates invalid EPUBs and requires internet access to see remote content. Only cover image will be embedded.')
2016-08-24 09:49:27 +12:00
)),
m('tr', m('td.section_header', {colspan: 3}, m('b', 'Metadata customization'))),
m('tr', m('td.label', {style: 'vertical-align: top;'}, 'Categories'), m('td', {colspan: 2},
2016-08-24 19:09:43 +12:00
m('textarea', {rows: 2, config: autosize, onchange: ctrl.setSubjects}, ctrl.subjects().join('\n')),
2016-08-25 06:18:01 +12:00
m(checkbox, {checked: ctrl.joinSubjects(), onchange: m.withAttr('checked', ctrl.joinSubjects)}, 'Join categories and separate with commas (for iBooks only)')
2016-08-24 02:32:55 +12:00
))
2016-08-23 07:57:19 +12:00
]),
m('.drop-down-pop-up-footer', [
m('button.styled_button', {onclick: ctrl.createEpub, disabled: ffcProgress() >= 0 && ffcProgress() < 1, style: 'float: right'}, 'Download EPUB'),
ffcProgress() >= 0 ? m('.rating_container',
2016-08-24 08:04:38 +12:00
m('.bars_container', m('.bar_container', m('.bar_dislike', m('.bar.bar_like', {style: {width: Math.max(0, ffcProgress()) * 100 + '%'}})))),
2016-08-24 02:32:55 +12:00
' ',
ffcProgress() >= 0 && ffcProgress() < 1 ? [ m('i.fa.fa-spin.fa-spinner'), m.trust('&nbsp;&nbsp;') ] : null,
2016-08-24 02:32:55 +12:00
ffcStatus()
) : null,
m('div', {style: 'clear: both'})
2016-08-23 07:57:19 +12:00
])
2016-08-23 02:28:30 +12:00
])
]))
}
}
2016-08-24 09:49:27 +12:00
let dialogOpen = false
2016-08-23 02:28:30 +12:00
function openDialog (args, extras) {
2016-08-24 09:49:27 +12:00
if (dialogOpen) {
return
}
dialogOpen = true
2016-08-23 02:28:30 +12:00
m.mount(dialogContainer, m(dialog, args, extras))
}
function closeDialog () {
2016-08-24 09:49:27 +12:00
dialogOpen = false
2016-08-23 02:28:30 +12:00
m.mount(dialogContainer, null)
}
function clickButton () {
if (!STORY_ID) return
2016-08-24 09:49:27 +12:00
if (!ffc) {
ffc = new FimFic2Epub(STORY_ID)
ffc.on('progress', (percent, status) => {
ffcProgress(percent)
if (status) {
ffcStatus(status)
}
m.redraw()
})
}
2016-08-23 02:28:30 +12:00
openDialog()
}
if (epubButton) {
if (isChromeExt) {
chrome.runtime.sendMessage({showPageAction: true})
chrome.runtime.onMessage.addListener(function (request) {
if (request === 'pageAction') {
clickButton()
}
2016-06-28 19:39:31 +12:00
})
2016-08-23 02:28:30 +12:00
}
epubButton.addEventListener('click', function (e) {
e.preventDefault()
clickButton()
2016-06-21 18:39:26 +12:00
}, false)
2016-06-21 09:04:08 +12:00
}