2016-06-24 01:26:01 +12:00
import m from 'mithril'
2017-10-19 02:02:07 +13:00
import render from 'mithril-node-render'
2016-06-24 01:26:01 +12:00
import { pd as pretty } from 'pretty-data'
import zeroFill from 'zero-fill'
import { NS } from './constants'
2016-06-28 09:19:01 +12:00
function nth ( d ) {
if ( d > 3 && d < 21 ) return 'th'
switch ( d % 10 ) {
case 1 : return 'st'
case 2 : return 'nd'
case 3 : return 'rd'
default : return 'th'
2016-06-24 01:26:01 +12:00
}
}
2019-10-08 22:26:52 +13:00
export function prettyDate ( d ) {
2016-06-28 09:19:01 +12:00
// format: 27th Oct 2011
2019-10-08 22:31:42 +13:00
const months = [ 'January' , 'February' , 'March' , 'April' , 'May' , 'June' , 'July' , 'August' , 'September' , 'October' , 'November' , 'December' ]
2020-03-04 22:55:45 +13:00
return d . getDate ( ) + nth ( d . getDate ( ) ) + ' ' + months [ d . getMonth ( ) ] . substring ( 0 , 3 ) + ' ' + d . getFullYear ( )
2016-06-28 09:19:01 +12:00
}
2019-10-08 22:26:52 +13:00
const metaGenerator = ( ) => m ( 'meta' , { name : 'generator' , content : 'Generated by fimfic2epub v' + FIMFIC2EPUB _VERSION + ' at ' + prettyDate ( new Date ( ) ) } )
2018-03-16 10:57:14 +13:00
function chapterBars ( chapters , currentChapter = - 1 , highlightCurrent = false ) {
2018-03-16 23:43:12 +13:00
if ( chapters . length <= 1 ) return null
2018-03-16 10:57:14 +13:00
let windowSize = 50
2018-03-16 04:14:57 +13:00
let wordCounts = [ ]
2019-10-08 22:31:42 +13:00
const highestWordCount = chapters . reduce ( ( max , ch ) => {
2018-03-16 04:14:57 +13:00
wordCounts . push ( ch . realWordCount )
if ( ch . realWordCount > max ) return ch . realWordCount
return max
} , 0 )
2018-03-16 10:57:14 +13:00
if ( wordCounts . length > windowSize && currentChapter >= 0 && currentChapter < wordCounts . length ) {
windowSize = 30
let start = Math . ceil ( Math . max ( 0 , currentChapter - windowSize / 2 + 1 ) )
start = Math . min ( start , wordCounts . length - windowSize )
wordCounts . splice ( 0 , start )
wordCounts . length = Math . min ( wordCounts . length , windowSize )
currentChapter -= start
}
2018-03-16 04:14:57 +13:00
wordCounts = wordCounts . map ( ( c ) => c / highestWordCount )
2019-10-08 22:31:42 +13:00
const barWidth = 9
const barSpacing = 2
const rowSpacing = 9
2018-03-16 10:57:14 +13:00
const barCount = Math . min ( wordCounts . length , windowSize )
const rows = Math . floor ( wordCounts . length / barCount ) + 1
const rowHeight = 30 + rowSpacing
2018-03-16 04:14:57 +13:00
return m ( 'svg.chapterbars' , {
2019-10-08 19:37:27 +13:00
style : { height : rows * 3 + 'em' } ,
2018-03-16 10:57:14 +13:00
viewBox : '0 0 ' + barCount * ( barWidth + barSpacing ) + ' ' + rowHeight * rows ,
2018-03-16 04:14:57 +13:00
xmlns : NS . SVG ,
fill : 'currentColor'
} , wordCounts . map ( ( c , i ) => {
2018-03-16 10:57:14 +13:00
const x = i % barCount
const y = Math . floor ( i / barCount )
const height = Math . ceil ( c * ( rowHeight - rowSpacing ) )
let opacity = 0.65
2018-03-16 04:14:57 +13:00
if ( i === currentChapter && highlightCurrent ) {
2018-03-16 10:57:14 +13:00
opacity = 0.85
2018-03-16 04:14:57 +13:00
} else if ( i > currentChapter ) {
2018-03-16 10:57:14 +13:00
opacity = 0.35
2018-03-16 04:14:57 +13:00
}
2019-10-08 19:37:27 +13:00
return m ( 'rect' , { x : x * ( barWidth + barSpacing ) , width : barWidth , y : y * rowHeight + ( rowHeight - rowSpacing - height ) , height , opacity } )
2018-03-16 04:14:57 +13:00
} ) )
}
export function createChapter ( ffc , ch , isNotesChapter ) {
2019-10-08 22:31:42 +13:00
const { content , notes , notesFirst , title , link , linkNotes , index , showHeadings , showDuration , showWordCount } = ch
2016-08-20 02:51:40 +12:00
2019-10-08 22:31:42 +13:00
const sections = [
2017-10-19 02:02:07 +13:00
m . trust ( content || '' ) ,
2019-10-08 19:37:27 +13:00
notes ? m ( 'div#author_notes' , { className : notesFirst ? 'top' : 'bottom' } , [
2017-10-19 02:02:07 +13:00
m ( 'p' , m ( 'b' , 'Author\'s Note:' ) ) ,
m . trust ( notes ) ] ) : null
]
2016-08-20 02:51:40 +12:00
2017-10-19 02:02:07 +13:00
// if author notes are a the beginning of the chapter
if ( notes && notesFirst ) {
sections . reverse ( )
}
2016-08-20 02:51:40 +12:00
2020-08-10 19:45:04 +12:00
return render (
m ( 'html' , { xmlns : NS . XHTML , 'xmlns:epub' : NS . OPS , lang : 'en' , 'xml:lang' : 'en' } , [
m ( 'head' , [
m ( 'meta' , { charset : 'utf-8' } ) ,
metaGenerator ( ) ,
m ( 'link' , { rel : 'stylesheet' , type : 'text/css' , href : '../Styles/style.css' } ) ,
m ( 'title' , title )
] ) ,
m ( 'body' , { 'epub:type' : 'bodymatter chapter' } , m ( 'div' , [
showHeadings ? m ( '.chapter-title' , [
! isNotesChapter && ( showDuration || showWordCount ) ? m ( 'aside.info' ,
showDuration ? m ( 'span.label' , ffc . options . wordsPerMinute ? calcReadingTime ( ffc , ffc . storyInfo . chapters [ index ] . realWordCount ) : '' ) : null ,
showWordCount ? m ( 'span.label' , ffc . storyInfo . chapters [ index ] . realWordCount . toLocaleString ( 'en-GB' ) + ' words' ) : null
2018-03-16 04:14:57 +13:00
) : null ,
2020-08-10 19:45:04 +12:00
m ( 'header' , m ( 'h1' , title ) ) ,
m ( 'hr.old' )
] ) : null ,
... sections ,
( link || linkNotes || isNotesChapter ) ? m ( 'p.double' , { style : 'text-align: center; clear: both;' } ,
link ? m ( 'a.chaptercomments' , { href : link + '#comment_list' } , 'Read chapter comments online' ) : null ,
linkNotes ? m ( 'a.chaptercomments' , { href : linkNotes } , 'Read author\'s note' ) : null ,
isNotesChapter ? m ( 'a.chaptercomments' , { href : './chapter_' + zeroFill ( 3 , index + 1 ) + '.xhtml' } , 'Read chapter' ) : null
) : null ,
! isNotesChapter && ffc . options . addChapterBars ? chapterBars ( ffc . storyInfo . chapters , index ) : null
] ) )
] )
, { strict : true } ) . then ( ( chapterPage ) => '<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE html>\n' + chapterPage )
2016-06-28 09:19:01 +12:00
}
2016-08-19 22:35:58 +12:00
// some eReaders doesn't understand linear=no, so push those items to the end of the spine/book.
function sortSpineItems ( items ) {
let count = items . length
for ( let i = 0 ; i < count ; i ++ ) {
2019-10-08 22:31:42 +13:00
const item = items [ i ]
2016-08-24 02:32:55 +12:00
if ( ! item ) {
continue
}
2016-08-19 22:35:58 +12:00
if ( item . attrs . linear === 'no' ) {
// push it to the end
items . splice ( i , 1 )
items . push ( item )
count --
i --
}
2016-08-19 20:57:53 +12:00
}
2016-08-19 22:35:58 +12:00
return items
}
2016-08-19 20:57:53 +12:00
2016-06-28 09:19:01 +12:00
export function createOpf ( ffc ) {
2019-10-08 22:31:42 +13:00
const remotes = [ ]
2016-08-29 22:12:04 +12:00
// let remoteCounter = 0
2019-10-08 22:31:42 +13:00
const remoteCache = new Set ( )
2016-06-28 09:19:01 +12:00
ffc . remoteResources . forEach ( ( r , url ) => {
2016-08-29 22:12:04 +12:00
// remoteCounter++
2016-08-25 00:47:48 +12:00
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
}
2016-06-24 01:26:01 +12:00
if ( ! r . dest ) {
return
}
2018-03-16 23:43:12 +13:00
// only add each file once
if ( remoteCache . has ( r . dest ) ) return
remoteCache . add ( r . dest )
2019-10-08 19:37:27 +13:00
remotes . push ( m ( 'item' , { id : r . filename , href : r . dest , 'media-type' : r . type } ) )
2016-06-24 01:26:01 +12:00
} )
2019-10-08 22:31:42 +13:00
const manifestChapters = ffc . storyInfo . chapters . map ( ( ch , num ) =>
2019-10-08 19:37:27 +13:00
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' : '' ) + ( ffc . options . addChapterBars ? ' svg' : '' ) ) . trim ( ) || null } )
2016-08-30 02:20:20 +12:00
)
2019-10-08 22:31:42 +13:00
const spineChapters = ffc . storyInfo . chapters . map ( ( ch , num ) =>
2019-10-08 19:37:27 +13:00
m ( 'itemref' , { idref : 'chapter_' + zeroFill ( 3 , num + 1 ) } )
2016-08-30 02:20:20 +12:00
)
2019-10-08 22:31:42 +13:00
const manifestNotes = [ ]
const spineNotes = [ ]
2016-08-30 02:20:20 +12:00
if ( ffc . options . includeAuthorNotes && ffc . options . useAuthorNotesIndex && ffc . hasAuthorNotes ) {
2019-10-08 19:37:27 +13:00
spineNotes . push ( m ( 'itemref' , { idref : 'notesnav' } ) )
2016-08-30 02:20:20 +12:00
ffc . chaptersWithNotes . forEach ( ( num ) => {
2019-10-08 22:31:42 +13:00
const id = 'note_' + zeroFill ( 3 , num + 1 )
2019-10-08 19:37:27 +13:00
manifestNotes . push ( m ( 'item' , { id : id , href : 'Text/' + id + '.xhtml' , 'media-type' : 'application/xhtml+xml' } ) )
spineNotes . push ( m ( 'itemref' , { idref : id } ) )
2016-08-30 02:20:20 +12:00
} )
}
2016-08-25 00:47:48 +12:00
let subjects = ffc . subjects
if ( ffc . options . joinSubjects ) {
subjects = [ subjects . join ( ', ' ) ]
}
2017-10-19 02:02:07 +13:00
return render (
2019-10-08 19:37:27 +13:00
m ( 'package' , { xmlns : NS . OPF , version : '3.0' , 'unique-identifier' : 'BookId' } , [
m ( 'metadata' , { 'xmlns:dc' : NS . DC , 'xmlns:opf' : NS . OPF } , [
2016-06-28 09:19:01 +12:00
m ( 'dc:identifier#BookId' , ffc . storyInfo . uuid ) ,
m ( 'dc:title' , ffc . storyInfo . title ) ,
m ( 'dc:creator#cre' , ffc . storyInfo . author . name ) ,
2019-10-08 19:37:27 +13:00
m ( 'meta' , { refines : '#cre' , property : 'role' , scheme : 'marc:relators' } , 'aut' ) ,
2016-06-28 09:19:01 +12:00
m ( 'dc:date' , new Date ( ( ffc . storyInfo . publishDate || ffc . storyInfo . date _modified ) * 1000 ) . toISOString ( ) . substring ( 0 , 10 ) ) ,
2016-06-24 01:26:01 +12:00
m ( 'dc:publisher' , 'Fimfiction' ) ,
2018-03-13 10:09:08 +13:00
ffc . storyInfo . short _description ? m ( 'dc:description' , ffc . storyInfo . short _description ) : null ,
2016-06-28 09:19:01 +12:00
m ( 'dc:source' , ffc . storyInfo . url ) ,
2016-06-24 01:26:01 +12:00
m ( 'dc:language' , 'en' ) ,
2019-10-08 19:37:27 +13:00
ffc . coverImage ? m ( 'meta' , { name : 'cover' , content : 'cover' } ) : null ,
m ( 'meta' , { property : 'dcterms:modified' } , new Date ( ffc . storyInfo . date _modified * 1000 ) . toISOString ( ) . replace ( '.000' , '' ) )
2016-08-25 00:47:48 +12:00
] . concat ( subjects . map ( ( s ) =>
m ( 'dc:subject' , s )
2019-10-08 19:37:27 +13:00
) , m ( 'meta' , { name : 'fimfic2epub version' , content : FIMFIC2EPUB _VERSION } ) ) ) ,
2016-06-24 01:26:01 +12:00
m ( 'manifest' , [
2019-10-08 19:37:27 +13:00
ffc . coverImage ? m ( 'item' , { id : 'cover' , href : ffc . coverFilename , 'media-type' : ffc . coverType , properties : 'cover-image' } ) : null ,
m ( 'item' , { id : 'ncx' , href : 'toc.ncx' , 'media-type' : 'application/x-dtbncx+xml' } ) ,
2019-10-08 22:31:42 +13:00
m ( 'item' , { id : 'nav' , href : 'nav.xhtml' , 'media-type' : 'application/xhtml+xml' , properties : 'nav' + ( ffc . options . addChapterBars && ffc . storyInfo . chapters . length > 1 ? ' svg' : '' ) } ) ,
ffc . options . includeAuthorNotes && ffc . options . useAuthorNotesIndex && ffc . hasAuthorNotes ? m ( 'item' , { id : 'notesnav' , href : 'notesnav.xhtml' , 'media-type' : 'application/xhtml+xml' } ) : null ,
2016-08-24 02:32:55 +12:00
2019-10-08 19:37:27 +13:00
m ( 'item' , { id : 'style' , href : 'Styles/style.css' , 'media-type' : 'text/css' } ) ,
m ( 'item' , { id : 'coverstyle' , href : 'Styles/coverstyle.css' , 'media-type' : 'text/css' } ) ,
m ( 'item' , { id : 'titlestyle' , href : 'Styles/titlestyle.css' , 'media-type' : 'text/css' } ) ,
m ( 'item' , { id : 'navstyle' , href : 'Styles/navstyle.css' , 'media-type' : 'text/css' } ) ,
2018-03-13 10:09:08 +13:00
2019-10-09 21:30:02 +13:00
ffc . iconsFont ? m ( 'item' , { id : 'font-awesome' , href : 'Fonts/fontawesome-webfont-subset.ttf' , 'media-type' : 'font/ttf' } ) : null ,
2016-06-28 09:19:01 +12:00
2019-10-08 19:37:27 +13:00
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' , properties : ffc . hasRemoteResources . titlePage ? 'remote-resources' : null } )
2016-06-28 09:19:01 +12:00
2016-08-30 02:20:20 +12:00
] . concat ( manifestChapters , manifestNotes , remotes ) ) ,
2016-06-24 01:26:01 +12:00
2019-10-08 19:37:27 +13:00
m ( 'spine' , { toc : 'ncx' } , sortSpineItems ( [
m ( 'itemref' , { idref : 'coverpage' } ) ,
m ( 'itemref' , { idref : 'titlepage' } ) ,
m ( 'itemref' , { idref : 'nav' , linear : ffc . storyInfo . chapters . length <= 1 && ! ( ffc . options . includeAuthorNotes && ffc . options . useAuthorNotesIndex && ffc . hasAuthorNotes ) ? 'no' : undefined } )
2016-08-30 02:20:20 +12:00
] . concat (
spineChapters ,
spineNotes
) ) ) ,
2016-06-28 09:19:01 +12:00
m ( 'guide' , [
2019-10-08 19:37:27 +13:00
m ( 'reference' , { type : 'cover' , title : 'Cover' , href : 'Text/cover.xhtml' } ) ,
m ( 'reference' , { type : 'toc' , title : 'Contents' , href : 'nav.xhtml' } )
2016-06-28 09:19:01 +12:00
] )
2016-06-24 01:26:01 +12:00
] )
2019-10-08 19:37:27 +13:00
, { strict : true } ) . then ( ( contentOpf ) => {
2018-05-09 06:26:37 +12:00
contentOpf = '<?xml version="1.0" encoding="utf-8"?>\n' + pretty . xml ( contentOpf )
return contentOpf
} )
2016-06-24 01:26:01 +12:00
}
function navPoints ( list ) {
2018-03-16 23:43:12 +13:00
let playOrder = 1
2019-10-08 22:31:42 +13:00
const arr = [ ]
2016-06-24 01:26:01 +12:00
for ( let i = 0 ; i < list . length ; i ++ ) {
2016-08-30 02:20:20 +12:00
if ( ! list [ i ] ) continue
2019-10-08 19:37:27 +13:00
arr . push ( m ( 'navPoint' , { id : 'navPoint-' + ( i + 1 ) , playOrder : playOrder ++ } , [
2016-06-24 01:26:01 +12:00
m ( 'navLabel' , m ( 'text' , list [ i ] [ 0 ] ) ) ,
2019-10-08 19:37:27 +13:00
m ( 'content' , { src : list [ i ] [ 1 ] } )
2016-06-24 01:26:01 +12:00
] ) )
}
return arr
}
2016-06-28 09:19:01 +12:00
export function createNcx ( ffc ) {
2017-10-19 02:02:07 +13:00
return render (
2019-10-08 19:37:27 +13:00
m ( 'ncx' , { version : '2005-1' , xmlns : NS . DAISY } , [
2016-06-24 01:26:01 +12:00
m ( 'head' , [
2019-10-08 19:37:27 +13:00
m ( 'meta' , { content : ffc . storyInfo . uuid , name : 'dtb:uid' } ) ,
m ( 'meta' , { content : 0 , name : 'dtb:depth' } ) ,
m ( 'meta' , { content : 0 , name : 'dtb:totalPageCount' } ) ,
m ( 'meta' , { content : 0 , name : 'dtb:maxPageNumber' } )
2016-06-24 01:26:01 +12:00
] ) ,
2016-06-28 09:19:01 +12:00
m ( 'docTitle' , m ( 'text' , ffc . storyInfo . title ) ) ,
2016-06-24 01:26:01 +12:00
m ( 'navMap' , navPoints ( [
2018-03-13 10:09:08 +13:00
[ 'Cover' , 'Text/cover.xhtml' ] ,
[ 'Title Page' , 'Text/title.xhtml' ] ,
2018-03-13 23:08:41 +13:00
ffc . storyInfo . chapters . length > 1 || ( ffc . options . includeAuthorNotes && ffc . options . useAuthorNotesIndex && ffc . hasAuthorNotes ) ? [ 'Contents' , 'nav.xhtml' ] : null
2016-06-28 09:19:01 +12:00
] . concat ( ffc . storyInfo . chapters . map ( ( ch , num ) =>
[ ch . title , 'Text/chapter_' + zeroFill ( 3 , num + 1 ) + '.xhtml' ]
2018-03-13 10:09:08 +13:00
) , ffc . options . includeAuthorNotes && ffc . options . useAuthorNotesIndex && ffc . hasAuthorNotes ? [ [ 'Author\'s Notes' , 'notesnav.xhtml' ] ] : null ) ) )
2016-06-24 01:26:01 +12:00
] )
2019-10-08 19:37:27 +13:00
, { strict : true } ) . then ( ( tocNcx ) => {
2018-05-09 06:26:37 +12:00
tocNcx = '<?xml version="1.0" encoding="utf-8" ?>\n' + pretty . xml ( tocNcx )
return tocNcx
} )
2016-06-24 01:26:01 +12:00
}
2018-03-13 10:09:08 +13:00
export function createNav ( ffc ) {
2019-10-08 22:31:42 +13:00
const list = [
2019-10-08 19:37:27 +13:00
m ( 'li' , m ( 'a' , { href : 'Text/cover.xhtml' } , 'Cover' ) ) ,
m ( 'li' , m ( 'a' , { href : 'Text/title.xhtml' } , 'Title Page' ) ) ,
ffc . storyInfo . chapters . length > 1 || ( ffc . options . includeAuthorNotes && ffc . options . useAuthorNotesIndex && ffc . hasAuthorNotes ) ? m ( 'li' , m ( 'a' , { href : 'nav.xhtml' } , 'Contents' ) ) : null
2018-03-13 10:09:08 +13:00
] . concat ( ffc . storyInfo . chapters . map ( ( ch , num ) =>
2018-03-15 23:37:18 +13:00
m ( 'li.leftalign' , [
2019-10-08 19:37:27 +13:00
m ( 'a' , { href : 'Text/chapter_' + zeroFill ( 3 , num + 1 ) + '.xhtml' } , ch . title )
2018-03-13 10:09:08 +13:00
] )
) )
2019-10-08 22:31:42 +13:00
const prettyList = ffc . storyInfo . chapters . map ( ( ch , num ) =>
2018-03-15 23:37:18 +13:00
m ( 'li.item' , [
2018-03-13 10:09:08 +13:00
m ( '.floatbox' , m ( 'span.wordcount' , ch . realWordCount . toLocaleString ( 'en-GB' ) ) ) ,
2019-10-08 19:37:27 +13:00
m ( 'a' , { href : 'Text/chapter_' + zeroFill ( 3 , num + 1 ) + '.xhtml' } , ch . title ) ,
2018-03-13 10:09:08 +13:00
m ( 'span.date' , [ m ( 'b' , ' · ' ) , prettyDate ( new Date ( ch . date _modified * 1000 ) ) ] )
] )
)
if ( ffc . options . includeAuthorNotes && ffc . options . useAuthorNotesIndex && ffc . hasAuthorNotes ) {
2019-10-08 19:37:27 +13:00
list . push ( m ( 'li' , m ( 'a' , { href : 'notesnav.xhtml' } , 'Author\'s Notes' ) ) )
prettyList . push ( m ( 'li.item.double' , m ( 'a' , { href : 'notesnav.xhtml' } , 'Author\'s Notes' ) ) )
2016-08-30 02:20:20 +12:00
}
2017-10-19 02:02:07 +13:00
return render (
2019-10-09 21:30:02 +13:00
m ( 'html' , { xmlns : NS . XHTML , 'xmlns:epub' : NS . OPS , lang : 'en' , 'xml:lang' : 'en' } , [
2016-06-24 01:26:01 +12:00
m ( 'head' , [
2019-10-08 19:37:27 +13:00
m ( 'meta' , { charset : 'utf-8' } ) ,
2019-10-08 22:26:52 +13:00
metaGenerator ( ) ,
2019-10-08 19:37:27 +13:00
m ( 'link' , { rel : 'stylesheet' , type : 'text/css' , href : 'Styles/style.css' } ) ,
m ( 'link' , { rel : 'stylesheet' , type : 'text/css' , href : 'Styles/navstyle.css' } ) ,
2018-03-13 10:09:08 +13:00
m ( 'title' , 'Contents' )
2016-06-24 01:26:01 +12:00
] ) ,
2019-10-08 19:37:27 +13:00
m ( 'body' , { 'epub:type' : 'frontmatter toc' } , m ( 'div' , [
2019-10-09 21:30:02 +13:00
m ( 'nav.invisible' , { 'epub:type' : 'toc' , role : 'doc-toc' } , m ( 'ol' , list ) ) ,
2018-03-13 10:09:08 +13:00
m ( 'h3' , 'Contents' ) ,
2018-03-15 23:37:18 +13:00
m ( 'ul#toc.hidden' , prettyList ) ,
2018-03-16 10:57:14 +13:00
ffc . options . addChapterBars ? chapterBars ( ffc . storyInfo . chapters ) : null
2018-03-13 10:09:08 +13:00
] ) )
2016-06-24 01:26:01 +12:00
] )
2019-10-08 19:37:27 +13:00
, { strict : true } ) . then ( ( navDocument ) => {
2018-05-09 06:26:37 +12:00
navDocument = '<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE html>\n' + pretty . xml ( navDocument )
return navDocument
} )
2018-03-13 10:09:08 +13:00
}
export function createNotesNav ( ffc ) {
2019-10-08 22:31:42 +13:00
const list = ffc . chaptersWithNotes . map ( ( num ) => {
const ch = ffc . storyInfo . chapters [ num ]
2019-10-08 19:37:27 +13:00
return m ( '.item' , m ( 'a.leftalign' , { href : 'Text/note_' + zeroFill ( 3 , num + 1 ) + '.xhtml' } , ch . title ) )
2017-10-19 02:02:07 +13:00
} )
2018-03-13 10:09:08 +13:00
return render (
2019-10-09 21:30:02 +13:00
m ( 'html' , { xmlns : NS . XHTML , 'xmlns:epub' : NS . OPS , lang : 'en' , 'xml:lang' : 'en' } , [
2018-03-13 10:09:08 +13:00
m ( 'head' , [
2019-10-08 19:37:27 +13:00
m ( 'meta' , { charset : 'utf-8' } ) ,
2019-10-08 22:26:52 +13:00
metaGenerator ( ) ,
2019-10-08 19:37:27 +13:00
m ( 'link' , { rel : 'stylesheet' , type : 'text/css' , href : 'Styles/style.css' } ) ,
m ( 'link' , { rel : 'stylesheet' , type : 'text/css' , href : 'Styles/navstyle.css' } ) ,
2018-03-13 10:09:08 +13:00
m ( 'title' , 'Author\'s Notes' )
] ) ,
2019-10-08 19:37:27 +13:00
m ( 'body#navpage' , { 'epub:type' : 'frontmatter toc' } , m ( 'div' , [
2018-03-13 10:09:08 +13:00
m ( 'h3' , 'Author\'s Notes' ) ,
m ( '#toc' , list )
] ) )
] )
2019-10-08 19:37:27 +13:00
, { strict : true } ) . then ( ( navDocument ) => {
2018-05-09 06:26:37 +12:00
navDocument = '<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE html>\n' + pretty . xml ( navDocument )
return navDocument
} )
2016-06-24 01:26:01 +12:00
}
2016-08-24 02:32:55 +12:00
export function createCoverPage ( ffc ) {
2016-06-28 09:19:01 +12:00
let body
2019-10-08 22:31:42 +13:00
const { width , height } = ffc . coverImageDimensions
2016-08-24 02:32:55 +12:00
if ( ffc . coverImage ) {
2019-10-08 19:37:27 +13:00
body = m ( 'svg#cover' , { xmlns : NS . SVG , 'xmlns:xlink' : NS . XLINK , version : '1.1' , viewBox : '0 0 ' + width + ' ' + height } ,
m ( 'image' , { width : width , height : height , 'xlink:href' : '../' + ffc . coverFilename } )
2016-06-28 09:19:01 +12:00
)
} else {
body = [
m ( 'h1' , ffc . storyInfo . title ) ,
m ( 'h2' , ffc . storyInfo . author . name )
]
}
2017-10-19 02:02:07 +13:00
return render (
2019-10-09 21:30:02 +13:00
m ( 'html' , { xmlns : NS . XHTML , 'xmlns:epub' : NS . OPS , lang : 'en' , 'xml:lang' : 'en' } , [
2016-06-24 01:26:01 +12:00
m ( 'head' , [
2019-10-08 22:26:52 +13:00
m ( 'meta' , { charset : 'utf-8' } ) ,
metaGenerator ( ) ,
2019-10-08 19:37:27 +13:00
ffc . coverImage ? m ( 'meta' , { name : 'viewport' , content : 'width=' + width + ', height=' + height } ) : null ,
2016-06-24 01:26:01 +12:00
m ( 'title' , 'Cover' ) ,
2019-10-08 19:37:27 +13:00
m ( 'link' , { rel : 'stylesheet' , type : 'text/css' , href : '../Styles/coverstyle.css' } )
2016-06-24 01:26:01 +12:00
] ) ,
2019-10-08 19:37:27 +13:00
m ( 'body#coverpage' , { 'epub:type' : 'frontmatter cover' } , body )
2016-06-24 01:26:01 +12:00
] )
2019-10-08 19:37:27 +13:00
, { strict : true } ) . then ( ( coverPage ) => {
2018-05-09 06:26:37 +12:00
coverPage = '<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE html>\n' + pretty . xml ( coverPage )
return coverPage
} )
2016-06-24 01:26:01 +12:00
}
2016-06-28 09:19:01 +12:00
2018-03-16 04:14:57 +13:00
function infoBox ( heading , data , title ) {
2019-10-08 19:37:27 +13:00
return m ( '.infobox' , { title } , m ( '.wrap' , [
2016-06-28 09:19:01 +12:00
m ( 'span.heading' , heading ) ,
m ( 'br' ) ,
2016-08-22 07:48:40 +12:00
m ( 'span.data' , data )
2016-06-28 09:19:01 +12:00
] ) )
}
2018-03-16 04:14:57 +13:00
function calcReadingTime ( ffc , wordCount = 0 ) {
const wpm = ffc . options . wordsPerMinute
2019-10-08 22:31:42 +13:00
const time = ( wordCount || ffc . totalWordCount ) / wpm
2018-03-16 04:14:57 +13:00
let value = 0
let unit = ''
if ( time < 1 ) {
value = Math . round ( time * 60 )
unit = 'second'
} else if ( time < 60 ) {
value = Math . round ( time )
unit = 'minute'
} else if ( time < 60 * 24 ) {
value = Math . round ( ( time / 60 ) * 10 ) / 10
unit = 'hour'
} else {
value = Math . round ( time / 60 )
unit = 'hour'
}
return value . toLocaleString ( 'en-GB' ) + ' ' + unit + ( value !== 1 ? 's' : '' )
2016-08-22 07:48:40 +12:00
}
2016-06-28 09:19:01 +12:00
export function createTitlePage ( ffc ) {
2018-03-13 10:09:08 +13:00
const tokenContent = '%%HTML_CONTENT_' + Math . random ( ) + '%%'
const completedIcon = {
complete : 'check' ,
incomplete : 'pencil' ,
hiatus : 'pause' ,
cancelled : 'ban'
}
2017-10-19 02:02:07 +13:00
return render (
2019-10-09 21:30:02 +13:00
m ( 'html' , { xmlns : NS . XHTML , 'xmlns:epub' : NS . OPS , lang : 'en' , 'xml:lang' : 'en' } , [
2016-06-28 09:19:01 +12:00
m ( 'head' , [
2019-10-08 19:37:27 +13:00
m ( 'meta' , { charset : 'utf-8' } ) ,
2019-10-08 22:26:52 +13:00
metaGenerator ( ) ,
2019-10-08 19:37:27 +13:00
m ( 'link' , { rel : 'stylesheet' , type : 'text/css' , href : '../Styles/style.css' } ) ,
m ( 'link' , { rel : 'stylesheet' , type : 'text/css' , href : '../Styles/titlestyle.css' } ) ,
2016-06-28 09:19:01 +12:00
m ( 'title' , ffc . storyInfo . title )
] ) ,
2019-10-08 19:37:27 +13:00
m ( 'body#titlepage' , { 'epub:type' : 'frontmatter titlepage' } , m ( 'div' , [
2018-03-13 23:11:32 +13:00
m ( 'header.title' , [
2019-10-08 19:37:27 +13:00
m ( 'div' , { className : 'content-rating content-rating-' + ffc . storyInfo . content _rating _text . toLowerCase ( ) } , ffc . storyInfo . content _rating _text . charAt ( 0 ) . toUpperCase ( ) ) ,
2018-05-09 08:31:55 +12:00
m ( 'span.story_name' , ffc . storyInfo . title + ' ' ) ,
m ( 'span.author' , [ 'by ' , m ( 'b' , ffc . storyInfo . author . name ) ] )
2016-06-28 23:59:39 +12:00
] ) ,
2016-08-22 07:48:40 +12:00
// m('hr'),
2018-05-09 07:11:50 +12:00
m ( '.tags' , ffc . tags . filter ( ( tag ) => tag . type !== 'character' ) . map ( ( tag ) =>
2019-10-08 19:37:27 +13:00
[ m ( 'span.tagbox' , m ( 'span' , { className : tag . className } , tag . name ) ) ]
2018-03-27 08:48:28 +13:00
) ) ,
2019-10-08 19:37:27 +13:00
m ( '.readlink' , m ( 'a' , { href : ffc . storyInfo . url } , 'Story on Fimfiction' ) ) ,
2016-08-22 07:48:40 +12:00
// m('hr'),
2016-06-28 09:19:01 +12:00
ffc . storyInfo . prequel ? [ m ( 'div' , [
2018-03-13 23:11:32 +13:00
m ( 'br' ) ,
2016-06-28 09:19:01 +12:00
'This story is a sequel to ' ,
2019-10-08 19:37:27 +13:00
m ( 'a' , { href : ffc . storyInfo . prequel . url } , ffc . storyInfo . prequel . title )
2018-03-13 23:11:32 +13:00
] ) , m ( 'hr.old' ) ] : null ,
2018-03-13 10:09:08 +13:00
m ( '#description' , tokenContent ) ,
2016-08-22 07:48:40 +12:00
m ( '.bottom' , [
2019-10-08 19:37:27 +13:00
m ( 'section' , { className : 'completed-status completed-status-' + ffc . storyInfo . status . toLowerCase ( ) } , [
2018-03-27 03:50:56 +13:00
m ( 'i.fa.fa-fw.fa-' + completedIcon [ ffc . storyInfo . status . toLowerCase ( ) ] , ' ' ) ,
2018-03-16 04:35:58 +13:00
ffc . storyInfo . status
2018-03-13 10:09:08 +13:00
] ) ,
2018-05-09 08:31:55 +12:00
ffc . storyInfo . publishDate && infoBox ( 'Published' , prettyDate ( new Date ( ffc . storyInfo . publishDate * 1000 ) ) ) ,
2016-08-22 07:48:40 +12:00
infoBox ( 'Last Modified' , prettyDate ( new Date ( ffc . storyInfo . date _modified * 1000 ) ) ) ,
2018-03-16 04:14:57 +13:00
ffc . totalWordCount ? infoBox ( 'Word Count' , ffc . totalWordCount . toLocaleString ( 'en-GB' ) ) : null ,
ffc . options . wordsPerMinute ? infoBox ( 'Time to Read' , calcReadingTime ( ffc ) , 'Estimated with ' + Math . round ( ffc . options . wordsPerMinute ) + ' words per minute' ) : null ,
2018-03-14 21:33:46 +13:00
ffc . options . calculateReadingEase && ffc . readingEase ? infoBox ( 'Reading Ease' , ( Math . round ( ffc . readingEase . ease * 100 ) / 100 ) . toLocaleString ( 'en-GB' ) ) : null
2016-08-22 07:48:40 +12:00
] ) ,
// m('hr'),
2018-05-09 07:11:50 +12:00
m ( '.tags' , ffc . tags . filter ( ( tag ) => tag . type === 'character' ) . map ( ( tag ) =>
2019-10-08 19:37:27 +13:00
[ m ( 'span.tagbox' , m ( 'span' , { className : tag . className } , tag . name ) ) ]
2018-03-27 08:48:28 +13:00
) )
2018-03-13 10:09:08 +13:00
] ) )
2016-06-28 09:19:01 +12:00
] )
2019-10-08 19:37:27 +13:00
, { strict : true } ) . then ( ( titlePage ) => {
2018-05-09 06:26:37 +12:00
titlePage = '<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE html>\n' + titlePage
titlePage = titlePage . replace ( tokenContent , '\n' + ffc . storyInfo . description + '\n' )
return titlePage
} )
2016-06-28 09:19:01 +12:00
}