diff --git a/extension/manifest.json b/extension/manifest.json index 15b3424..b43c18f 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -4,7 +4,7 @@ "name": "fimfic2epub", "short_name": "fimfic2epub", "description": "Improved EPUB exporter for Fimfiction", - "version": "1.5.0", + "version": "1.6.0", "icons": { "128": "icon-128.png" diff --git a/package.json b/package.json index e2787cd..0147435 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fimfic2epub", - "version": "1.5.0", + "version": "1.6.0", "description": "Tool to generate improved EPUB ebooks from Fimfiction stories", "author": "djazz", "repository": { @@ -32,7 +32,6 @@ "pretty-data": "^0.40.0", "request": "^2.74.0", "sanitize-filename": "^1.6.0", - "tidy-html5": "^0.1.1", "zero-fill": "^2.2.3" }, "devDependencies": { diff --git a/src/FimFic2Epub.js b/src/FimFic2Epub.js index f650da3..dfc4f31 100644 --- a/src/FimFic2Epub.js +++ b/src/FimFic2Epub.js @@ -10,7 +10,7 @@ import fileType from 'file-type' import sizeOf from 'image-size' import Emitter from 'es6-event-emitter' -import { styleCss, coverstyleCss, titlestyleCss } from './styles' +import { styleCss, coverstyleCss, titlestyleCss, paragraphsCss } from './styles' import { cleanMarkup } from './cleanMarkup' import htmlWordCount from './html-wordcount' @@ -91,7 +91,7 @@ class FimFic2Epub extends Emitter { useAuthorNotesIndex: false, addChapterHeadings: true, includeExternal: true, - + paragraphStyle: 'spaced', joinSubjects: false } @@ -386,7 +386,7 @@ class FimFic2Epub extends Emitter { } } - this.zip.file('OEBPS/Styles/style.css', styleCss) + this.zip.file('OEBPS/Styles/style.css', styleCss + '\n\n' + (paragraphsCss[this.options.paragraphStyle] || '')) this.remoteResources.forEach((r) => { if (r.dest) { @@ -532,36 +532,49 @@ class FimFic2Epub extends Emitter { } extractTitlePageInfo (html) { - let descPos = html.indexOf('
') + 2 - html = html.substring(descPos) - let ma = html.match(/Source<\/a>/) - this.storyInfo.source_image = null - if (ma) { - this.storyInfo.source_image = ma[1] - } - let endCatsPos = html.indexOf('
') - let startCatsPos = html.substring(0, endCatsPos).lastIndexOf('
') - let catsHtml = html.substring(startCatsPos, endCatsPos) - html = html.substring(endCatsPos + 6) + let startTagsPos = html.indexOf('
') + 23 + let tagsHtml = html.substring(startTagsPos) + + let endTagsPos = tagsHtml.indexOf('') + tagsHtml = tagsHtml.substring(0, endTagsPos) let categories = [] + let tags = [] + tags.byImage = {} this.subjects.length = 0 this.subjects.push('Fimfiction') this.subjects.push(this.storyInfo.content_rating_text) - let matchCategory = /(.*?)<\/a>/g - for (let c; (c = matchCategory.exec(catsHtml));) { - let cat = { - url: 'http://www.fimfiction.net' + c[1], - className: c[2], - name: entities.decode(c[3]) + let matchCategory = /(.*?)<\/a>/g + for (let c; (c = matchCategory.exec(tagsHtml));) { + if (c[2] === 'tag-genre') { + let cat = { + url: 'http://www.fimfiction.net' + c[1], + className: 'story_category story_category_' + c[3], + name: entities.decode(c[4]) + } + categories.push(cat) + this.subjects.push(cat.name) + } else if (c[2] === 'tag-character') { + let t = { + url: 'http://www.fimfiction.net' + c[1], + // filename: 'tag-' + c[3], + name: entities.decode(c[4]) + // image: 'https://static.fimfiction.net/images/characters/' + entities.decode(c[3]).replace(/-/g, '_') + '.png' + } + tags.push(t) + // tags.byImage[t.image] = t + // this.remoteResources.set(t.image, {filename: t.filename, originalUrl: t.image, where: ['tags']}) + } else { + console.log(c) } - categories.push(cat) - this.subjects.push(cat.name) } this.categories = categories - ma = html.match(/This story is a sequel to (.*?)<\/a>/) + html = html.substring(endTagsPos + 5) + html = html.substring(html.indexOf('') + 38) + + let ma = html.match(/This story is a sequel to (.*?)<\/a>/) if (ma) { this.storyInfo.prequel = { url: 'http://www.fimfiction.net' + ma[1], @@ -569,7 +582,8 @@ class FimFic2Epub extends Emitter { } html = html.substring(html.indexOf('
') + 6) } - let endDescPos = html.indexOf('
\n') + + let endDescPos = html.indexOf('\n') let description = html.substring(0, endDescPos).trim() this.description = description @@ -586,8 +600,6 @@ class FimFic2Epub extends Emitter { html = html.substring(0, html.indexOf('
<\/a>/g for (let tag; (tag = matchTag.exec(html));) { let t = { @@ -616,10 +628,10 @@ class FimFic2Epub extends Emitter { authorNotes = authorNotes.replace(trimWhitespace, '') } - let chapterPos = html.indexOf('
') - let chapter = html.substring(chapterPos + 29) + let chapterPos = html.indexOf('
') + let chapter = html.substring(chapterPos + 20) - let pos = chapter.indexOf('\t
\t\t\n\t') + let pos = chapter.indexOf('\t\t
\n') chapter = chapter.substring(0, pos).trim() diff --git a/src/cleanMarkup.js b/src/cleanMarkup.js index 43e5cd3..2f08813 100644 --- a/src/cleanMarkup.js +++ b/src/cleanMarkup.js @@ -3,16 +3,7 @@ import m from 'mithril' import render from './lib/mithril-node-render' import fetch from './fetch' -import { tidyOptions, youtubeKey } from './constants' - -import isNode from 'detect-node' - -let tidy -if (!isNode) { - tidy = require('exports-loader?tidy_html5!tidy-html5') -} else { - tidy = require('tidy-html5').tidy_html5 -} +import { youtubeKey } from './constants' export function cleanMarkup (html) { if (!html) { @@ -20,11 +11,23 @@ export function cleanMarkup (html) { } return new Promise((resolve, reject) => { + // replace HTML non-breaking spaces with normal spaces + html = html.replace(/ /g, ' ') + html = html.replace(/ /g, ' ') + html = fixParagraphIndent(html) + html = fixDoubleSpacing(html) + // fix center tags - html = html.replace(/
/g, '

') - html = html.replace(/<\/center>/g, '

') + // html = html.replace(/
/g, '

') + // html = html.replace(/<\/center>/g, '

') + + html = html.replace(/

\s*/g, '

') + html = html.replace(/\s*<\/p>/g, '

') + + html = html.replace(/

/g, '

') + html = html.replace(/<\/div><\/p>/g, '

') // fix floating blockquote tags html = html.replace('
', '
') @@ -101,13 +104,7 @@ export function cleanMarkup (html) { } function continueParsing () { - html = tidy(html, tidyOptions).trim() - - // replace HTML non-breaking spaces with normal spaces - html = html.replace(/ /g, ' ') - html = html.replace(/ /g, ' ') - - html = fixDoubleSpacing(html) + // html = tidy(html, tidyOptions).trim() resolve(html) } diff --git a/src/fetch.js b/src/fetch.js index 140f0c4..fd15899 100644 --- a/src/fetch.js +++ b/src/fetch.js @@ -37,7 +37,7 @@ export default function fetch (url, responseType) { window.fetch(url, { method: 'GET', mode: 'cors', - credentials: 'omit', + credentials: 'include', cache: 'default', redirect: 'follow' }).then((response) => { diff --git a/src/main.js b/src/main.js index 702d056..eadcfdc 100644 --- a/src/main.js +++ b/src/main.js @@ -33,11 +33,11 @@ let logoUrl = chrome.extension.getURL('fimfic2epub-logo.png') let ffc -let stories = document.querySelectorAll('.story_container .story_content_box') +let stories = document.querySelectorAll('.story_container') stories.forEach((story) => { - let id = story.id.substring(6) - let epubButton = story.querySelector('ul.chapters li.bottom a[title="Download Story (.epub)"]') + let id = story.dataset.story + let epubButton = story.querySelector('.story-top-toolbar .button-group .drop-down ul li a[title="Download Story (.epub)"]') if (!epubButton) return epubButton.addEventListener('click', function (e) { e.preventDefault() @@ -47,7 +47,7 @@ stories.forEach((story) => { logo.className = 'fimfic2epub-logo' logo.title = 'Download EPUB with fimfic2epub' logo.src = logoUrl - story.querySelector('.title').appendChild(logo) + story.querySelector('.story_content_box .title').appendChild(logo) logo.addEventListener('click', function (e) { e.preventDefault() openStory(id) @@ -89,6 +89,15 @@ let checkbox = { } } +function selectOptions (list, selected = '') { + return list.map((item) => { + return m('option', { + value: item[0], + selected: selected === item[0] + }, item[1]) + }) +} + let ffcProgress = m.prop(0) let ffcStatus = m.prop('') @@ -117,6 +126,7 @@ let dialog = { this.addChapterHeadings = m.prop(ffc.options.addChapterHeadings) this.includeExternal = m.prop(ffc.options.includeExternal) this.joinSubjects = m.prop(ffc.options.joinSubjects) + this.paragraphStyle = m.prop(ffc.options.paragraphStyle) this.onOpen = function (el, isInitialized) { if (!isInitialized) { @@ -219,6 +229,7 @@ let dialog = { ffc.options.useAuthorNotesIndex = this.useAuthorNotesIndex() ffc.options.addChapterHeadings = this.addChapterHeadings() ffc.options.includeExternal = this.includeExternal() + ffc.options.paragraphStyle = this.paragraphStyle() ffc.subjects = this.subjects() ffc.options.joinSubjects = this.joinSubjects() m.redraw() @@ -240,7 +251,7 @@ let dialog = { }, 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', {style: {'min-width': '700px'}}, [ 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', [ @@ -253,6 +264,14 @@ let dialog = { ), m('td', {style: 'width: 1px'}, m(checkbox, {checked: ctrl.checkboxCoverUrl(), onchange: m.withAttr('checked', ctrl.checkboxCoverUrl)}, 'Use image URL')) ), + m('tr', m('td.label', 'Paragraph style'), m('td', {colspan: 2}, + m('select', {onchange: m.withAttr('value', ctrl.paragraphStyle)}, selectOptions([ + ['indented', 'Indent first line in all paragraphs except the first (Traditional Paperback)'], + ['spaced', 'Separate each paragraph with double space (Traditional Web)'], + ['both', 'Double space and indent all paragraphs except first (Fusion)'], + ['indentedall', 'Indent all paragraphs including the first (Modified Traditional)'] + ], ctrl.paragraphStyle())) + )), 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)'), diff --git a/src/coverstyle.styl b/src/style/coverstyle.styl similarity index 100% rename from src/coverstyle.styl rename to src/style/coverstyle.styl diff --git a/src/mixins.styl b/src/style/mixins.styl similarity index 100% rename from src/mixins.styl rename to src/style/mixins.styl diff --git a/src/style/paragraphs-indented.styl b/src/style/paragraphs-indented.styl new file mode 100644 index 0000000..b8175cd --- /dev/null +++ b/src/style/paragraphs-indented.styl @@ -0,0 +1,8 @@ + +p + p { + text-indent: 1em; +} + +.chapter-title + p.indented { + text-indent: 0; +} diff --git a/src/style/paragraphs-indentedall.styl b/src/style/paragraphs-indentedall.styl new file mode 100644 index 0000000..482ab17 --- /dev/null +++ b/src/style/paragraphs-indentedall.styl @@ -0,0 +1,4 @@ + +p { + text-indent: 1em; +} diff --git a/src/style/paragraphs-spaced.styl b/src/style/paragraphs-spaced.styl new file mode 100644 index 0000000..9ba6602 --- /dev/null +++ b/src/style/paragraphs-spaced.styl @@ -0,0 +1,12 @@ + +p { + margin-top: 1em; +} + +.chapter-title + p { + margin-top: 0; +} + +.bbcode-center, .bbcode-right { + margin-top: 1em; +} diff --git a/src/style.styl b/src/style/style.styl similarity index 92% rename from src/style.styl rename to src/style/style.styl index bc67743..714618c 100644 --- a/src/style.styl +++ b/src/style/style.styl @@ -8,26 +8,39 @@ body { } p { - margin-top: 0.0em; - margin-bottom: 0.0em; - text-indent: 0.0em; + margin-top: 0; + margin-bottom: 0; + text-indent: 0; &.double { - margin-top: 1.0em; + margin-top: 1em; } &.double2 { - margin-top: 1.0em; - margin-bottom: 1.0em; + margin-top: 1em; + margin-bottom: 1em; } &.indented { - text-indent: 1.0em; + text-indent: 1em; } + &:first-child { + margin-top: 0; + } +} +div p { + text-indent: 0; } h1, h2, h3, h4, h5, h6 { text-align: left; } +em { + font-style: italic; +} +em em { + font-style: normal; +} + .chapter-title { margin-top: 1.5em; margin-bottom: 1.5em; @@ -37,9 +50,6 @@ h1, h2, h3, h4, h5, h6 { font-weight: normal; margin: 0; } - & + p.indented { - text-indent: 0; - } } img { diff --git a/src/titlestyle.styl b/src/style/titlestyle.styl similarity index 96% rename from src/titlestyle.styl rename to src/style/titlestyle.styl index b52ae60..a1c3f0c 100644 --- a/src/titlestyle.styl +++ b/src/style/titlestyle.styl @@ -52,6 +52,7 @@ .characters { margin-top: 0.5em; text-align: left; + font-size: .875em; } // Last Modified and First Published @@ -172,6 +173,18 @@ span.character_icon { font-family: sans-serif; } +.story_character { + display: inline-block; + padding: 0.3em 0.5em; + line-height: 1.0em; + bgcolor(#23b974); + textcolor(#fff); + text-decoration: none; + border-radius: 0.3em; + border: 1px solid #1e9d63; + font-family: sans-serif; +} + .story_category_sex { bgcolor(#992584); box-shadow: 0px 1px 0px #b82c9e inset; diff --git a/src/styles.js b/src/styles.js index 3cd2d29..4affcb2 100644 --- a/src/styles.js +++ b/src/styles.js @@ -1,8 +1,15 @@ let styleCss, coverstyleCss, titlestyleCss -styleCss = require('./style') -coverstyleCss = require('./coverstyle') -titlestyleCss = require('./titlestyle') +styleCss = require('./style/style') +coverstyleCss = require('./style/coverstyle') +titlestyleCss = require('./style/titlestyle') +let paragraphsCss = { + spaced: require('./style/paragraphs-spaced'), + indented: require('./style/paragraphs-indented'), + indentedall: require('./style/paragraphs-indentedall') +} -export { styleCss, coverstyleCss, titlestyleCss } +paragraphsCss.both = paragraphsCss.indented + '\n' + paragraphsCss.spaced + +export { styleCss, coverstyleCss, titlestyleCss, paragraphsCss } diff --git a/src/templates.js b/src/templates.js index 6facd82..86e6221 100644 --- a/src/templates.js +++ b/src/templates.js @@ -351,7 +351,8 @@ export function createTitlePage (ffc) { // m('hr'), m('.characters', [ ffc.tags.map((t) => - m('span', {className: 'character_icon', title: t.name}, m('img', {src: t.image, className: 'character_icon'})) + // m('span', {className: 'character_icon', title: t.name}, m('img', {src: t.image, className: 'character_icon'})) + m('span.story_character', t.name) ) ]) ]) diff --git a/webpack.config.babel.js b/webpack.config.babel.js index d79fb91..c1b1afa 100644 --- a/webpack.config.babel.js +++ b/webpack.config.babel.js @@ -30,9 +30,6 @@ const bundleExtensionConfig = { test: /\.styl$/, use: ['raw-loader', 'stylus-loader'] } - ], - noParse: [ - /[/\\]node_modules[/\\]tidy-html5[/\\]tidy\.js$/ ] }, @@ -47,7 +44,7 @@ const bundleExtensionConfig = { } }, - externals: ['request', 'tidy-html5'], + externals: ['request'], plugins: [], devtool: 'source-map' @@ -79,9 +76,6 @@ const bundleNpmModuleConfig = { test: /\.styl$/, use: ['raw-loader', 'stylus-loader'] } - ], - noParse: [ - /[/\\]node_modules[/\\]tidy-html5[/\\]tidy\.js$/ ] }, @@ -93,7 +87,7 @@ const bundleNpmModuleConfig = { ] }, - externals: [nodeExternals({whitelist: ['es6-event-emitter', /^babel-runtime/]}), 'exports?tidy_html5!tidy-html5'], + externals: [nodeExternals({whitelist: ['es6-event-emitter', /^babel-runtime/]})], plugins: [], devtool: 'source-map'