mirror of
https://github.com/daniel-j/fimfic2epub.git
synced 2024-05-19 20:03:53 +12:00
refactoring, more options, various fixes
This commit is contained in:
parent
2cfa868570
commit
89c489511b
|
@ -14,7 +14,13 @@ if (outputStdout) {
|
||||||
console.log('Outputting to stdout')
|
console.log('Outputting to stdout')
|
||||||
}
|
}
|
||||||
|
|
||||||
ffc.fetch()
|
ffc.on('progress', (percent, status) => {
|
||||||
|
if (status) {
|
||||||
|
console.log('fimfic2epub: ' + status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ffc.fetchAll()
|
||||||
.then(ffc.build.bind(ffc))
|
.then(ffc.build.bind(ffc))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
let filename = process.argv[3] || ffc.filename
|
let filename = process.argv[3] || ffc.filename
|
||||||
|
|
|
@ -4,9 +4,11 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
#epubDialogContainer .drop-down-pop-up-container .drop-down-pop-up {
|
#epubDialogContainer .drop-down-pop-up-container .drop-down-pop-up {
|
||||||
width: 600px;
|
width: 650px;
|
||||||
}
|
}
|
||||||
#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;
|
||||||
|
@ -17,8 +19,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#epubDialogContainer table.properties textarea {
|
#epubDialogContainer table.properties textarea {
|
||||||
line-height: 1em;
|
line-height: 1.2em;
|
||||||
font-size: 1em;
|
font-size: 0.95em;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,11 +34,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#epubDialogContainer div.rating_container {
|
#epubDialogContainer div.rating_container {
|
||||||
padding-top: 4px;
|
padding-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#epubDialogContainer div.rating_container div.bar_container {
|
#epubDialogContainer div.rating_container div.bar_container {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#epubDialogContainer div.rating_container div.bar_dislike {
|
#epubDialogContainer div.rating_container div.bar_dislike {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import Emitter from 'es6-event-emitter'
|
||||||
import { styleCss, coverstyleCss, titlestyleCss } from './styles'
|
import { styleCss, coverstyleCss, titlestyleCss } from './styles'
|
||||||
|
|
||||||
import { cleanMarkup } from './cleanMarkup'
|
import { cleanMarkup } from './cleanMarkup'
|
||||||
|
import htmlWordCount from './html-wordcount'
|
||||||
import fetch from './fetch'
|
import fetch from './fetch'
|
||||||
import fetchRemote from './fetchRemote'
|
import fetchRemote from './fetchRemote'
|
||||||
import * as template from './templates'
|
import * as template from './templates'
|
||||||
|
@ -87,15 +88,27 @@ class FimFic2Epub extends Emitter {
|
||||||
this.options = {
|
this.options = {
|
||||||
addCommentsLink: true,
|
addCommentsLink: true,
|
||||||
includeAuthorNotes: true,
|
includeAuthorNotes: true,
|
||||||
addChapterHeadings: true
|
addChapterHeadings: true,
|
||||||
|
includeExternal: true,
|
||||||
|
|
||||||
|
joinSubjects: false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchPromise = null
|
// promise cache
|
||||||
|
this.pcache = {
|
||||||
|
metadata: null,
|
||||||
|
chapters: null,
|
||||||
|
remoteResources: null,
|
||||||
|
coverImage: null,
|
||||||
|
fetchAll: null
|
||||||
|
}
|
||||||
|
|
||||||
this.storyInfo = null
|
this.storyInfo = null
|
||||||
this.description = ''
|
this.description = ''
|
||||||
this.subjects = []
|
this.subjects = []
|
||||||
this.chapters = []
|
this.chapters = []
|
||||||
|
this.chaptersHtml = []
|
||||||
|
this.remoteResourcesCached = false
|
||||||
this.remoteResources = new Map()
|
this.remoteResources = new Map()
|
||||||
this.coverUrl = ''
|
this.coverUrl = ''
|
||||||
this.coverImage = null
|
this.coverImage = null
|
||||||
|
@ -103,6 +116,10 @@ class FimFic2Epub extends Emitter {
|
||||||
this.coverType = ''
|
this.coverType = ''
|
||||||
this.coverImageDimensions = {width: 0, height: 0}
|
this.coverImageDimensions = {width: 0, height: 0}
|
||||||
|
|
||||||
|
this.hasRemoteResources = {
|
||||||
|
titlePage: false
|
||||||
|
}
|
||||||
|
|
||||||
this.cachedFile = null
|
this.cachedFile = null
|
||||||
this.categories = []
|
this.categories = []
|
||||||
this.tags = []
|
this.tags = []
|
||||||
|
@ -110,103 +127,236 @@ class FimFic2Epub extends Emitter {
|
||||||
this.zip = null
|
this.zip = null
|
||||||
}
|
}
|
||||||
|
|
||||||
setCoverImage (buffer) {
|
fetchAll () {
|
||||||
let info = fileType(isNode ? buffer : new Uint8Array(buffer))
|
if (this.pcache.fetchAll) {
|
||||||
if (!info || info.mime.indexOf('image/') !== 0) {
|
return this.pcache.fetchAll
|
||||||
throw new Error('Invalid image')
|
|
||||||
}
|
}
|
||||||
this.coverImage = buffer
|
|
||||||
this.coverFilename = 'Images/cover.' + info.ext
|
|
||||||
this.coverType = info.mime
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
.then(this.fetchTitlePage.bind(this))
|
|
||||||
.then(() => cleanMarkup(this.description)).then((html) => {
|
|
||||||
this.storyInfo.description = html
|
|
||||||
this.findRemoteResources('description', 'description', html)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
setTitle (title) {
|
|
||||||
this.storyInfo.title = title.trim()
|
|
||||||
this.filename = FimFic2Epub.getFilename(this.storyInfo)
|
|
||||||
}
|
|
||||||
setAuthorName (name) {
|
|
||||||
this.storyInfo.author.name = name.trim()
|
|
||||||
this.filename = FimFic2Epub.getFilename(this.storyInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch () {
|
|
||||||
if (this.fetchPromise) {
|
|
||||||
return this.fetchPromise
|
|
||||||
}
|
|
||||||
|
|
||||||
this.chapters.length = 0
|
|
||||||
this.remoteResources.clear()
|
|
||||||
|
|
||||||
this.progress(0, 0, 'Fetching...')
|
this.progress(0, 0, 'Fetching...')
|
||||||
|
|
||||||
this.fetchPromise = Promise.resolve()
|
this.pcache.fetchAll = this.fetchMetadata()
|
||||||
if (!this.storyInfo) {
|
|
||||||
this.fetchPromise = this.fetchPromise.then(this.fetchMetadata.bind(this))
|
|
||||||
}
|
|
||||||
this.fetchPromise = this.fetchPromise
|
|
||||||
.then(this.fetchCoverImage.bind(this))
|
|
||||||
.then(this.fetchChapters.bind(this))
|
.then(this.fetchChapters.bind(this))
|
||||||
|
.then(this.fetchCoverImage.bind(this))
|
||||||
.then(this.fetchRemoteFiles.bind(this))
|
.then(this.fetchRemoteFiles.bind(this))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.fetchPromise = null
|
this.progress(0, 0.95)
|
||||||
|
this.pcache.fetchAll = null
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.fetchPromise
|
return this.pcache.fetchAll
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchMetadata () {
|
||||||
|
if (this.pcache.metadata) {
|
||||||
|
return this.pcache.metadata
|
||||||
|
}
|
||||||
|
if (this.storyInfo) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
this.storyInfo = null
|
||||||
|
this.description = ''
|
||||||
|
this.subjects = []
|
||||||
|
|
||||||
|
this.progress(0, 0, 'Fetching metadata...')
|
||||||
|
|
||||||
|
this.pcache.metadata = 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.5)
|
||||||
|
})
|
||||||
|
.then(this.fetchTitlePage.bind(this))
|
||||||
|
.then(() => {
|
||||||
|
this.progress(0, 1)
|
||||||
|
})
|
||||||
|
.then(() => cleanMarkup(this.description)).then((html) => {
|
||||||
|
this.storyInfo.description = html
|
||||||
|
this.findRemoteResources('description', 'description', html)
|
||||||
|
}).then(() => {
|
||||||
|
this.pcache.metadata = null
|
||||||
|
})
|
||||||
|
return this.pcache.metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchChapters () {
|
||||||
|
if (this.pcache.chapters) {
|
||||||
|
return this.pcache.chapters
|
||||||
|
}
|
||||||
|
// chapters have already been fetched
|
||||||
|
if (this.chapters.length !== 0) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
this.chapters.length = 0
|
||||||
|
this.chaptersHtml.length = 0
|
||||||
|
|
||||||
|
this.progress(0, 0, 'Fetching chapters...')
|
||||||
|
this.pcache.chapters = new Promise((resolve, reject) => {
|
||||||
|
let chapters = this.storyInfo.chapters
|
||||||
|
let chapterCount = this.storyInfo.chapters.length
|
||||||
|
let currentChapter = 0
|
||||||
|
let completeCount = 0
|
||||||
|
|
||||||
|
if (chapterCount === 0) {
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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://www.fimfiction.net', '')
|
||||||
|
fetch(url).then((html) => {
|
||||||
|
let chapter = this.parseChapterPage(html)
|
||||||
|
Promise.all([
|
||||||
|
cleanMarkup(chapter.content),
|
||||||
|
cleanMarkup(chapter.notes)
|
||||||
|
]).then((values) => {
|
||||||
|
chapter.content = values[0]
|
||||||
|
chapter.notes = values[1]
|
||||||
|
ch.realWordCount = htmlWordCount(chapter.content)
|
||||||
|
this.chapters[index] = chapter
|
||||||
|
|
||||||
|
completeCount++
|
||||||
|
this.progress(0, completeCount / chapterCount, 'Fetched chapter ' + (completeCount) + ' / ' + chapters.length)
|
||||||
|
if (completeCount < chapterCount) {
|
||||||
|
recursive()
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// concurrent downloads!
|
||||||
|
recursive()
|
||||||
|
recursive()
|
||||||
|
recursive()
|
||||||
|
recursive()
|
||||||
|
}).then(() => {
|
||||||
|
this.pcache.chapters = null
|
||||||
|
})
|
||||||
|
return this.pcache.chapters
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchRemoteFiles () {
|
||||||
|
if (!this.options.includeExternal) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
if (this.pcache.remoteResources) {
|
||||||
|
return this.pcache.remoteResources
|
||||||
|
}
|
||||||
|
if (this.remoteResourcesCached) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.progress(0, 0, 'Fetching remote files...')
|
||||||
|
this.pcache.remoteResources = new Promise((resolve, reject) => {
|
||||||
|
let iter = this.remoteResources.entries()
|
||||||
|
let count = 0
|
||||||
|
let completeCount = 0
|
||||||
|
|
||||||
|
let recursive = () => {
|
||||||
|
let r = iter.next().value
|
||||||
|
if (!r) {
|
||||||
|
if (completeCount === this.remoteResources.size) {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let url = r[0]
|
||||||
|
r = r[1]
|
||||||
|
|
||||||
|
count++
|
||||||
|
|
||||||
|
fetchRemote(url, 'arraybuffer').then((data) => {
|
||||||
|
r.dest = null
|
||||||
|
let info = fileType(isNode ? data : new Uint8Array(data))
|
||||||
|
if (info) {
|
||||||
|
let type = info.mime
|
||||||
|
r.type = type
|
||||||
|
let isImage = type.indexOf('image/') === 0
|
||||||
|
let folder = isImage ? 'Images' : 'Misc'
|
||||||
|
let dest = folder + '/*.' + info.ext
|
||||||
|
r.dest = dest.replace('*', r.filename)
|
||||||
|
r.data = data
|
||||||
|
}
|
||||||
|
completeCount++
|
||||||
|
this.progress(0, completeCount / this.remoteResources.size, 'Fetched remote file ' + completeCount + ' / ' + this.remoteResources.size)
|
||||||
|
recursive()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// concurrent downloads!
|
||||||
|
recursive()
|
||||||
|
recursive()
|
||||||
|
recursive()
|
||||||
|
recursive()
|
||||||
|
}).then(() => {
|
||||||
|
this.remoteResourcesCached = true
|
||||||
|
this.pcache.remoteResources = null
|
||||||
|
})
|
||||||
|
return this.pcache.remoteResources
|
||||||
|
}
|
||||||
|
|
||||||
|
buildChapters () {
|
||||||
|
let chain = Promise.resolve()
|
||||||
|
this.chaptersHtml.length = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < this.chapters.length; i++) {
|
||||||
|
let ch = this.storyInfo.chapters[i]
|
||||||
|
let chapter = this.chapters[i]
|
||||||
|
chain = chain.then(template.createChapter.bind(null, ch, chapter, this)).then((html) => {
|
||||||
|
this.findRemoteResources('ch_' + zeroFill(3, i + 1), i, html)
|
||||||
|
this.chaptersHtml[i] = html
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain
|
||||||
}
|
}
|
||||||
|
|
||||||
build () {
|
build () {
|
||||||
this.cachedFile = null
|
this.cachedFile = null
|
||||||
this.zip = null
|
this.zip = null
|
||||||
|
|
||||||
this.replaceRemoteResources()
|
return this.buildChapters().then(() => {
|
||||||
|
this.replaceRemoteResources()
|
||||||
|
|
||||||
this.zip = new JSZip()
|
this.zip = new JSZip()
|
||||||
|
|
||||||
this.zip.file('mimetype', 'application/epub+zip')
|
this.zip.file('mimetype', 'application/epub+zip')
|
||||||
this.zip.file('META-INF/container.xml', containerXml)
|
this.zip.file('META-INF/container.xml', containerXml)
|
||||||
|
|
||||||
this.zip.file('OEBPS/content.opf', template.createOpf(this))
|
this.zip.file('OEBPS/content.opf', template.createOpf(this))
|
||||||
|
|
||||||
if (this.coverImage) {
|
if (this.coverImage) {
|
||||||
this.zip.file('OEBPS/' + this.coverFilename, this.coverImage)
|
this.zip.file('OEBPS/' + this.coverFilename, this.coverImage)
|
||||||
}
|
}
|
||||||
this.zip.file('OEBPS/Text/cover.xhtml', template.createCoverPage(this))
|
this.zip.file('OEBPS/Text/cover.xhtml', template.createCoverPage(this))
|
||||||
this.zip.file('OEBPS/Styles/coverstyle.css', coverstyleCss)
|
this.zip.file('OEBPS/Styles/coverstyle.css', coverstyleCss)
|
||||||
|
|
||||||
this.zip.file('OEBPS/Text/title.xhtml', template.createTitlePage(this))
|
this.zip.file('OEBPS/Text/title.xhtml', template.createTitlePage(this))
|
||||||
this.zip.file('OEBPS/Styles/titlestyle.css', titlestyleCss)
|
this.zip.file('OEBPS/Styles/titlestyle.css', titlestyleCss)
|
||||||
|
|
||||||
this.zip.file('OEBPS/Text/nav.xhtml', template.createNav(this))
|
this.zip.file('OEBPS/Text/nav.xhtml', template.createNav(this))
|
||||||
this.zip.file('OEBPS/toc.ncx', template.createNcx(this))
|
this.zip.file('OEBPS/toc.ncx', template.createNcx(this))
|
||||||
|
|
||||||
for (let i = 0; i < this.chapters.length; i++) {
|
for (let i = 0; i < this.chapters.length; i++) {
|
||||||
let filename = 'OEBPS/Text/chapter_' + zeroFill(3, i + 1) + '.xhtml'
|
let filename = 'OEBPS/Text/chapter_' + zeroFill(3, i + 1) + '.xhtml'
|
||||||
let html = this.chapters[i]
|
let html = this.chaptersHtml[i]
|
||||||
this.zip.file(filename, html)
|
this.zip.file(filename, html)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.zip.file('OEBPS/Styles/style.css', styleCss)
|
this.zip.file('OEBPS/Styles/style.css', styleCss)
|
||||||
|
|
||||||
this.remoteResources.forEach((r) => {
|
this.remoteResources.forEach((r) => {
|
||||||
this.zip.file('OEBPS/' + r.dest, r.data)
|
if (r.dest) {
|
||||||
|
this.zip.file('OEBPS/' + r.dest, r.data)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,7 +368,7 @@ 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...')
|
this.progress(0, 0.95, 'Compressing...')
|
||||||
|
|
||||||
return this.zip
|
return this.zip
|
||||||
.generateAsync({
|
.generateAsync({
|
||||||
|
@ -228,7 +378,7 @@ class FimFic2Epub extends Emitter {
|
||||||
compressionOptions: {level: 9}
|
compressionOptions: {level: 9}
|
||||||
})
|
})
|
||||||
.then((file) => {
|
.then((file) => {
|
||||||
this.progress(6, 0.3, 'Complete!')
|
this.progress(0, 1, 'Complete!')
|
||||||
this.cachedFile = file
|
this.cachedFile = file
|
||||||
return file
|
return file
|
||||||
})
|
})
|
||||||
|
@ -249,11 +399,30 @@ class FimFic2Epub extends Emitter {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTitle (title) {
|
||||||
|
this.storyInfo.title = title.trim()
|
||||||
|
this.filename = FimFic2Epub.getFilename(this.storyInfo)
|
||||||
|
}
|
||||||
|
setAuthorName (name) {
|
||||||
|
this.storyInfo.author.name = name.trim()
|
||||||
|
this.filename = FimFic2Epub.getFilename(this.storyInfo)
|
||||||
|
}
|
||||||
|
setCoverImage (buffer) {
|
||||||
|
let info = fileType(isNode ? buffer : new Uint8Array(buffer))
|
||||||
|
if (!info || info.mime.indexOf('image/') !== 0) {
|
||||||
|
throw new Error('Invalid image')
|
||||||
|
}
|
||||||
|
this.coverImage = buffer
|
||||||
|
this.coverFilename = 'Images/cover.' + info.ext
|
||||||
|
this.coverType = info.mime
|
||||||
|
this.coverImageDimensions = sizeOf(new Buffer(buffer))
|
||||||
|
}
|
||||||
|
|
||||||
// Internal/private methods
|
// Internal/private methods
|
||||||
progress (part, percent, status) {
|
progress (part, percent, status) {
|
||||||
let parts = 6.3
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,19 +452,21 @@ class FimFic2Epub extends Emitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchCoverImage () {
|
fetchCoverImage () {
|
||||||
|
if (this.pcache.coverImage) {
|
||||||
|
return this.pcache.coverImage
|
||||||
|
}
|
||||||
if (this.coverImage) {
|
if (this.coverImage) {
|
||||||
return this.coverImage
|
return Promise.resolve(this.coverImage)
|
||||||
}
|
}
|
||||||
this.coverImage = null
|
this.coverImage = null
|
||||||
let url = this.coverUrl || this.storyInfo.full_image
|
let url = this.coverUrl || this.storyInfo.full_image
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return null
|
return Promise.resolve(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.progress(0, 0.6, 'Fetching cover image...')
|
this.progress(0, 0, 'Fetching cover image...')
|
||||||
|
|
||||||
return fetchRemote(url, 'arraybuffer').then((data) => {
|
this.pcache.coverImage = fetchRemote(url, 'arraybuffer').then((data) => {
|
||||||
this.progress(0, 1)
|
|
||||||
let info = fileType(isNode ? data : new Uint8Array(data))
|
let info = fileType(isNode ? data : new Uint8Array(data))
|
||||||
if (info) {
|
if (info) {
|
||||||
let type = info.mime
|
let type = info.mime
|
||||||
|
@ -314,7 +485,10 @@ class FimFic2Epub extends Emitter {
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
}).then(() => {
|
||||||
|
this.pcache.coverImage = null
|
||||||
})
|
})
|
||||||
|
return this.pcache.coverImage
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchTitlePage () {
|
fetchTitlePage () {
|
||||||
|
@ -397,7 +571,7 @@ class FimFic2Epub extends Emitter {
|
||||||
|
|
||||||
let authorNotesPos = html.indexOf('<div class="authors-note"')
|
let authorNotesPos = html.indexOf('<div class="authors-note"')
|
||||||
let authorNotes = ''
|
let authorNotes = ''
|
||||||
if (this.options.includeAuthorNotes && authorNotesPos !== -1) {
|
if (authorNotesPos !== -1) {
|
||||||
authorNotesPos = authorNotesPos + html.substring(authorNotesPos).indexOf('<b>Author\'s Note:</b>')
|
authorNotesPos = authorNotesPos + html.substring(authorNotesPos).indexOf('<b>Author\'s Note:</b>')
|
||||||
authorNotes = html.substring(authorNotesPos + 22)
|
authorNotes = html.substring(authorNotesPos + 22)
|
||||||
authorNotes = authorNotes.substring(0, authorNotes.indexOf('\t\n\t</div>'))
|
authorNotes = authorNotes.substring(0, authorNotes.indexOf('\t\n\t</div>'))
|
||||||
|
@ -417,115 +591,45 @@ class FimFic2Epub extends Emitter {
|
||||||
return {content: chapter, notes: authorNotes, notesFirst: authorNotesPos < chapterPos}
|
return {content: chapter, notes: authorNotes, notesFirst: authorNotesPos < chapterPos}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchChapters () {
|
|
||||||
this.progress(1, 0, 'Fetching chapters...')
|
|
||||||
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) {
|
|
||||||
resolve()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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://www.fimfiction.net', '')
|
|
||||||
fetch(url).then((html) => {
|
|
||||||
html = this.parseChapterPage(html)
|
|
||||||
template.createChapter(ch, html, this).then((html) => {
|
|
||||||
this.findRemoteResources('ch_' + zeroFill(3, index + 1), index, html)
|
|
||||||
this.chapters[index] = html
|
|
||||||
completeCount++
|
|
||||||
this.progress(1, (completeCount / chapterCount) * 4)
|
|
||||||
if (completeCount < chapterCount) {
|
|
||||||
recursive()
|
|
||||||
} else {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// concurrent downloads!
|
|
||||||
recursive()
|
|
||||||
recursive()
|
|
||||||
recursive()
|
|
||||||
recursive()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchRemoteFiles () {
|
|
||||||
this.progress(5, 0, 'Fetching remote files...')
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let iter = this.remoteResources.entries()
|
|
||||||
let count = 0
|
|
||||||
let completeCount = 0
|
|
||||||
|
|
||||||
let recursive = () => {
|
|
||||||
let r = iter.next().value
|
|
||||||
if (!r) {
|
|
||||||
if (completeCount === this.remoteResources.size) {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let url = r[0]
|
|
||||||
r = r[1]
|
|
||||||
|
|
||||||
// console.log('Fetching remote file ' + (count + 1) + ' of ' + this.remoteResources.size + ': ' + r.filename, url)
|
|
||||||
count++
|
|
||||||
|
|
||||||
fetchRemote(url, 'arraybuffer').then((data) => {
|
|
||||||
r.dest = null
|
|
||||||
let info = fileType(isNode ? data : new Uint8Array(data))
|
|
||||||
if (info) {
|
|
||||||
let type = info.mime
|
|
||||||
r.type = type
|
|
||||||
let isImage = type.indexOf('image/') === 0
|
|
||||||
let folder = isImage ? 'Images' : 'Misc'
|
|
||||||
let dest = folder + '/*.' + info.ext
|
|
||||||
r.dest = dest.replace('*', r.filename)
|
|
||||||
r.data = data
|
|
||||||
}
|
|
||||||
completeCount++
|
|
||||||
this.progress(5, completeCount / this.remoteResources.size)
|
|
||||||
recursive()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// concurrent downloads!
|
|
||||||
recursive()
|
|
||||||
recursive()
|
|
||||||
recursive()
|
|
||||||
recursive()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceRemoteResources () {
|
replaceRemoteResources () {
|
||||||
this.remoteResources.forEach((r, url) => {
|
if (!this.options.includeExternal) {
|
||||||
let dest = '../' + r.dest
|
this.remoteResources.forEach((r, url) => {
|
||||||
if (r.dest && r.originalUrl && r.where) {
|
if (r.originalUrl && r.where) {
|
||||||
let ourl = new RegExp(escapeStringRegexp(r.originalUrl), 'g')
|
let ourl = new RegExp(escapeStringRegexp(r.originalUrl), 'g')
|
||||||
for (var i = 0; i < r.where.length; i++) {
|
for (var i = 0; i < r.where.length; i++) {
|
||||||
let w = r.where[i]
|
let w = r.where[i]
|
||||||
if (typeof w === 'number') {
|
if (typeof w === 'number') {
|
||||||
this.chapters[w] = this.chapters[w].replace(ourl, dest)
|
if (ourl.test(this.chapters[w])) {
|
||||||
} else if (w === 'description') {
|
this.storyInfo.chapters[w].remote = true
|
||||||
this.storyInfo.description = this.storyInfo.description.replace(ourl, dest)
|
}
|
||||||
} else if (w === 'tags') {
|
} else if (w === 'description') {
|
||||||
this.tags.byImage[r.originalUrl].image = dest
|
if (ourl.test(this.storyInfo.description)) {
|
||||||
|
this.hasRemoteResources.titlePage = true
|
||||||
|
}
|
||||||
|
} else if (w === 'tags') {
|
||||||
|
this.hasRemoteResources.titlePage = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
} else {
|
||||||
|
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.chaptersHtml[w] = this.chaptersHtml[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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
93
src/main.js
93
src/main.js
|
@ -44,13 +44,15 @@ let checkbox = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ffcProgress = m.prop(-1)
|
let ffcProgress = m.prop(0)
|
||||||
let ffcStatus = m.prop('')
|
let ffcStatus = m.prop('')
|
||||||
|
|
||||||
let dialog = {
|
let dialog = {
|
||||||
controller (args) {
|
controller (args) {
|
||||||
const ctrl = this
|
const ctrl = this
|
||||||
|
|
||||||
|
ffcProgress(0)
|
||||||
|
|
||||||
this.isLoading = m.prop(true)
|
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)
|
||||||
|
@ -62,10 +64,33 @@ let dialog = {
|
||||||
|
|
||||||
this.title = m.prop('')
|
this.title = m.prop('')
|
||||||
this.author = m.prop('')
|
this.author = m.prop('')
|
||||||
this.subjects = m.prop(ffc.subjects)
|
this.subjects = m.prop([])
|
||||||
this.addCommentsLink = m.prop(ffc.options.addCommentsLink)
|
this.addCommentsLink = m.prop(ffc.options.addCommentsLink)
|
||||||
this.includeAuthorNotes = m.prop(ffc.options.includeAuthorNotes)
|
this.includeAuthorNotes = m.prop(ffc.options.includeAuthorNotes)
|
||||||
this.addChapterHeadings = m.prop(ffc.options.addChapterHeadings)
|
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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
@ -73,7 +98,13 @@ let dialog = {
|
||||||
|
|
||||||
this.setSubjects = function () {
|
this.setSubjects = function () {
|
||||||
// 'this' is the textarea
|
// 'this' is the textarea
|
||||||
ctrl.subjects(this.value.split('\n').map((s) => s.trim()).filter((s) => !!s))
|
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
|
||||||
|
}))
|
||||||
this.value = ctrl.subjects().join('\n')
|
this.value = ctrl.subjects().join('\n')
|
||||||
autosize.update(this)
|
autosize.update(this)
|
||||||
}
|
}
|
||||||
|
@ -96,31 +127,21 @@ 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, isInitialized) {
|
|
||||||
if (!isInitialized) {
|
|
||||||
this.el(el)
|
|
||||||
this.center()
|
|
||||||
this.isLoading(true)
|
|
||||||
ffc.fetchMetadata().then(() => {
|
|
||||||
this.isLoading(false)
|
|
||||||
this.title(ffc.storyInfo.title)
|
|
||||||
this.author(ffc.storyInfo.author.name)
|
|
||||||
m.redraw(true)
|
|
||||||
this.center()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.move = (xpos, ypos) => {
|
this.move = (xpos, ypos) => {
|
||||||
this.xpos(Math.max(0, xpos))
|
let bc = document.querySelector('.body_container')
|
||||||
this.ypos(Math.max(0, ypos))
|
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)))
|
||||||
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 = () => {
|
this.center = () => {
|
||||||
|
if (this.dragging()) return
|
||||||
let rect = this.el().firstChild.getBoundingClientRect()
|
let rect = this.el().firstChild.getBoundingClientRect()
|
||||||
this.move(
|
this.move(
|
||||||
(window.innerWidth / 2) - (rect.width / 2) + document.body.scrollLeft,
|
Math.max(document.body.scrollLeft, (window.innerWidth / 2) - (rect.width / 2) + document.body.scrollLeft),
|
||||||
(window.innerHeight / 2) - (rect.height / 2) + document.body.scrollTop
|
Math.max(document.body.scrollTop, (window.innerHeight / 2) - (rect.height / 2) + document.body.scrollTop)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,10 +162,13 @@ let dialog = {
|
||||||
ffc.options.addCommentsLink = this.addCommentsLink()
|
ffc.options.addCommentsLink = this.addCommentsLink()
|
||||||
ffc.options.includeAuthorNotes = this.includeAuthorNotes()
|
ffc.options.includeAuthorNotes = this.includeAuthorNotes()
|
||||||
ffc.options.addChapterHeadings = this.addChapterHeadings()
|
ffc.options.addChapterHeadings = this.addChapterHeadings()
|
||||||
|
ffc.options.includeExternal = this.includeExternal()
|
||||||
|
ffc.subjects = this.subjects()
|
||||||
|
ffc.options.joinSubjects = this.joinSubjects()
|
||||||
m.redraw()
|
m.redraw()
|
||||||
|
|
||||||
chain
|
chain
|
||||||
.then(ffc.fetch.bind(ffc))
|
.then(ffc.fetchAll.bind(ffc))
|
||||||
.then(ffc.build.bind(ffc))
|
.then(ffc.build.bind(ffc))
|
||||||
.then(ffc.getFile.bind(ffc)).then((file) => {
|
.then(ffc.getFile.bind(ffc)).then((file) => {
|
||||||
console.log('Saving file...')
|
console.log('Saving file...')
|
||||||
|
@ -163,8 +187,8 @@ 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})),
|
||||||
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('.drop-down-pop-up-content', [
|
||||||
m('table.properties', [
|
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', [
|
||||||
m('tr', m('td.section_header', {colspan: 3}, m('b', 'General settings'))),
|
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', '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', 'Author'), m('td', {colspan: 2}, m('input', {type: 'text', value: ctrl.author(), onchange: m.withAttr('value', ctrl.author)}))),
|
||||||
|
@ -176,25 +200,27 @@ let dialog = {
|
||||||
),
|
),
|
||||||
m('tr', m('td.label', ''), m('td', {colspan: 2},
|
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.addChapterHeadings(), onchange: m.withAttr('checked', ctrl.addChapterHeadings)}, 'Add chapter headings'),
|
||||||
m(checkbox, {checked: ctrl.addCommentsLink(), onchange: m.withAttr('checked', ctrl.addCommentsLink)}, 'Add links to online comments'),
|
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.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.')
|
||||||
)),
|
)),
|
||||||
|
|
||||||
m('tr', m('td.section_header', {colspan: 3}, m('b', 'Metadata customization'))),
|
m('tr', m('td.section_header', {colspan: 3}, m('b', 'Metadata customization'))),
|
||||||
m('tr', m('td.label', 'Categories'), m('td', {colspan: 2},
|
m('tr', m('td.label', {style: 'vertical-align: top;'}, 'Categories'), m('td', {colspan: 2},
|
||||||
m('textarea', {rows: 2, config: autosize, onchange: ctrl.setSubjects}, ctrl.subjects().join('\n')),
|
m('textarea', {rows: 2, config: autosize, onchange: ctrl.setSubjects}, ctrl.subjects().join('\n')),
|
||||||
m(checkbox, {checked: false}, 'Join categories into one (iBooks only)')
|
m(checkbox, {checked: ctrl.joinSubjects(), onchange: m.withAttr('checked', ctrl.joinSubjects)}, 'Join categories into one, separated by commas')
|
||||||
))
|
))
|
||||||
]),
|
]),
|
||||||
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, style: 'float: right'}, 'Download EPUB'),
|
||||||
m('.rating_container',
|
ffcProgress() >= 0 ? m('.rating_container',
|
||||||
m('.bars_container', m('.bar_container', m('.bar_dislike', m('.bar.bar_like', {style: {width: Math.max(0, ffcProgress()) * 100 + '%'}})))),
|
m('.bars_container', m('.bar_container', m('.bar_dislike', m('.bar.bar_like', {style: {width: Math.max(0, ffcProgress()) * 100 + '%'}})))),
|
||||||
' ',
|
' ',
|
||||||
ffcProgress() >= 0 && ffcProgress() < 1 ? m('i.fa.fa-spin.fa-spinner') : null,
|
ffcProgress() >= 0 && ffcProgress() < 1 ? [ m('i.fa.fa-spin.fa-spinner'), m.trust(' ') ] : null,
|
||||||
' ',
|
|
||||||
ffcStatus()
|
ffcStatus()
|
||||||
)
|
) : null,
|
||||||
|
m('div', {style: 'clear: both'})
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
]))
|
]))
|
||||||
|
@ -219,7 +245,6 @@ function clickButton () {
|
||||||
if (!ffc) {
|
if (!ffc) {
|
||||||
ffc = new FimFic2Epub(STORY_ID)
|
ffc = new FimFic2Epub(STORY_ID)
|
||||||
ffc.on('progress', (percent, status) => {
|
ffc.on('progress', (percent, status) => {
|
||||||
console.log(Math.round(percent * 100), status)
|
|
||||||
ffcProgress(percent)
|
ffcProgress(percent)
|
||||||
if (status) {
|
if (status) {
|
||||||
ffcStatus(status)
|
ffcStatus(status)
|
||||||
|
|
|
@ -4,8 +4,6 @@ import render from './lib/mithril-node-render'
|
||||||
import { pd as pretty } from 'pretty-data'
|
import { pd as pretty } from 'pretty-data'
|
||||||
import zeroFill from 'zero-fill'
|
import zeroFill from 'zero-fill'
|
||||||
|
|
||||||
import htmlWordCount from './html-wordcount'
|
|
||||||
import { cleanMarkup } from './cleanMarkup'
|
|
||||||
import { NS } from './constants'
|
import { NS } from './constants'
|
||||||
|
|
||||||
function nth (d) {
|
function nth (d) {
|
||||||
|
@ -25,24 +23,19 @@ function prettyDate (d) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createChapter (ch, chapter, ffc) {
|
export function createChapter (ch, chapter, ffc) {
|
||||||
return Promise.all([
|
return new Promise((resolve, reject) => {
|
||||||
cleanMarkup(chapter.content),
|
let {content, notes} = chapter
|
||||||
cleanMarkup(chapter.notes)
|
|
||||||
]).then((values) => {
|
|
||||||
let [chapterContent, authorNotes] = values
|
|
||||||
|
|
||||||
ch.realWordCount = htmlWordCount(chapterContent)
|
let sections = [
|
||||||
|
m.trust(content),
|
||||||
let content = [
|
ffc.options.includeAuthorNotes && notes ? m('div#author_notes', {className: chapter.notesFirst ? 'top' : 'bottom'}, [
|
||||||
m.trust(chapterContent),
|
|
||||||
authorNotes ? m('div#author_notes', {className: chapter.notesFirst ? 'top' : 'bottom'}, [
|
|
||||||
m('p', m('b', 'Author\'s Note:')),
|
m('p', m('b', 'Author\'s Note:')),
|
||||||
m.trust(authorNotes)]) : null
|
m.trust(notes)]) : null
|
||||||
]
|
]
|
||||||
|
|
||||||
// if author notes are a the beginning of the chapter
|
// if author notes are a the beginning of the chapter
|
||||||
if (authorNotes && chapter.notesFirst) {
|
if (notes && chapter.notesFirst) {
|
||||||
content.reverse()
|
sections.reverse()
|
||||||
}
|
}
|
||||||
|
|
||||||
let chapterPage = '<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE html>\n' + pretty.xml(render(
|
let chapterPage = '<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE html>\n' + pretty.xml(render(
|
||||||
|
@ -57,7 +50,7 @@ export function createChapter (ch, chapter, ffc) {
|
||||||
m('h1', ch.title),
|
m('h1', ch.title),
|
||||||
m('hr')
|
m('hr')
|
||||||
]) : null,
|
]) : null,
|
||||||
content,
|
sections,
|
||||||
ffc.options.addCommentsLink ? m('p.double', {style: 'text-align: center; clear: both;'},
|
ffc.options.addCommentsLink ? m('p.double', {style: 'text-align: center; clear: both;'},
|
||||||
m('a.chaptercomments', {href: ch.link + '#comment_list'}, 'Read chapter comments online')
|
m('a.chaptercomments', {href: ch.link + '#comment_list'}, 'Read chapter comments online')
|
||||||
) : null
|
) : null
|
||||||
|
@ -65,7 +58,7 @@ export function createChapter (ch, chapter, ffc) {
|
||||||
])
|
])
|
||||||
))
|
))
|
||||||
|
|
||||||
return Promise.resolve(chapterPage)
|
resolve(chapterPage)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,14 +83,42 @@ function sortSpineItems (items) {
|
||||||
|
|
||||||
export function createOpf (ffc) {
|
export function createOpf (ffc) {
|
||||||
let remotes = []
|
let remotes = []
|
||||||
|
let remoteCounter = 0
|
||||||
ffc.remoteResources.forEach((r, url) => {
|
ffc.remoteResources.forEach((r, url) => {
|
||||||
|
remoteCounter++
|
||||||
|
if (!ffc.options.includeExternal) {
|
||||||
|
// hack-ish, but what can I do?
|
||||||
|
// turns out only video and audio can be remote resources.. :I
|
||||||
|
/*
|
||||||
|
if (url.indexOf('//') === 0) {
|
||||||
|
url = 'http:' + url
|
||||||
|
}
|
||||||
|
if (url.indexOf('/') === 0) {
|
||||||
|
url = 'http://www.fimfiction.net' + url
|
||||||
|
}
|
||||||
|
let mime = null
|
||||||
|
if (url.toLowerCase().lastIndexOf('.png')) {
|
||||||
|
mime = 'image/png'
|
||||||
|
} else if (url.toLowerCase().lastIndexOf('.jpg')) {
|
||||||
|
mime = 'image/jpeg'
|
||||||
|
}
|
||||||
|
if (mime) {
|
||||||
|
remotes.push(m('item', {id: 'remote_' + zeroFill(3, remoteCounter), href: url, 'media-type': mime}))
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!r.dest) {
|
if (!r.dest) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let attrs = {id: r.filename, href: r.dest, 'media-type': r.type}
|
remotes.push(m('item', {id: r.filename, href: r.dest, 'media-type': r.type}))
|
||||||
remotes.push(m('item', attrs))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let subjects = ffc.subjects
|
||||||
|
if (ffc.options.joinSubjects) {
|
||||||
|
subjects = [subjects.join(', ')]
|
||||||
|
}
|
||||||
|
|
||||||
let contentOpf = '<?xml version="1.0" encoding="utf-8"?>\n' + pretty.xml(render(
|
let contentOpf = '<?xml version="1.0" encoding="utf-8"?>\n' + pretty.xml(render(
|
||||||
m('package', {xmlns: NS.OPF, version: '3.0', 'unique-identifier': 'BookId'}, [
|
m('package', {xmlns: NS.OPF, version: '3.0', 'unique-identifier': 'BookId'}, [
|
||||||
m('metadata', {'xmlns:dc': NS.DC, 'xmlns:opf': NS.OPF}, [
|
m('metadata', {'xmlns:dc': NS.DC, 'xmlns:opf': NS.OPF}, [
|
||||||
|
@ -111,10 +132,9 @@ export function createOpf (ffc) {
|
||||||
m('dc:source', ffc.storyInfo.url),
|
m('dc:source', ffc.storyInfo.url),
|
||||||
m('dc:language', 'en'),
|
m('dc:language', 'en'),
|
||||||
ffc.coverImage ? m('meta', {name: 'cover', content: 'cover'}) : null,
|
ffc.coverImage ? m('meta', {name: 'cover', content: 'cover'}) : null,
|
||||||
m('meta', {property: 'dcterms:modified'}, new Date(ffc.storyInfo.date_modified * 1000).toISOString().replace('.000', '')),
|
m('meta', {property: 'dcterms:modified'}, new Date(ffc.storyInfo.date_modified * 1000).toISOString().replace('.000', ''))
|
||||||
m('dc:subject', 'Fimfiction')
|
].concat(subjects.map((s) =>
|
||||||
].concat(ffc.categories.map((tag) =>
|
m('dc:subject', s)
|
||||||
m('dc:subject', tag.name)
|
|
||||||
), m('meta', {name: 'fimfic2epub version', content: FIMFIC2EPUB_VERSION}))),
|
), m('meta', {name: 'fimfic2epub version', content: FIMFIC2EPUB_VERSION}))),
|
||||||
|
|
||||||
m('manifest', [
|
m('manifest', [
|
||||||
|
@ -127,10 +147,10 @@ export function createOpf (ffc) {
|
||||||
m('item', {id: 'titlestyle', href: 'Styles/titlestyle.css', 'media-type': 'text/css'}),
|
m('item', {id: 'titlestyle', href: 'Styles/titlestyle.css', 'media-type': 'text/css'}),
|
||||||
|
|
||||||
m('item', {id: 'coverpage', href: 'Text/cover.xhtml', 'media-type': 'application/xhtml+xml', properties: ffc.coverImage ? 'svg' : undefined}),
|
m('item', {id: 'coverpage', href: 'Text/cover.xhtml', 'media-type': 'application/xhtml+xml', properties: ffc.coverImage ? 'svg' : undefined}),
|
||||||
m('item', {id: 'titlepage', href: 'Text/title.xhtml', 'media-type': 'application/xhtml+xml'})
|
m('item', {id: 'titlepage', href: 'Text/title.xhtml', 'media-type': 'application/xhtml+xml', properties: ffc.hasRemoteResources.titlePage ? 'remote-resources' : null})
|
||||||
|
|
||||||
].concat(ffc.storyInfo.chapters.map((ch, num) =>
|
].concat(ffc.storyInfo.chapters.map((ch, num) =>
|
||||||
m('item', {id: 'chapter_' + zeroFill(3, num + 1), href: 'Text/chapter_' + zeroFill(3, num + 1) + '.xhtml', 'media-type': 'application/xhtml+xml'})
|
m('item', {id: 'chapter_' + zeroFill(3, num + 1), href: 'Text/chapter_' + zeroFill(3, num + 1) + '.xhtml', 'media-type': 'application/xhtml+xml', properties: ch.remote ? 'remote-resources' : null})
|
||||||
), remotes)),
|
), remotes)),
|
||||||
|
|
||||||
m('spine', {toc: 'ncx'}, sortSpineItems([
|
m('spine', {toc: 'ncx'}, sortSpineItems([
|
||||||
|
|
Loading…
Reference in a new issue