From 9ad70f66b15c0f837b633f001fef49a209c4d15a Mon Sep 17 00:00:00 2001 From: daniel-j Date: Fri, 19 Aug 2016 16:51:40 +0200 Subject: [PATCH] button to chapter comments, calculate word count (unused atm), clean up cleanup code --- package.json | 9 +-- src/FimFic2Epub.js | 2 +- src/cleanMarkup.js | 128 ++++++++++++++++++++++-------------------- src/html-wordcount.js | 12 ++++ src/style.styl | 27 +++++++++ src/templates.js | 61 +++++++++++--------- 6 files changed, 148 insertions(+), 91 deletions(-) create mode 100644 src/html-wordcount.js diff --git a/package.json b/package.json index bcce920..f32957a 100644 --- a/package.json +++ b/package.json @@ -14,19 +14,19 @@ "fimfic2epub": "./bin/fimfic2epub" }, "main": "fimfic2epub.js", - "files": [ "fimfic2epub.js", "fimfic2epub.js.map", "bin/" ], - "dependencies": { "detect-node": "^2.0.3", "escape-string-regexp": "^1.0.5", "html-entities": "^1.2.0", + "html-to-text": "^2.1.3", "image-size": "^0.5.0", "jszip": "^3.1.1", + "match-words": "^0.1.0", "mithril": "^0.2.5", "pretty-data": "^0.40.0", "request": "^2.74.0", @@ -59,11 +59,12 @@ "webpack": "^2.1.0-beta.20", "webpack-node-externals": "^1.3.3" }, - "standard": { "env": { "browser": true }, - "globals": ["FIMFIC2EPUB_VERSION"] + "globals": [ + "FIMFIC2EPUB_VERSION" + ] } } diff --git a/src/FimFic2Epub.js b/src/FimFic2Epub.js index b1a803f..38e30d8 100644 --- a/src/FimFic2Epub.js +++ b/src/FimFic2Epub.js @@ -211,7 +211,7 @@ module.exports = class FimFic2Epub { } this.tags = tags - cleanMarkup(description, (html) => { + cleanMarkup(description).then((html) => { this.storyInfo.description = html this.findRemoteResources('description', 'description', html) resolve() diff --git a/src/cleanMarkup.js b/src/cleanMarkup.js index ed9de42..54f246a 100644 --- a/src/cleanMarkup.js +++ b/src/cleanMarkup.js @@ -14,80 +14,86 @@ if (!isNode) { tidy = require('tidy-html5').tidy_html5 } -export function cleanMarkup (html, callback) { - // fix center tags - html = html.replace(/
/g, '

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

') +export function cleanMarkup (html) { + if (!html) { + return Promise.resolve('') + } - // replace HTML non-breaking spaces with normal spaces - html = html.replace(/ /g, ' ') + return new Promise((resolve, reject) => { + // fix center tags + html = html.replace(/
/g, '

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

') - html = fixParagraphIndent(html) + // replace HTML non-breaking spaces with normal spaces + html = html.replace(/ /g, ' ') - html = fixDoubleSpacing(html) + html = fixParagraphIndent(html) - let cache = new Map() - let completeCount = 0 + html = fixDoubleSpacing(html) - function getYoutubeInfo (ids) { - fetch('https://www.googleapis.com/youtube/v3/videos?id=' + ids + '&part=snippet&maxResults=50&key=' + youtubeKey, (raw, type) => { - let data - try { - data = JSON.parse(raw).items - } catch (e) { + let cache = new Map() + let completeCount = 0 - } - data.forEach((video) => { - cache.set(video.id, video.snippet) - completeCount++ + function getYoutubeInfo (ids) { + fetch('https://www.googleapis.com/youtube/v3/videos?id=' + ids + '&part=snippet&maxResults=50&key=' + youtubeKey, (raw, type) => { + let data + try { + data = JSON.parse(raw).items + } catch (e) { + + } + data.forEach((video) => { + cache.set(video.id, video.snippet) + completeCount++ + }) + if (completeCount === cache.size) { + continueParsing() + } }) - if (completeCount === cache.size) { - continueParsing() - } - }) - } + } - let matchYoutube = /
(.+?)<\/div>/g - for (let ma; (ma = matchYoutube.exec(html));) { - let youtubeId = ma[1].match(/src="https:\/\/www.youtube.com\/embed\/(.+?)"/)[1] - cache.set(youtubeId, null) - } + let matchYoutube = /
(.+?)<\/div>/g + for (let ma; (ma = matchYoutube.exec(html));) { + let youtubeId = ma[1].match(/src="https:\/\/www.youtube.com\/embed\/(.+?)"/)[1] + cache.set(youtubeId, null) + } - if (cache.size === 0) { - continueParsing() - } else { - getYoutubeInfo([...cache.keys()]) - } + if (cache.size === 0) { + continueParsing() + } else { + getYoutubeInfo([...cache.keys()]) + } - function continueParsing () { - html = html.replace(matchYoutube, (match, contents) => { - // console.log(match, contents) - let youtubeId = contents.match(/src="https:\/\/www.youtube.com\/embed\/(.+?)"/)[1] - let thumbnail = 'http://img.youtube.com/vi/' + youtubeId + '/hqdefault.jpg' - let youtubeUrl = 'https://youtube.com/watch?v=' + youtubeId - let title = 'Youtube Video' - let caption = '' - let data = cache.get(youtubeId) - if (data) { - thumbnail = (data.thumbnails.standard || data.thumbnails.high || data.thumbnails.medium || data.thumbnails.default).url - title = data.title - caption = data.title + ' on YouTube' - } - return render(m('figure.youtube', [ - m('a', {href: youtubeUrl}, - m('img', {src: thumbnail, alt: title}) - ), - m('figcaption', m('a', {href: youtubeUrl}, caption)) - ])) - }) + function continueParsing () { + html = html.replace(matchYoutube, (match, contents) => { + // console.log(match, contents) + let youtubeId = contents.match(/src="https:\/\/www.youtube.com\/embed\/(.+?)"/)[1] + let thumbnail = 'http://img.youtube.com/vi/' + youtubeId + '/hqdefault.jpg' + let youtubeUrl = 'https://youtube.com/watch?v=' + youtubeId + let title = 'Youtube Video' + let caption = '' + let data = cache.get(youtubeId) + if (data) { + thumbnail = (data.thumbnails.standard || data.thumbnails.high || data.thumbnails.medium || data.thumbnails.default).url + title = data.title + caption = data.title + ' on YouTube' + } + return render(m('figure.youtube', [ + m('a', {href: youtubeUrl}, + m('img', {src: thumbnail, alt: title}) + ), + m('figcaption', m('a', {href: youtubeUrl}, caption)) + ])) + }) - html = html.replace('
', '
') - html = html.replace('
', '
') + html = html.replace('
', '
') + html = html.replace('
', '
') - html = tidy(`\n` + html, tidyOptions) + html = tidy(html, tidyOptions).trim() - callback(html) - } + resolve(html) + } + }) } export function fixDoubleSpacing (html) { diff --git a/src/html-wordcount.js b/src/html-wordcount.js new file mode 100644 index 0000000..8a201e0 --- /dev/null +++ b/src/html-wordcount.js @@ -0,0 +1,12 @@ +import htmlToText from 'html-to-text' +import matchWords from 'match-words' + +export default function htmlWordCount (html) { + let text = htmlToText.fromString(html, { + wordwrap: false, + ignoreImage: true, + ignoreHref: true + }) + + return matchWords(text).length +} diff --git a/src/style.styl b/src/style.styl index b37a29b..9ee338c 100644 --- a/src/style.styl +++ b/src/style.styl @@ -145,6 +145,33 @@ figure.youtube { } } +a.chaptercomments { + text-decoration: none; + display: inline-block; + position: relative; + margin: 1em; + padding: 0.5em 1em; + cursor: pointer; + + textcolor(#fff); + font-weight: normal; + font-family: sans-serif; + font-size: 0.9em; + text-shadow: -1px -1px #699739; + + box-shadow: 0px 1px #81b945 inset; + border-radius: 3px; + border: 1px solid #699739; + border-bottom-color: #638f36; + border-top-color: #6fa03c; + outline: none; + + bgcolor(#7bb042); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #7bb042), color-stop(100%, #6fa03c)); + background: -webkit-linear-gradient(top, #7bb042 0%, #6fa03c 100%); + background: linear-gradient(to bottom, #7bb042 0%, #6fa03c 100%); +} + #toc { ol { list-style-type: none; diff --git a/src/templates.js b/src/templates.js index 2e0e287..90effd9 100644 --- a/src/templates.js +++ b/src/templates.js @@ -4,6 +4,7 @@ import render from './lib/mithril-node-render' import { pd as pretty } from 'pretty-data' import zeroFill from 'zero-fill' +import htmlWordCount from './html-wordcount' import { cleanMarkup } from './cleanMarkup' import { NS } from './constants' @@ -46,34 +47,44 @@ export function createChapter (ch, html, callback) { // remove leading and trailing
tags and whitespace chapter = chapter.replace(trimWhitespace, '') - let sections = [ - [ - m('.chapter-title', [ - m('h1', ch.title), - m('hr') - ]), - m.trust(chapter) - ], - authorNotes ? m('div#author_notes', {className: authorNotesPos < chapterPos ? 'top' : 'bottom'}, m.trust(authorNotes)) : null - ] + Promise.all([cleanMarkup(chapter), cleanMarkup(authorNotes)]).then((values) => { + let [cleanChapter, cleanAuthorNotes] = values - if (authorNotes && authorNotesPos < chapterPos) { - sections.reverse() - } + ch.realWordCount = htmlWordCount(cleanChapter) - let chapterPage = '' + render( - m('html', {xmlns: NS.XHTML}, [ - m('head', [ - m('meta', {charset: 'utf-8'}), - m('link', {rel: 'stylesheet', type: 'text/css', href: '../Styles/style.css'}), - m('title', ch.title) - ]), - m('body', sections) - ]) - ) + let content = [ + m.trust(cleanChapter), + cleanAuthorNotes ? m('div#author_notes', {className: authorNotesPos < chapterPos ? 'top' : 'bottom'}, [ + m('p', m('b', 'Author\'s Note:')), + m.trust(cleanAuthorNotes)]) : null + ] - cleanMarkup(chapterPage, (html) => { - callback(html) + // if author notes are a the beginning of the chapter + if (cleanAuthorNotes && authorNotesPos < chapterPos) { + content.reverse() + } + + let chapterPage = '' + render( + m('html', {xmlns: NS.XHTML}, [ + m('head', [ + m('meta', {charset: 'utf-8'}), + m('link', {rel: 'stylesheet', type: 'text/css', href: '../Styles/style.css'}), + m('title', ch.title) + ]), + m('body', [ + m('.chapter-title', [ + m('h1', ch.title), + m('hr') + ]), + content, + m('p.double', {style: 'text-align: center; clear: both;'}, + m('a.chaptercomments', {href: ch.link + '#comment_list'}, 'Read chapter comments online') + ) + ]) + ]) + ) + + callback(chapterPage) }) }