2016-08-23 02:28:30 +12:00
/* global chrome */
'use strict'
2016-06-20 08:28:28 +12:00
2016-06-28 09:19:01 +12:00
import FimFic2Epub from './FimFic2Epub'
2016-08-23 02:28:30 +12:00
import m from 'mithril'
2017-10-19 02:02:07 +13:00
import prop from 'mithril/stream'
2016-08-15 21:11:20 +12:00
import { saveAs } from 'file-saver'
2016-08-24 19:09:43 +12:00
import autosize from 'autosize'
2018-03-13 23:10:04 +13:00
import { htmlToText } from './utils'
2016-08-15 21:11:20 +12:00
2016-08-24 08:04:38 +12:00
function blobToDataURL ( blob ) {
return new Promise ( ( resolve , reject ) => {
let fr = new FileReader ( )
fr . onloadend = function ( e ) { resolve ( fr . result ) }
fr . readAsDataURL ( blob )
} )
2016-08-24 02:32:55 +12:00
}
function blobToArrayBuffer ( blob ) {
return new Promise ( ( resolve , reject ) => {
let fr = new FileReader ( )
fr . onloadend = function ( e ) { resolve ( fr . result ) }
fr . readAsArrayBuffer ( blob )
} )
2016-08-15 21:11:20 +12:00
}
2016-06-20 08:28:28 +12:00
2016-08-23 02:28:30 +12:00
const isChromeExt = typeof chrome !== 'undefined'
2016-08-28 05:04:22 +12:00
let pageStoryId
try {
pageStoryId = document . location . pathname . match ( /^\/story\/(\d*)/ ) [ 1 ]
} catch ( e ) { }
let logoUrl = chrome . extension . getURL ( 'fimfic2epub-logo.png' )
2016-08-23 02:28:30 +12:00
let ffc
2017-06-07 08:15:05 +12:00
let stories = document . querySelectorAll ( '.story_container' )
2016-08-28 05:04:22 +12:00
stories . forEach ( ( story ) => {
2017-06-07 08:15:05 +12:00
let id = story . dataset . story
2017-06-13 03:44:05 +12:00
function epubClick ( e ) {
2016-08-28 05:04:22 +12:00
e . preventDefault ( )
openStory ( id )
2017-06-13 03:44:05 +12:00
}
let epubButtons = story . querySelectorAll ( '.drop-down ul li a[title="Download Story (.epub)"]' )
if ( epubButtons . length === 0 ) return
for ( let i = 0 ; i < epubButtons . length ; i ++ ) {
epubButtons [ i ] . addEventListener ( 'click' , epubClick , false )
}
2016-08-28 05:04:22 +12:00
let logo = new Image ( )
logo . className = 'fimfic2epub-logo'
logo . title = 'Download EPUB with fimfic2epub'
logo . src = logoUrl
2017-06-07 08:15:05 +12:00
story . querySelector ( '.story_content_box .title' ) . appendChild ( logo )
2017-06-13 03:44:05 +12:00
logo . addEventListener ( 'click' , epubClick , false )
2016-08-28 05:04:22 +12:00
} )
let cards = document . querySelectorAll ( '.story-card-container' )
cards . forEach ( ( card ) => {
let id
let classes = card . className . split ( ' ' )
for ( let i = 0 ; i < classes . length && ! id ; i ++ ) {
let c = classes [ i ]
id = c . substring ( 21 )
}
if ( ! id ) return
let flip = card . querySelector ( 'a.card-flip' )
let epubButton = card . querySelector ( 'a[title="Download .ePub"]' )
if ( ! epubButton ) return
epubButton . addEventListener ( 'click' , function ( e ) {
e . preventDefault ( )
openStory ( id )
flip . click ( )
} , false )
} )
2016-06-21 09:04:08 +12:00
2016-08-23 02:28:30 +12:00
const dialogContainer = document . createElement ( 'div' )
dialogContainer . id = 'epubDialogContainer'
document . body . appendChild ( dialogContainer )
let checkbox = {
2017-10-19 02:02:07 +13:00
view : function ( { attrs , children } ) {
2016-08-30 02:20:20 +12:00
return m ( 'label.toggleable-switch' , [
m ( 'input' , Object . assign ( {
type : 'checkbox'
2017-10-19 02:02:07 +13:00
} , attrs ) ) ,
2016-08-30 02:20:20 +12:00
m ( 'a' ) ,
2017-10-19 02:02:07 +13:00
children ? m ( 'span' , children ) : null
2016-08-23 02:28:30 +12:00
] )
}
}
2017-06-07 08:15:05 +12:00
function selectOptions ( list , selected = '' ) {
return list . map ( ( item ) => {
return m ( 'option' , {
value : item [ 0 ] ,
selected : selected === item [ 0 ]
} , item [ 1 ] )
} )
}
2017-06-08 02:17:40 +12:00
function redraw ( arg ) {
try {
m . redraw ( arg )
} catch ( err ) {
console . log ( err )
}
}
2017-10-19 02:02:07 +13:00
let ffcProgress = prop ( 0 )
let ffcStatus = prop ( '' )
2016-08-24 02:32:55 +12:00
2016-08-23 02:28:30 +12:00
let dialog = {
2017-10-19 02:02:07 +13:00
oninit ( ) {
2016-08-24 19:09:43 +12:00
const ctrl = this
2016-08-25 00:47:48 +12:00
ffcProgress ( 0 )
2017-10-19 02:02:07 +13:00
this . isLoading = prop ( true )
this . dragging = prop ( false )
this . xpos = prop ( 0 )
this . ypos = prop ( 0 )
this . el = prop ( null )
this . coverFile = prop ( null )
this . coverUrl = prop ( '' )
this . checkboxCoverUrl = prop ( false )
this . title = prop ( '' )
this . author = prop ( '' )
this . description = prop ( '' )
this . subjects = prop ( [ ] )
this . addCommentsLink = prop ( ffc . options . addCommentsLink )
this . includeAuthorNotes = prop ( ffc . options . includeAuthorNotes )
this . useAuthorNotesIndex = prop ( ffc . options . useAuthorNotesIndex )
this . addChapterHeadings = prop ( ffc . options . addChapterHeadings )
this . includeExternal = prop ( ffc . options . includeExternal )
this . joinSubjects = prop ( ffc . options . joinSubjects )
this . paragraphStyle = prop ( ffc . options . paragraphStyle )
2018-03-13 23:10:04 +13:00
this . calculateReadingEase = prop ( ffc . options . calculateReadingEase )
2017-10-19 02:02:07 +13:00
2018-03-13 10:08:16 +13:00
this . onOpen = ( vnode ) => {
2017-10-19 02:02:07 +13:00
this . el ( vnode . dom )
this . center ( )
this . isLoading ( true )
ffc . fetchMetadata ( ) . then ( ( ) => {
this . isLoading ( false )
ffcProgress ( - 1 )
this . title ( ffc . storyInfo . title )
this . author ( ffc . storyInfo . author . name )
2018-03-13 23:10:04 +13:00
this . description ( htmlToText ( ffc . storyInfo . description ) || ffc . storyInfo . short _description )
2017-10-19 02:02:07 +13:00
this . subjects ( ffc . subjects . slice ( 0 ) )
redraw ( true )
2016-08-25 00:47:48 +12:00
this . center ( )
2017-10-19 02:02:07 +13:00
ffc . fetchChapters ( ) . then ( ( ) => {
2016-08-25 00:47:48 +12:00
ffcProgress ( - 1 )
2017-10-19 02:02:07 +13:00
redraw ( )
2016-08-25 00:47:48 +12:00
} )
2017-10-19 02:02:07 +13:00
} ) . catch ( ( err ) => {
2018-03-13 10:08:16 +13:00
console . error ( err )
2017-10-19 02:02:07 +13:00
} )
2016-08-25 00:47:48 +12:00
}
2016-08-24 02:32:55 +12:00
this . setCoverFile = ( e ) => {
2018-03-13 10:08:16 +13:00
this . coverUrl ( '' )
2016-08-24 02:32:55 +12:00
this . coverFile ( e . target . files ? e . target . files [ 0 ] : null )
}
2016-08-24 19:09:43 +12:00
this . setSubjects = function ( ) {
// 'this' is the textarea
2016-08-25 00:47:48 +12:00
let set = new Set ( )
ctrl . subjects ( this . value . split ( '\n' ) . map ( ( s ) => s . trim ( ) ) . filter ( ( s ) => {
if ( ! s ) return false
if ( set . has ( s ) ) return false
set . add ( s )
return true
} ) )
2016-08-24 19:09:43 +12:00
this . value = ctrl . subjects ( ) . join ( '\n' )
autosize . update ( this )
}
2016-08-30 02:20:20 +12:00
this . setDescription = function ( ) {
ctrl . description ( this . value . trim ( ) )
this . value = ctrl . description ( )
autosize . update ( this )
}
2016-08-23 02:28:30 +12:00
this . ondown = ( e ) => {
2016-08-23 07:57:19 +12:00
let rect = this . el ( ) . firstChild . getBoundingClientRect ( )
2017-10-19 02:02:07 +13:00
let offset = { x : e . pageX - rect . left - document . documentElement . scrollLeft , y : e . pageY - rect . top - document . documentElement . scrollTop }
2016-08-23 02:28:30 +12:00
this . dragging ( true )
let onmove = ( e ) => {
e . preventDefault ( )
if ( this . dragging ( ) ) {
2016-08-24 09:49:27 +12:00
this . move ( e . pageX - offset . x , e . pageY - offset . y )
2016-08-15 21:11:20 +12:00
}
2016-08-23 02:28:30 +12:00
}
let onup = ( ) => {
this . dragging ( false )
window . removeEventListener ( 'mousemove' , onmove )
window . removeEventListener ( 'mouseup' , onup )
}
window . addEventListener ( 'mousemove' , onmove , false )
window . addEventListener ( 'mouseup' , onup , false )
}
2016-08-25 00:47:48 +12:00
2016-08-24 09:49:27 +12:00
this . move = ( xpos , ypos ) => {
2016-08-25 00:47:48 +12:00
let bc = document . querySelector ( '.body_container' )
let rect = this . el ( ) . firstChild . getBoundingClientRect ( )
this . xpos ( Math . max ( 0 , Math . min ( xpos , bc . offsetWidth - rect . width ) ) )
this . ypos ( Math . max ( 0 , Math . min ( ypos , bc . offsetHeight - rect . height ) ) )
2016-08-23 07:57:19 +12:00
this . el ( ) . style . left = this . xpos ( ) + 'px'
this . el ( ) . style . top = this . ypos ( ) + 'px'
}
2016-08-24 08:04:38 +12:00
this . center = ( ) => {
2016-08-25 00:47:48 +12:00
if ( this . dragging ( ) ) return
2016-08-24 08:04:38 +12:00
let rect = this . el ( ) . firstChild . getBoundingClientRect ( )
2016-08-24 09:49:27 +12:00
this . move (
2017-10-19 02:02:07 +13:00
Math . max ( document . documentElement . scrollLeft , ( window . innerWidth / 2 ) - ( rect . width / 2 ) + document . documentElement . scrollLeft ) ,
Math . max ( document . documentElement . scrollTop , ( window . innerHeight / 2 ) - ( rect . height / 2 ) + document . documentElement . scrollTop )
2016-08-24 09:49:27 +12:00
)
2016-08-24 08:04:38 +12:00
}
2016-08-23 07:57:19 +12:00
this . createEpub = ( e ) => {
e . target . disabled = true
2018-03-13 10:08:16 +13:00
createEpub ( this )
2016-08-23 07:57:19 +12:00
}
2016-08-23 02:28:30 +12:00
} ,
2016-08-24 02:32:55 +12:00
2017-10-19 02:02:07 +13:00
view ( vnode ) {
let ctrl = vnode . state
return m ( '.drop-down-pop-up-container' , { oncreate : ctrl . onOpen . bind ( ctrl ) } , m ( '.drop-down-pop-up' , { style : { 'min-width' : '700px' } } , [
2016-08-23 07:57:19 +12:00
m ( 'h1' , { onmousedown : ctrl . ondown } , m ( 'i.fa.fa-book' ) , 'Export to EPUB' , m ( 'a.close_button' , { onclick : closeDialog } ) ) ,
2016-08-25 00:47:48 +12:00
m ( '.drop-down-pop-up-content' , [
ctrl . isLoading ( ) ? m ( 'div' , { style : 'text-align:center;' } , m ( 'i.fa.fa-spin.fa-spinner' , { style : 'font-size:50px; margin:20px; color:#777;' } ) ) : m ( 'table.properties' , [
2016-08-24 09:49:27 +12:00
m ( 'tr' , m ( 'td.section_header' , { colspan : 3 } , m ( 'b' , 'General settings' ) ) ) ,
m ( 'tr' , m ( 'td.label' , 'Title' ) , m ( 'td' , { colspan : 2 } , m ( 'input' , { type : 'text' , value : ctrl . title ( ) , onchange : m . withAttr ( 'value' , ctrl . title ) } ) ) ) ,
m ( 'tr' , m ( 'td.label' , 'Author' ) , m ( 'td' , { colspan : 2 } , m ( 'input' , { type : 'text' , value : ctrl . author ( ) , onchange : m . withAttr ( 'value' , ctrl . author ) } ) ) ) ,
m ( 'tr' , m ( 'td.label' , 'Custom cover image' ) ,
m ( 'td' ,
ctrl . checkboxCoverUrl ( ) ? m ( 'input' , { type : 'url' , placeholder : 'Image URL' , onchange : m . withAttr ( 'value' , ctrl . coverUrl ) } ) : m ( 'input' , { type : 'file' , accept : 'image/*' , onchange : ctrl . setCoverFile } )
) ,
m ( 'td' , { style : 'width: 1px' } , m ( checkbox , { checked : ctrl . checkboxCoverUrl ( ) , onchange : m . withAttr ( 'checked' , ctrl . checkboxCoverUrl ) } , 'Use image URL' ) )
) ,
2017-06-07 08:15:05 +12:00
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 ( ) ) )
) ) ,
2016-08-24 09:49:27 +12:00
m ( 'tr' , m ( 'td.label' , '' ) , m ( 'td' , { colspan : 2 } ,
m ( checkbox , { checked : ctrl . addChapterHeadings ( ) , onchange : m . withAttr ( 'checked' , ctrl . addChapterHeadings ) } , 'Add chapter headings' ) ,
2016-08-25 00:47:48 +12:00
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' ) ,
2016-08-30 02:20:20 +12:00
m ( checkbox , { checked : ctrl . useAuthorNotesIndex ( ) , onchange : m . withAttr ( 'checked' , ctrl . useAuthorNotesIndex ) , disabled : ! ctrl . includeAuthorNotes ( ) } , 'Put all notes at the end of the ebook' ) ,
2018-03-13 23:10:04 +13:00
m ( checkbox , { checked : ctrl . calculateReadingEase ( ) , onchange : m . withAttr ( 'checked' , ctrl . calculateReadingEase ) } , 'Calculate Flesch reading ease' ) ,
2016-08-25 00:47:48 +12:00
m ( checkbox , { checked : ctrl . includeExternal ( ) , onchange : m . withAttr ( 'checked' , ctrl . includeExternal ) } , 'Download & include remote content (embed images)' ) ,
m ( 'div' , { style : 'font-size: 0.9em; line-height: 1em; margin-top: 4px; margin-bottom: 6px; color: #777;' } , 'Note: Disabling this creates invalid EPUBs and requires internet access to see remote content. Only cover image will be embedded.' )
2016-08-24 09:49:27 +12:00
) ) ,
m ( 'tr' , m ( 'td.section_header' , { colspan : 3 } , m ( 'b' , 'Metadata customization' ) ) ) ,
2017-10-19 02:02:07 +13:00
m ( 'tr' , m ( 'td.label' , { style : 'vertical-align: top;' } , 'Description' ) , m ( 'td' , { colspan : 2 } , m ( 'textarea' , { oncreate : ( { dom } ) => autosize ( dom ) , onchange : ctrl . setDescription } , ctrl . description ( ) ) ) ) ,
2016-08-25 00:47:48 +12:00
m ( 'tr' , m ( 'td.label' , { style : 'vertical-align: top;' } , 'Categories' ) , m ( 'td' , { colspan : 2 } ,
2017-10-19 02:02:07 +13:00
m ( 'textarea' , { rows : 2 , oncreate : ( { dom } ) => autosize ( dom ) , onchange : ctrl . setSubjects } , ctrl . subjects ( ) . join ( '\n' ) ) ,
2016-08-25 06:18:01 +12:00
m ( checkbox , { checked : ctrl . joinSubjects ( ) , onchange : m . withAttr ( 'checked' , ctrl . joinSubjects ) } , 'Join categories and separate with commas (for iBooks only)' )
2016-08-24 02:32:55 +12:00
) )
2016-08-23 07:57:19 +12:00
] ) ,
m ( '.drop-down-pop-up-footer' , [
2016-08-25 00:47:48 +12:00
m ( 'button.styled_button' , { onclick : ctrl . createEpub , disabled : ffcProgress ( ) >= 0 && ffcProgress ( ) < 1 , style : 'float: right' } , 'Download EPUB' ) ,
ffcProgress ( ) >= 0 ? m ( '.rating_container' ,
2017-06-12 23:23:48 +12:00
m ( '.rating-bar' , { style : { background : 'rgba(0, 0, 0, 0.2)' , 'margin-right' : '5px' } } , m ( '.like-bar' , { style : { width : Math . max ( 0 , ffcProgress ( ) ) * 100 + '%' } } ) ) ,
2016-08-24 02:32:55 +12:00
' ' ,
2016-08-25 00:47:48 +12:00
ffcProgress ( ) >= 0 && ffcProgress ( ) < 1 ? [ m ( 'i.fa.fa-spin.fa-spinner' ) , m . trust ( ' ' ) ] : null ,
2016-08-24 02:32:55 +12:00
ffcStatus ( )
2016-08-25 00:47:48 +12:00
) : null ,
m ( 'div' , { style : 'clear: both' } )
2016-08-23 07:57:19 +12:00
] )
2016-08-23 02:28:30 +12:00
] )
] ) )
}
}
2016-08-24 09:49:27 +12:00
let dialogOpen = false
2017-10-19 02:02:07 +13:00
function openDialog ( ) {
2016-08-24 09:49:27 +12:00
if ( dialogOpen ) {
return
}
dialogOpen = true
2017-10-19 02:02:07 +13:00
m . mount ( dialogContainer , dialog )
2016-08-23 02:28:30 +12:00
}
function closeDialog ( ) {
2016-08-24 09:49:27 +12:00
dialogOpen = false
2016-08-23 02:28:30 +12:00
m . mount ( dialogContainer , null )
}
2018-03-13 10:08:16 +13:00
function createEpub ( model ) {
ffcProgress ( 0 )
ffcStatus ( '' )
let chain = Promise . resolve ( )
ffc . coverUrl = ''
ffc . coverImage = null
if ( model . checkboxCoverUrl ( ) ) {
ffc . coverUrl = model . coverUrl ( )
} else if ( model . coverFile ( ) ) {
chain = chain
. then ( ( ) => blobToArrayBuffer ( model . coverFile ( ) ) )
. then ( ffc . setCoverImage . bind ( ffc ) )
}
ffc . setTitle ( model . title ( ) )
ffc . setAuthorName ( model . author ( ) )
ffc . storyInfo . short _description = model . description ( )
ffc . options . addCommentsLink = model . addCommentsLink ( )
ffc . options . includeAuthorNotes = model . includeAuthorNotes ( )
ffc . options . useAuthorNotesIndex = model . useAuthorNotesIndex ( )
ffc . options . addChapterHeadings = model . addChapterHeadings ( )
ffc . options . includeExternal = model . includeExternal ( )
ffc . options . paragraphStyle = model . paragraphStyle ( )
ffc . subjects = model . subjects ( )
ffc . options . joinSubjects = model . joinSubjects ( )
2018-03-13 23:10:04 +13:00
ffc . options . calculateReadingEase = model . calculateReadingEase ( )
2018-03-13 10:08:16 +13:00
redraw ( )
chain
. then ( ffc . fetchAll . bind ( ffc ) )
. then ( ffc . build . bind ( ffc ) )
. then ( ffc . getFile . bind ( ffc ) ) . then ( ( file ) => {
if ( typeof safari !== 'undefined' ) {
blobToDataURL ( file ) . then ( ( dataurl ) => {
document . location . href = dataurl
alert ( 'Add .epub to the filename of the downloaded file' )
} )
} else {
saveAs ( file , ffc . filename )
}
} )
}
2016-08-28 05:04:22 +12:00
function openStory ( id ) {
2016-08-24 09:49:27 +12:00
if ( ! ffc ) {
2016-08-28 05:04:22 +12:00
ffc = new FimFic2Epub ( id )
ffc . on ( 'progress' , onProgress )
} else if ( ffc . storyId !== id ) {
ffc . off ( 'progress' , onProgress )
closeDialog ( )
ffc = new FimFic2Epub ( id )
ffc . on ( 'progress' , onProgress )
2016-08-24 09:49:27 +12:00
}
2016-08-23 02:28:30 +12:00
openDialog ( )
}
2016-08-28 05:04:22 +12:00
function onProgress ( percent , status ) {
ffcProgress ( percent )
if ( status ) {
ffcStatus ( status )
2016-08-23 02:28:30 +12:00
}
2017-06-08 02:17:40 +12:00
redraw ( )
2016-08-28 05:04:22 +12:00
}
2016-08-23 02:28:30 +12:00
2016-08-28 05:04:22 +12:00
if ( pageStoryId && isChromeExt ) {
chrome . runtime . sendMessage ( { showPageAction : true } )
chrome . runtime . onMessage . addListener ( function ( request ) {
if ( request === 'pageAction' ) {
openStory ( pageStoryId )
}
} )
2016-06-21 09:04:08 +12:00
}