/*! * (℠) * ## Bibi (heart) | Heart of Bibi. * */ export const Bibi = { 'version': '____Bibi-Version____', 'href': 'https://bibi.epub.link', Status: '', TimeOrigin: Date.now() }; Bibi.SettingTypes = { 'boolean': [ 'allow-placeholders', 'prioritise-fallbacks' ], 'yes-no': [ 'autostart', 'autostart-embedded', 'fix-nav-ttb', 'fix-reader-view-mode', 'flip-pages-during-sliding', 'full-breadth-layout-in-scroll', 'start-embedded-in-new-window', 'use-arrows', 'use-bookmarks', 'use-font-size-changer', 'use-full-height', 'use-history', 'use-keys', 'use-loupe', 'use-menubar', 'use-nombre', 'use-slider', 'zoom-out-for-utilities' ], 'string': [ 'book', 'default-page-progression-direction', 'on-doubletap', 'on-tripletap', 'pagination-method', 'reader-view-mode' ], 'integer': [ 'item-padding-bottom', 'item-padding-left', 'item-padding-right', 'item-padding-top', 'spread-gap', 'spread-margin' ], 'number': [ 'base-font-size', 'flipper-width', 'font-size-scale-per-step', 'loupe-max-scale', 'loupe-scale-per-step', 'orientation-border-ratio' ], 'array': [ 'content-draggable', 'orthogonal-edges', 'orthogonal-arrow-keys', 'orthogonal-touch-moves', 'orthogonal-wheelings' ] }; Bibi.SettingTypes_PresetOnly = { 'boolean': [ 'accept-base64-encoded-data', 'accept-blob-converted-data', 'allow-scripts-in-content', 'remove-bibi-website-link' ], 'yes-no': [ 'accept-local-file', 'keep-settings', 'resume-from-last-position' ], 'string': [ 'bookshelf', 'website-name-in-title', 'website-name-in-menu', 'website-href' ], 'integer': [ 'max-bookmarks', 'max-history' ], 'number': [ ], 'array': [ 'extensions', 'extract-if-necessary', 'trustworthy-origins' ] }; Bibi.SettingTypes_UserOnly = { 'boolean': [ 'debug', 'wait', 'zine' ], 'yes-no': [ ], 'string': [ 'edge', 'epubcfi', 'p', ], 'integer': [ 'log', 'nav', 'parent-bibi-index' ], 'number': [ 'iipp', 'sipp' ], 'array': [ ] }; Bibi.verifySettingValue = (SettingType, _P, _V, Fill) => Bibi.verifySettingValue[SettingType](_P, _V, Fill); (Verifiers => { for(const SettingType in Verifiers) Bibi.verifySettingValue[SettingType] = Verifiers[SettingType]; })({ 'boolean': (_P, _V, Fill) => { if(typeof _V == 'boolean') return _V; if(_V === 'true' || _V === '1' || _V === 1) return true; if(_V === 'false' || _V === '0' || _V === 0) return false; if(Fill) return false; }, 'yes-no': (_P, _V, Fill) => { if(/^(yes|no|mobile|desktop)$/.test(_V)) return _V; if(_V === 'true' || _V === '1' || _V === 1) return 'yes'; if(_V === 'false' || _V === '0' || _V === 0) return 'no'; if(Fill) return 'no'; }, 'string': (_P, _V, Fill) => { if(typeof _V == 'string') { switch(_P) { case 'edge' : return /^(head|foot)$/.test(_V) ? _V : undefined; case 'book' : return (_V = decodeURIComponent(_V).trim()) ? _V : undefined; case 'default-page-progression-direction' : return _V == 'rtl' ? _V : 'ltr'; case 'on-doubletap': case 'on-tripletap' : return /^(panel|zoom)$/.test(_V) ? _V : undefined; case 'p' : return /^([a-z]+|[1-9]\d*((\.[1-9]\d*)*|-[a-z]+))$/.test(_V) ? _V : undefined; case 'pagination-method' : return _V == 'x' ? _V : 'auto'; case 'reader-view-mode' : return /^(paged|horizontal|vertical)$/.test(_V) ? _V : 'paged'; } return _V; } if(Fill) return ''; }, 'integer': (_P, _V, Fill) => { if(typeof (_V *= 1) == 'number' && isFinite(_V)) { _V = Math.max(Math.round(_V), 0); switch(_P) { case 'log' : return Math.min(_V, 9); case 'max-bookmarks' : return Math.min(_V, 9); case 'max-history' : return Math.min(_V, 19); } return _V; } if(Fill) return 0; }, 'number': (_P, _V, Fill) => { if(typeof (_V *= 1) == 'number' && isFinite(_V) && _V >= 0) return _V; if(Fill) return 0; }, 'array': (_P, _V, Fill) => { if(Array.isArray(_V)) { switch(_P) { case 'content-draggable' : _V.length = 2; for(let i = 0; i < 2; i++) _V[i] = _V[i] === false || _V[i] === 'false' || _V[i] === '0' || _V[i] === 0 ? false : true; return _V; case 'extensions' : return _V.filter(_I => typeof _I['src'] == 'string' && (_I['src'] = _I['src'].trim())); case 'extract-if-necessary' : return (_V = _V.map(_I => typeof _I == 'string' ? _I.trim().toLowerCase() : '')).includes('*') ? ['*'] : _V.filter(_I => /^(\.[\w\d]+)*$/.test(_I)); case 'orthogonal-arrow-keys' : case 'orthogonal-edges' : case 'orthogonal-touch-moves' : case 'orthogonal-wheelings' : _V.length = 2; for(let i = 0; i < 2; i++) _V[i] = typeof _V[i] == 'string' ? _V[i] : ''; return _V; case 'trustworthy-origins' : return _V.reduce((_VN, _I) => typeof _I == 'string' && /^https?:\/\/[^\/]+$/.test(_I = _I.trim().replace(/\/$/, '')) && !_VN.includes(_I) ? _VN.push(_I) && _VN : false, []); } return _V.filter(_I => typeof _I != 'function'); } if(Fill) return []; } }); Bibi.applyFilteredSettingsTo = (To, From, ListOfSettingTypes, Fill) => { ListOfSettingTypes.forEach(STs => { for(const ST in STs) { STs[ST].forEach(_P => { const VSV = Bibi.verifySettingValue[ST](_P, From[_P]); if(Fill) { To[_P] = Bibi.verifySettingValue[ST](_P, To[_P]); if(typeof VSV != 'undefined' || typeof To[_P] == 'undefined') To[_P] = Bibi.verifySettingValue[ST](_P, From[_P], true); } else if(From.hasOwnProperty(_P)) { if(typeof VSV != 'undefined') To[_P] = VSV; } }); } }); return To; }; Bibi.ErrorMessages = { Canceled: `Fetch Canceled`, CORSBlocked: `Probably CORS Blocked`, DataInvalid: `Data Invalid`, NotFound: `404 Not Found` }; //============================================================================================================================================== //---------------------------------------------------------------------------------------------------------------------------------------------- //-- Hello ! //---------------------------------------------------------------------------------------------------------------------------------------------- Bibi.at1st = () => Bibi.at1st.List.forEach(fn => typeof fn == 'function' ? fn() : true), Bibi.at1st.List = []; Bibi.hello = () => new Promise(resolve => { Bibi.at1st(); O.log.initialize(); O.log(`Hello!`, ''); O.log(`[ja] ${ Bibi['href'] }`); O.log(`[en] https://github.com/satorumurmur/bibi`); resolve(); }) .then(Bibi.initialize) .then(Bibi.loadExtensions) .then(Bibi.ready) .then(Bibi.getBookData) .then(Bibi.loadBook) .then(Bibi.bindBook) .then(Bibi.openBook) .catch(O.error); Bibi.initialize = () => { //const PromiseTryingRangeRequest = O.tryRangeRequest().then(() => true).catch(() => false).then(TF => O.RangeRequest = TF); { // Path / URI O.Origin = location.origin || (location.protocol + '//' + (location.host || (location.hostname + (location.port ? ':' + location.port : '')))); O.Local = location.protocol == 'file:'; O.RequestedURL = location.href; } { // DOM O.contentWindow = window; O.contentDocument = document; O.HTML = document.documentElement; O.Head = document.head; O.Body = document.body; O.Info = document.getElementById('bibi-info'); O.Title = document.getElementsByTagName('title')[0]; } { // Environments O.HTML.classList.add(...sML.Environments, 'Bibi', 'welcome'); if(O.TouchOS = (sML.OS.iOS || sML.OS.Android) ? true : false) { // Touch Device O.HTML.classList.add('touch'); if(sML.OS.iOS) { O.Head.appendChild(sML.create('meta', { name: 'apple-mobile-web-app-capable', content: 'yes' })); O.Head.appendChild(sML.create('meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'white' })); } } if(Bibi.Dev) O.HTML.classList.add('dev'); if(Bibi.Debug) O.HTML.classList.add('debug'); O.HTML.classList.add('default-lang-' + (O.Language = (NLs => { // Language if(Array.isArray(navigator.languages)) NLs = NLs.concat(navigator.languages); if(navigator.language && navigator.language != NLs[0]) NLs.unshift(navigator.language); for(let l = NLs.length, i = 0; i < l; i++) { const Lan = NLs[i].split ? NLs[i].split('-')[0] : ''; if(Lan == 'ja') return 'ja'; if(Lan == 'en') break; } return 'en'; })([]))); } { // Modules E.initialize(); O.Biscuits.initialize(); P.initialize(); U.initialize(); D.initialize(); S.initialize(); I.initialize(); } { // Embedding, Window, Fullscreen O.Embedded = (() => { // Window Embedded or Not if(window.parent == window) { O.HTML.classList.add('window-direct' ); return 0; } // false else { O.HTML.classList.add('window-embedded'); try { if(location.host == parent.location.host || parent.location.href) return 1; } catch(Err) {} return -1; } // true (1:Reachable or -1:Unreachable) })(); O.ParentBibi = O.Embedded === 1 && typeof S['parent-bibi-index'] == 'number' ? window.parent['bibi:jo'].Bibis[S['parent-bibi-index']] || null : null; O.ParentOrigin = O.ParentBibi ? window.parent.location.origin : ''; O.FullscreenTarget = (() => { // Fullscreen Target const FsT = (() => { if(!O.Embedded) { sML.Fullscreen.polyfill(window ); return O.HTML; } if(O.ParentBibi) { sML.Fullscreen.polyfill(window.parent); return O.ParentBibi.Frame; } })() || null; if(FsT && FsT.ownerDocument.fullscreenEnabled) { O.HTML.classList.add('fullscreen-enabled' ); return FsT; } else { O.HTML.classList.add('fullscreen-disabled'); return null; } })(); if(O.ParentBibi) { O.ParentBibi.Window = window, O.ParentBibi.Document = document, O.ParentBibi.HTML = O.HTML, O.ParentBibi.Body = O.Body; ['bibi:initialized', 'bibi:readied', 'bibi:prepared', 'bibi:opened'].forEach(EN => E.add(EN, Det => O.ParentBibi.dispatch(EN, Det))); window.addEventListener('message', M.receive, false); } } if(sML.UA.Trident && !(sML.UA.Trident[0] >= 7)) { // Say Bye-bye I.note(`Your Browser Is Not Compatible`, 99999999999, 'ErrorOccured'); throw I.Veil.byebye({ 'en': `Sorry.... Your Browser Is Not Compatible.`, 'ja': `大変申し訳ありません。 お使いのブラウザでは、動作しません。` }); } else { // Say Welcome! I.note(`Welcome!`); } { // Writing Mode, Font Size, Slider Size, Menu Height O.WritingModeProperty = (() => { const HTMLComputedStyle = getComputedStyle(O.HTML); if(/^(vertical|horizontal)-/.test(HTMLComputedStyle[ 'writing-mode']) || sML.UA.Trident) return 'writing-mode'; if(/^(vertical|horizontal)-/.test(HTMLComputedStyle['-webkit-writing-mode']) ) return '-webkit-writing-mode'; if(/^(vertical|horizontal)-/.test(HTMLComputedStyle[ '-epub-writing-mode']) ) return '-epub-writing-mode'; return undefined; })(); const StyleChecker = O.Body.appendChild(sML.create('div', { id: 'bibi-style-checker', innerHTML: ' aAaAあ亜 ', style: { width: 'auto', height: 'auto', left: '-1em', top: '-1em' } })); O.VerticalTextEnabled = (StyleChecker.offsetWidth < StyleChecker.offsetHeight); O.DefaultFontSize = Math.min(StyleChecker.offsetWidth, StyleChecker.offsetHeight); StyleChecker.style.fontSize = '0.01px'; O.MinimumFontSize = Math.min(StyleChecker.offsetWidth, StyleChecker.offsetHeight); StyleChecker.setAttribute('style', ''), StyleChecker.innerHTML = ''; I.Slider.Size = S['use-slider' ] ? StyleChecker.offsetWidth : 0; I.Menu.Height = S['use-menubar'] ? StyleChecker.offsetHeight : 0; delete document.body.removeChild(StyleChecker); } { // Scrollbars O.Body.style.width = '101vw', O.Body.style.height = '101vh'; O.Scrollbars = { Width: window.innerWidth - O.HTML.offsetWidth, Height: window.innerHeight - O.HTML.offsetHeight }; O.HTML.style.width = O.Body.style.width = '100%', O.Body.style.height = ''; } O.HTML.classList.toggle('book-full-height', S['use-full-height']); O.HTML.classList.remove('welcome'); E.dispatch('bibi:initialized', Bibi.Status = Bibi.Initialized = 'Initialized'); //return PromiseTryingRangeRequest; }; Bibi.loadExtensions = () => { return new Promise((resolve, reject) => { const AdditionalExtensions = []; if(!S['allow-scripts-in-content']) AdditionalExtensions.push('sanitizer.js'); let ReadyForExtraction = false, ReadyForBibiZine = false; if(S['book']) { if(O.isToBeExtractedIfNecessary(S['book'])) ReadyForExtraction = true; if(S['zine']) ReadyForBibiZine = true; } else { if(S['accept-local-file'] || S['accept-blob-converted-data']) ReadyForExtraction = ReadyForBibiZine = true; } if(ReadyForBibiZine) AdditionalExtensions.unshift('zine.js'); (ReadyForExtraction ? (S['book'] ? O.tryRangeRequest().then(() => 'on-the-fly') : Promise.reject()).catch(() => 'at-once').then(_ => AdditionalExtensions.unshift('extractor/' + _ + '.js')) : Promise.resolve()).then(() => { if(AdditionalExtensions.length) AdditionalExtensions.forEach(AX => S['extensions'].unshift({ 'src': new URL('../../extensions/' + AX, Bibi.Script.src).href })); if(S['extensions'].length == 0) return reject(); O.log(`Loading Extension${ S['extensions'].length > 1 ? 's' : '' }...`, ''); const loadExtensionInPreset = (i) => { X.load(S['extensions'][i]).then(Msg => { //O.log(Msg); }).catch(Msg => { O.log(Msg); }).then(() => { S['extensions'][i + 1] ? loadExtensionInPreset(i + 1) : resolve(); }); }; loadExtensionInPreset(0); }); }).then(() => { O.log(`Extensions: %O`, X.Extensions); O.log(X.Extensions.length ? `Loaded. (${ X.Extensions.length } Extension${ X.Extensions.length > 1 ? 's' : '' })` : `No Extension.`, '') }).catch(() => false); }; Bibi.ready = () => new Promise(resolve => { O.HTML.classList.add('ready'); O.ReadiedURL = location.href; E.add('bibi:readied', resolve); setTimeout(() => E.dispatch('bibi:readied', Bibi.Status = Bibi.Readied = 'Readied'), (O.TouchOS && !O.Embedded) ? 1234 : 0); }).then(() => { O.HTML.classList.remove('ready'); }); Bibi.getBookData = () => S['book-data'] ? Promise.resolve({ BookData: S['book-data'], BookDataMIMEType: S['book-data-mimetype'] }) : S['book'] ? Promise.resolve({ Book: S['book'] }) : S['accept-local-file'] ? new Promise(resolve => { Bibi.getBookData.resolve = (Par) => { resolve(Par), O.HTML.classList.remove('waiting-file'); }; O.HTML.classList.add('waiting-file'); }) : Promise.reject (`Tell me EPUB name via ${ O.Embedded ? 'embedding tag' : 'URI' }.`); Bibi.setBookData = (Par) => Bibi.getBookData.resolve ? Bibi.getBookData.resolve(Par) : Promise.reject(Par); Bibi.busyHerself = () => new Promise(resolve => { O.Busy = true; O.HTML.classList.add('busy'); O.HTML.classList.add('loading'); window.addEventListener(E['resize'], R.resetBibiHeight); Bibi.busyHerself.resolve = () => { resolve(); delete Bibi.busyHerself; }; }).then(() => { window.removeEventListener(E['resize'], R.resetBibiHeight); O.Busy = false; O.HTML.classList.remove('busy'); O.HTML.classList.remove('loading'); }); Bibi.loadBook = (BookInfo) => Promise.resolve().then(() => { Bibi.busyHerself(); I.note(`Loading...`); O.log(`Initializing Book...`, ''); return L.initializeBook(BookInfo).then(InitializedAs => { O.log(`${ InitializedAs }: %O`, B); O.log(`Initialized. (as ${ /^[aiueo]/i.test(InitializedAs) ? 'an' : 'a' } ${ InitializedAs })`, ''); }); }).then(() => { S.update(); R.updateOrientation(); R.resetStage(); }).then(() => { // Create Cover O.log(`Creating Cover...`, ''); if(B.CoverImage.Source) { O.log(`Cover Image: %O`, B.CoverImage.Source); O.log(`Will Be Created.`, ''); } else { O.log(`Will Be Created. (w/o Image)`, ''); } return L.createCover(); // ← loading is async }).then(() => { // Load Navigation if(!B.Nav.Source) return O.log(`No Navigation.`) O.log(`Loading Navigation...`, ''); return L.loadNavigation().then(PNav => { O.log(`${ B.Nav.Type }: %O`, B.Nav.Source); O.log(`Loaded.`, ''); E.dispatch('bibi:loaded-navigation', B.Nav.Source); }); }).then(() => { // Announce "Prepared" (and Wait, sometime) E.dispatch('bibi:prepared', Bibi.Status = Bibi.Prepared = 'Prepared'); if(!S['autostart'] && !L.Played) return L.wait(); }).then(() => { // Background Preparing return L.preprocessResources(); }).then(() => { // Load & Layout Items in Spreads and Pages O.log(`Loading Items in Spreads...`, ''); const Promises = []; const LayoutOption = { TargetSpreadIndex: 0, Destination: { Edge: 'head' }, resetter: () => { LayoutOption.Reset = true; LayoutOption.removeResetter(); }, addResetter: () => { window .addEventListener('resize', LayoutOption.resetter); }, removeResetter: () => { window.removeEventListener('resize', LayoutOption.resetter); } }; if(typeof R.StartOn == 'object') { const Item = typeof R.StartOn.Item == 'object' ? R.StartOn.Item : (() => { if(typeof R.StartOn.ItemIndex == 'number') { let II = R.StartOn.ItemIndex; if(II < 0 ) R.StartOn = { ItemIndex: 0 }; else if(II >= R.Items.length) R.StartOn = { ItemIndex: R.Items.length - 1 }; return R.Items[R.StartOn.ItemIndex]; } if(typeof R.StartOn.ItemIndexInSpine == 'number') { let IIIS = R.StartOn.ItemIndexInSpine; if(IIIS < 0 ) IIIS = 0; else if(IIIS >= B.Package.Spine.length) IIIS = B.Package.Spine.length - 1; let Item = B.Package.Spine[R.StartOn.ItemIndexInSpine]; if(!Item.Spread) { R.StartOn = { ItemIndex: 0 }; Item = R.Items[0]; } return Item; } if(typeof R.StartOn.P == 'string') { const Steps = R.StartOn.P.split('.'); let II = Steps.shift() * 1 - 1; if(II < 0 ) II = 0, R.StartOn = { P: String(II + 1) }; else if(II >= R.Items.length) II = R.Items.length - 1, R.StartOn = { P: String(II + 1) }; return R.Items[II]; } if(typeof R.StartOn.IIPP == 'number') { let II = Math.floor(R.StartOn.IIPP); if(II < 0 ) II = 0, R.StartOn = { IIPP: II }; else if(II >= R.Items.length) II = R.Items.length - 1, R.StartOn = { IIPP: II }; return R.Items[II]; } if(typeof R.StartOn.Edge == 'string') { R.StartOn = (R.StartOn.Edge != 'foot') ? { Edge: 'head' } : { Edge: 'foot' }; switch(R.StartOn.Edge) { case 'head': return R.Items[0]; case 'foot': return R.Items[R.Items.length - 1]; } } })(); LayoutOption.TargetSpreadIndex = Item && Item.Spread ? Item.Spread.Index : 0; LayoutOption.Destination = R.StartOn; } LayoutOption.addResetter(); let LoadedItems = 0; R.Spreads.forEach(Spread => Promises.push(new Promise(resolve => L.loadSpread(Spread, { AllowPlaceholderItems: S['allow-placeholders'] && Spread.Index != LayoutOption.TargetSpreadIndex }).then(() => { LoadedItems += Spread.Items.length; I.note(`Loading... (${ LoadedItems }/${ R.Items.length } Items Loaded.)`); !LayoutOption.Reset ? R.layOutSpreadAndItsItems(Spread).then(resolve) : resolve(); })))); return Promise.all(Promises).then(() => { O.log(`Loaded. (${ R.Items.length } in ${ R.Spreads.length })`, ''); return LayoutOption; }); }); Bibi.bindBook = (LayoutOption) => { if(!LayoutOption.Reset) { R.organizePages(); R.layOutStage(); } return R.layOutBook(LayoutOption).then(() => { LayoutOption.removeResetter(); E.dispatch('bibi:laid-out-for-the-first-time', LayoutOption); return LayoutOption }); }; Bibi.openBook = (LayoutOption) => new Promise(resolve => { // Open Bibi.busyHerself.resolve(); I.Veil.close(); L.Opened = true; document.body.click(); // To responce for user scrolling/keypressing immediately I.note(''); O.log(`Enjoy Readings!`, ''); E.dispatch('bibi:opened', Bibi.Status = Bibi.Opened = 'Opened'); E.dispatch('bibi:scrolled'); resolve(); }).then(() => { const LandingPage = R.hatchPage(LayoutOption.Destination) || R.Pages[0]; if(!I.History.List.length) { I.History.List = [{ UI: Bibi, Item: LandingPage.Item, PageProgressInItem: LandingPage.IndexInItem / LandingPage.Item.Pages.length }]; I.History.update(); } if(S['allow-placeholders']) E.add('bibi:scrolled', () => I.PageObserver.turnItems()); if(S['resume-from-last-position']) E.add('bibi:changed-intersection', () => { try { if(O.Biscuits) O.Biscuits.memorize('Book', { Position: { IIPP: I.PageObserver.getIIPP() } }); } catch(Err) {} }); E.add('bibi:commands:move-by', R.moveBy); E.add('bibi:commands:scroll-by', R.scrollBy); E.add('bibi:commands:focus-on', R.focusOn); E.add('bibi:commands:change-view', R.changeView); (Bibi.Dev && location.hostname != 'localhost') ? Bibi.createDevNote() : delete Bibi.createDevNote; /* alert((Alert => { [ 'document.referrer', 'navigator.userAgent', '[navigator.appName, navigator.vendor, navigator.platform]', 'window.innerHeight', '[O.HTML.offsetHeight, O.HTML.clientHeight, O.HTML.scrollHeight]', '[O.Body.offsetHeight, O.Body.clientHeight, O.Body.scrollHeight]', '[R.Main.offsetHeight, R.Main.clientHeight, R.Main.scrollHeight]' ].forEach(X => Alert.push(`┌ ' + X + '\n' + eval(X))); return Alert.join('\n\n'); })([])); //*/ }); Bibi.createDevNote = () => { const Dev = Bibi.IsDevMode = O.Body.appendChild(sML.create('div', { id: 'bibi-is-dev-mode' })); Bibi.createDevNote.logBorderLine(); Bibi.createDevNote.appendParagraph(`This Bibi seems to be a Development Version`); Bibi.createDevNote.appendParagraph(`Please don't forget to create a production version before publishing on the Internet.`); Bibi.createDevNote.appendParagraph(`(To create a production version, run it on terminal: \`npm run build\`)`); Bibi.createDevNote.appendParagraph(`Close`, 'NoLog').addEventListener('click', () => Dev.className = 'hide'); Bibi.createDevNote.logBorderLine(); [E['pointerdown'], E['pointerup'], E['pointermove'], E['pointerover'], E['pointerout'], 'click'].forEach(EN => Dev.addEventListener(EN, Eve => { Eve.preventDefault(); Eve.stopPropagation(); return false; })); setTimeout(() => Dev.className = 'show', 0); delete Bibi.createDevNote; }; Bibi.createDevNote.logBorderLine = (InnerHTML, NoLog) => { O.log('========================', '<*/>'); }; Bibi.createDevNote.appendParagraph = (InnerHTML, NoLog) => { const P = Bibi.IsDevMode.appendChild(sML.create('p', { innerHTML: InnerHTML })); if(!NoLog) O.log(InnerHTML.replace(/<[^<>]*>/g, ''), '<*/>'); return P; }; Bibi.createElement = (...Args) => { const TagName = Args[0]; if(!Bibi.Elements) Bibi.Elements = {}; if(window.customElements) { if(!Bibi.Elements[TagName]) Bibi.Elements[TagName] = class extends HTMLElement { constructor() { super(); } }, window.customElements.define(TagName, Bibi.Elements[TagName]); } else if(document.registerElement) { if(!Bibi.Elements[TagName]) Bibi.Elements[TagName] = document.registerElement(TagName); return sML.edit(new Bibi.Elements[Args.shift()](), Args); } return sML.create(...Args); }; //============================================================================================================================================== //---------------------------------------------------------------------------------------------------------------------------------------------- //-- Book //---------------------------------------------------------------------------------------------------------------------------------------------- export const B = { // Bibi.Book Path: '', PathDelimiter: ' > ', DataElement: null, Container: { Source: { Path: 'META-INF/container.xml' } }, Package: { Source: {}, Metadata: {}, Manifest: {}, Spine: [] }, Nav: {}, CoverImage: {}, FileDigit: 0 }; //============================================================================================================================================== //---------------------------------------------------------------------------------------------------------------------------------------------- //-- Loader //---------------------------------------------------------------------------------------------------------------------------------------------- export const L = { // Bibi.Loader Opened: false }; L.wait = () => { L.Waiting = true; O.Busy = false; O.HTML.classList.remove('busy'); O.HTML.classList.add('waiting'); E.dispatch('bibi:waits'); O.log(`(Waiting...)`, ''); I.note(''); return new Promise(resolve => L.wait.resolve = resolve).then(() => { L.Waiting = false; O.Busy = true; O.HTML.classList.add('busy'); O.HTML.classList.remove('waiting'); I.note(`Loading...`); return new Promise(resolve => setTimeout(resolve, 99)); }); }; L.openNewWindow = (HRef) => { const WO = window.open(HRef); return WO ? WO : location.href = HRef; }; L.play = () => { if(S['start-in-new-window']) return L.openNewWindow(location.href); L.Played = true; R.resetStage(); L.wait.resolve(); E.dispatch('bibi:played'); }; L.initializeBook = (BookInfo = {}) => new Promise((resolve, reject) => { const reject_failedToOpenTheBook = (Msg) => reject(`Failed to open the book (${ Msg })`); if(!BookInfo.Book && !BookInfo.BookData) return reject_failedToOpenTheBook(Bibi.ErrorMessages.DataInvalid); const BookDataFormat = typeof BookInfo.Book == 'string' ? 'URI' : typeof BookInfo.BookData == 'string' ? 'Base64' : typeof BookInfo.BookData == 'object' && BookInfo.BookData.size && BookInfo.BookData.type ? (BookInfo.BookData.name ? 'File' : 'Blob') : ''; if(!BookDataFormat) return reject_failedToOpenTheBook(Bibi.ErrorMessages.DataInvalid); B.Type = !S['book'] ? '' : S['zine'] ? 'Zine' : 'EPUB'; if(B.Type != 'EPUB') B.ZineData = { Source: { Path: 'zine.yaml' } }; if(BookDataFormat == 'URI') { // Online if(O.Local) return reject(`Bibi can't open books via ${ D['book'] ? 'data-bibi-book' : 'URL' } on local mode`); B.Path = BookInfo.Book; const RootSource = (B.Type == 'Zine' ? B.ZineData : B.Container).Source; const InitErrors = [], initialize_as = (FileOrFolder) => ({ Promised: ( FileOrFolder == 'folder' ? O.download(RootSource).then(() => (B.PathDelimiter = '/') && '') : O.RangeLoader ? O.extract(RootSource).then(() => 'on-the-fly') : O.loadZippedBookData( B.Path ).then(() => 'at-once') ).then(ExtractionPolicy => { B.ExtractionPolicy = ExtractionPolicy; //O.log(`Succeed to Open as ${ B.Type } ${ FileOrFolder }.`); resolve(`${ B.Type } ${ FileOrFolder }`); }).catch(Err => { InitErrors.push(Err = (/404/.test(String(Err)) ? Bibi.ErrorMessages.NotFound : String(Err).replace(/^Error: /, ''))); O.log(`Failed as ${ /^[aiueo]/i.test(B.Type) ? 'an' : 'a' } ${ B.Type } ${ FileOrFolder }: ` + Err); return Promise.reject(); }), or: function(fun) { return this.Promised.catch(fun); }, or_reject: function(fun) { return this.or(() => reject_failedToOpenTheBook(InitErrors.length < 2 || InitErrors[0] == InitErrors[1] ? InitErrors[0] : `as a file: ${ InitErrors[0] } / as a folder: ${ InitErrors[1] }`)); } }); O.isToBeExtractedIfNecessary(B.Path) ? initialize_as('file').or(() => initialize_as('folder').or_reject()) : initialize_as('folder').or_reject(); } else { let BookData = BookInfo.BookData; let FileOrData; const MIMETypeREs = { EPUB: /^application\/epub\+zip$/, Zine: /^application\/(zip|x-zip(-compressed)?)$/ }; const MIMETypeErrorMessage = 'File of this type is unacceptable'; if(BookDataFormat == 'File') { // Local-Archived EPUB/Zine File if(!S['accept-local-file']) return reject(`Local file is set to unacceptable`); if(!BookData.name) return reject(`File without a name is unacceptable`); if(!/\.[\w\d]+$/.test(BookData.name)) return reject(`Local file without extension is set to unacceptable`); if(!O.isToBeExtractedIfNecessary(BookData.name)) return reject(`File with this extension is set to unacceptable`); if(BookData.type) { if(/\.epub$/i.test(BookData.name) ? !MIMETypeREs['EPUB'].test(BookData.type) : /\.zip$/i.test(BookData.name) ? !MIMETypeREs['Zine'].test(BookData.type) : true) return reject(MIMETypeErrorMessage); } FileOrData = 'file'; B.Path = '[Local File] ' + BookData.name; } else { if(BookDataFormat == 'Base64') { // Base64-Encoded EPUB/Zine Data if(!S['accept-base64-encoded-data']) return reject(`Base64 encoded data is set to unacceptable`); try { const Bin = atob(BookData.replace(/^.*,/, '')); const Buf = new Uint8Array(Bin.length); for(let l = Bin.length, i = 0; i < l; i++) Buf[i] = Bin.charCodeAt(i); BookData = new Blob([Buf.buffer], { type: BookInfo.BookDataMIMEType }); if(!BookData || !BookData.size || !BookData.type) throw ''; } catch(_) { return reject(Bibi.ErrorMessages.DataInvalid); } B.Path = '[Base64 Encoded Data]'; } else { // Blob of EPUB/Zine Data if(!S['accept-blob-converted-data']) return reject(`Blob converted data is set to unacceptable`); B.Path = '[Blob Converted Data]'; } if(!MIMETypeREs['EPUB'].test(BookData.type) && !MIMETypeREs['Zine'].test(BookData.type)) return reject(MIMETypeErrorMessage); FileOrData = 'data'; } O.loadZippedBookData(BookData).then(() => { switch(B.Type) { case 'EPUB': case 'Zine': B.ExtractionPolicy = 'at-once'; return resolve(`${ B.Type } ${ FileOrData }`); default: return reject_failedToOpenTheBook(Bibi.ErrorMessages.DataInvalid); } }).catch(reject_failedToOpenTheBook); } }).then(InitializedAs => { delete S['book-data']; delete S['book-data-mimetype']; return (B.Type == 'Zine' ? X.Zine.loadZineData() : L.loadContainer().then(L.loadPackage)).then(() => E.dispatch('bibi:initialized-book')).then(() => InitializedAs); }); L.loadContainer = () => O.openDocument(B.Container.Source).then(L.loadContainer.process); L.loadContainer.process = (Doc) => B.Package.Source.Path = Doc.getElementsByTagName('rootfile')[0].getAttribute('full-path'); L.loadPackage = () => O.openDocument(B.Package.Source).then(L.loadPackage.process); L.loadPackage.process = (Doc) => { // This is Used also from the Zine Extention. const _Package = Doc.getElementsByTagName('package' )[0]; const _Metadata = Doc.getElementsByTagName('metadata')[0], Metadata = B.Package.Metadata; const _Manifest = Doc.getElementsByTagName('manifest')[0], Manifest = B.Package.Manifest; const _Spine = Doc.getElementsByTagName('spine' )[0], Spine = B.Package.Spine; const SourcePaths = {}; // ================================================================================ // METADATA // -------------------------------------------------------------------------------- const DCNS = _Package.getAttribute('xmlns:dc') || _Metadata.getAttribute('xmlns:dc'); const UIDID = _Package.getAttribute('unique-identifier'), UIDE = UIDID ? Doc.getElementById(UIDID) : null, UIDTC = UIDE ? UIDE.textContent : ''; Metadata['unique-identifier'] = UIDTC ? UIDTC.trim() : ''; ['identifier', 'language', 'title', 'creator', 'publisher'].forEach(Pro => sML.forEach(Doc.getElementsByTagNameNS(DCNS, Pro))(_Meta => (Metadata[Pro] ? Metadata[Pro] : Metadata[Pro] = []).push(_Meta.textContent.trim()))); sML.forEach(_Metadata.getElementsByTagName('meta'))(_Meta => { if(_Meta.getAttribute('refines')) return; // Should be solved. let Property = _Meta.getAttribute('property'); if(Property) { if(/^dcterms:/.test(Property)) { if(!Metadata[Property]) Metadata[Property] = []; Metadata[Property].push(_Meta.textContent.trim()); // 'dcterms:~' } else { Metadata[Property] = _Meta.textContent.trim(); // ex.) 'rendition:~' } } else { let Name = _Meta.getAttribute('name'); if(Name) { Metadata[Name] = _Meta.getAttribute('content').trim(); // ex.) 'cover' } } }); // -------------------------------------------------------------------------------- if(!Metadata['identifier']) Metadata['identifier'] = Metadata['dcterms:identifier'] || []; if(!Metadata['language' ]) Metadata['language' ] = Metadata['dcterms:language' ] || ['en']; if(!Metadata['title' ]) Metadata['title' ] = Metadata['dcterms:title' ] || Metadata['identifier']; // -------------------------------------------------------------------------------- if(!Metadata['rendition:layout' ] ) Metadata['rendition:layout' ] = 'reflowable'; if(Metadata['omf:version']) Metadata['rendition:layout'] = 'pre-paginated'; if(!Metadata['rendition:orientation'] || Metadata['rendition:orientation'] == 'auto') Metadata['rendition:orientation'] = 'portrait'; if(!Metadata['rendition:spread' ] || Metadata['rendition:spread' ] == 'auto') Metadata['rendition:spread' ] = 'landscape'; if( Metadata[ 'original-resolution']) Metadata[ 'original-resolution'] = O.getViewportByOriginalResolution(Metadata[ 'original-resolution']); if( Metadata[ 'rendition:viewport']) Metadata[ 'rendition:viewport'] = O.getViewportByMetaContent( Metadata[ 'rendition:viewport']); if( Metadata['fixed-layout-jp:viewport']) Metadata['fixed-layout-jp:viewport'] = O.getViewportByMetaContent( Metadata['fixed-layout-jp:viewport']); if( Metadata[ 'omf:viewport']) Metadata[ 'omf:viewport'] = O.getViewportByMetaContent( Metadata[ 'omf:viewport']); B.ICBViewport = Metadata['original-resolution'] || Metadata['rendition:viewport'] || Metadata['fixed-layout-jp:viewport'] || Metadata['omf:viewport'] || null; // ================================================================================ // MANIFEST // -------------------------------------------------------------------------------- const PackageDir = B.Package.Source.Path.replace(/\/?[^\/]+$/, ''); sML.forEach(_Manifest.getElementsByTagName('item'))(_Item => { let Source = { 'id': _Item.getAttribute('id'), 'href': _Item.getAttribute('href'), 'media-type': _Item.getAttribute('media-type') }; if(!Source['id'] || !Source['href'] || (!Source['media-type'] && B.Type == 'EPUB')) return false; Source.Path = O.getPath(PackageDir, Source['href']); if(Manifest[Source.Path]) Source = Object.assign(Manifest[Source.Path], Source); if(!Source.Content) Source.Content = ''; Source.Of = []; let Properties = _Item.getAttribute('properties'); if(Properties) { Properties = Properties.trim().replace(/\s+/g, ' ').split(' '); if(Properties.includes('cover-image')) B.CoverImage.Source = Source; else if(Properties.includes('nav' )) B.Nav.Source = Source, B.Nav.Type = 'Navigation Document'; } const FallbackItemID = _Item.getAttribute('fallback'); if(FallbackItemID) Source['fallback'] = FallbackItemID; Manifest[Source.Path] = Source; SourcePaths[Source['id']] = Source.Path; }); [B.Container, B.Package].forEach(Meta => { if(Meta && Meta.Source) Meta.Source.Content = ''; }); // ================================================================================ // SPINE // -------------------------------------------------------------------------------- if(!B.Nav.Source) { const Source = Manifest[SourcePaths[_Spine.getAttribute('toc')]]; if(Source) B.Nav.Source = Source, B.Nav.Type = 'TOC-NCX'; } if( B.Nav.Source) B.Nav.Source.Of.push( B.Nav); if(B.CoverImage.Source) B.CoverImage.Source.Of.push(B.CoverImage); // -------------------------------------------------------------------------------- B.PPD = _Spine.getAttribute('page-progression-direction'); if(!B.PPD || !/^(ltr|rtl)$/.test(B.PPD)) B.PPD = S['default-page-progression-direction']; // default; // -------------------------------------------------------------------------------- const PropertyRE = /^((rendition:)?(layout|orientation|spread|page-spread))-(.+)$/; let SpreadBefore, SpreadAfter; if(B.PPD == 'rtl') SpreadBefore = 'right', SpreadAfter = 'left'; else SpreadBefore = 'left', SpreadAfter = 'right'; const SpreadsDocumentFragment = document.createDocumentFragment(); sML.forEach(_Spine.getElementsByTagName('itemref'))(ItemRef => { const IDRef = ItemRef.getAttribute('idref'); if(!IDRef) return false; const Source = Manifest[SourcePaths[IDRef]]; if(!Source) return false; const Item = sML.create('iframe', { className: 'item', scrolling: 'no', allowtransparency: 'true' /*, TimeCard: {}, stamp: function(What) { O.stamp(What, this.TimeCard); }*/ }); Item['idref'] = IDRef; Item.Source = Source; Item.AnchorPath = Source.Path; Item.FallbackChain = []; if(S['prioritise-fallbacks']) while(Item.Source['fallback']) { const FallbackItem = Manifest[SourcePaths[Item.Source['fallback']]]; if(FallbackItem) Item.FallbackChain.push(Item.Source = FallbackItem); else delete Item.Source['fallback']; } Item.Source.Of.push(Item); let Properties = ItemRef.getAttribute('properties'); if(Properties) { Properties = Properties.trim().replace(/\s+/g, ' ').split(' '); Properties.forEach(Pro => { if(PropertyRE.test(Pro)) ItemRef[Pro.replace(PropertyRE, '$1')] = Pro.replace(PropertyRE, '$4'); }); } Item['rendition:layout'] = ItemRef['rendition:layout'] || Metadata['rendition:layout']; Item['rendition:orientation'] = ItemRef['rendition:orientation'] || Metadata['rendition:orientation']; Item['rendition:spread'] = ItemRef['rendition:spread'] || Metadata['rendition:spread']; Item['rendition:page-spread'] = ItemRef['rendition:page-spread'] || ItemRef['page-spread'] || undefined; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Item.IndexInSpine = Spine.push(Item) - 1; if(ItemRef.getAttribute('linear') == 'no') { Item['linear'] = 'no'; Item.IndexInNonLinearItems = R.NonLinearItems.push(Item) - 1; } else { Item['linear'] = 'yes'; Item.Index = R.Items.push(Item) - 1; let Spread = null; if(Item['rendition:layout'] == 'pre-paginated' && Item['rendition:page-spread'] == SpreadAfter && Item.Index > 0) { const PreviousItem = R.Items[Item.Index - 1]; if(Item['rendition:layout'] == 'pre-paginated' && PreviousItem['rendition:page-spread'] == SpreadBefore) { PreviousItem.SpreadPair = Item; Item.SpreadPair = PreviousItem; Spread = Item.Spread = PreviousItem.Spread; Spread.Box.classList.remove('single-item-spread-before', 'single-item-spread-' + SpreadBefore); Spread.Box.classList.add(Item['rendition:layout']); } } if(!Spread) { Spread = Item.Spread = sML.create('div', { className: 'spread', Items: [], Pages: [], Index: R.Spreads.length }); Spread.Box = sML.create('div', { className: 'spread-box ' + Item['rendition:layout'], Inside: Spread, Spread: Spread }); if(Item['rendition:page-spread']) { Spread.Box.classList.add('single-item-spread-' + Item['rendition:page-spread']); switch(Item['rendition:page-spread']) { case SpreadBefore: Spread.Box.classList.add('single-item-spread-before'); break; case SpreadAfter: Spread.Box.classList.add('single-item-spread-after' ); break; } } R.Spreads.push(SpreadsDocumentFragment.appendChild(Spread.Box).appendChild(Spread)); } Item.IndexInSpread = Spread.Items.push(Item) - 1; Item.Box = Spread.appendChild(sML.create('div', { className: 'item-box ' + Item['rendition:layout'], Inside: Item, Item: Item })); Item.Pages = []; if(Item['rendition:layout'] == 'pre-paginated') { Item.PrePaginated = true; if(Item.IndexInSpread == 0) Spread.PrePaginated = true; const Page = sML.create('span', { className: 'page', Spread: Spread, Item: Item, IndexInItem: 0 }); Item.Pages.push(Item.Box.appendChild(Page)); I.PageObserver.observePageIntersection(Page); } else { Item.PrePaginated = Spread.PrePaginated = false; } } }); R.createSpine(SpreadsDocumentFragment); // -------------------------------------------------------------------------------- B.FileDigit = String(Spine.length).length; // ================================================================================ B.ID = Metadata['unique-identifier'] || Metadata['identifier'][0] || ''; B.Language = Metadata['language'][0].split('-')[0]; B.Title = Metadata['title' ].join(', '); B.Creator = !Metadata['creator' ] ? '' : Metadata['creator' ].join(', '); B.Publisher = !Metadata['publisher' ] ? '' : Metadata['publisher'].join(', '); const FullTitleFragments = [B.Title]; if(B.Creator) FullTitleFragments.push(B.Creator); if(B.Publisher) FullTitleFragments.push(B.Publisher); B.FullTitle = FullTitleFragments.join(' - ').replace(/&?/gi, '&').replace(/<?/gi, '<').replace(/>?/gi, '>'); O.Title.innerHTML = ''; O.Title.appendChild(document.createTextNode(B.FullTitle + ' | ' + (S['website-name-in-title'] ? S['website-name-in-title'] : 'Published with Bibi'))); try { O.Info.querySelector('h1').innerHTML = document.title; } catch(_) {} B.WritingMode = /^(zho?|chi|kor?|ja|jpn)$/.test(B.Language) ? (B.PPD == 'rtl' ? 'tb-rl' : 'lr-tb') : /^(aze?|ara?|ui?g|urd?|kk|kaz|ka?s|ky|kir|kur?|sn?d|ta?t|pu?s|bal|pan?|fas?|per|ber|msa?|may|yid?|heb?|arc|syr|di?v)$/.test(B.Language) ? 'rl-tb' : /^(mo?n)$/.test(B.Language) ? 'tb-lr' : 'lr-tb'; B.AllowPlaceholderItems = (B.ExtractionPolicy != 'at-once' && Metadata['rendition:layout'] == 'pre-paginated'); // ================================================================================ E.dispatch('bibi:processed-package'); }; L.createCover = () => { const VCover = I.Veil.Cover, PCover = I.Panel.BookInfo.Cover; VCover.Info.innerHTML = PCover.Info.innerHTML = (() => { const BookID = []; if(B.Title) BookID.push(`${ L.createCover.optimizeString(B.Title) }`); if(B.Creator) BookID.push( `${ L.createCover.optimizeString(B.Creator) }` ); if(B.Publisher) BookID.push( `${ L.createCover.optimizeString(B.Publisher) }` ); return BookID.join(' '); })(); return Promise.resolve(new Promise((resolve, reject) => { if(!B.CoverImage.Source || !B.CoverImage.Source.Path) return reject(); let TimedOut = false; const TimerID = setTimeout(() => { TimedOut = true; reject(); }, 5000); O.file(B.CoverImage.Source, { URI: true }).then(Item => { if(!TimedOut) resolve(Item.URI); }).catch(() => { if(!TimedOut) reject(); }).then(() => clearTimeout(TimerID)); }).then(ImageURI => { VCover.className = PCover.className = 'with-cover-image'; sML.style(VCover, { 'background-image': 'url(' + ImageURI + ')' }); PCover.insertBefore(sML.create('img', { src: ImageURI }), PCover.Info); }).catch(() => { VCover.className = PCover.className = 'without-cover-image'; VCover.insertBefore(I.getBookIcon(), VCover.Info); PCover.insertBefore(I.getBookIcon(), PCover.Info); })); }; L.createCover.optimizeString = (Str) => `` + Str.replace( /([  ・/]+)/g, '$1' ) + ``; L.loadNavigation = () => O.openDocument(B.Nav.Source).then(Doc => { const PNav = I.Panel.BookInfo.Navigation = I.Panel.BookInfo.insertBefore(sML.create('div', { id: 'bibi-panel-bookinfo-navigation' }), I.Panel.BookInfo.firstElementChild); PNav.innerHTML = ''; const NavContent = document.createDocumentFragment(); if(B.Nav.Type == 'Navigation Document') { sML.forEach(Doc.querySelectorAll('nav'))(Nav => { switch(Nav.getAttribute('epub:type')) { case 'toc': Nav.classList.add('bibi-nav-toc'); break; case 'landmarks': Nav.classList.add('bibi-nav-landmarks'); break; case 'page-list': Nav.classList.add('bibi-nav-page-list'); break; } sML.forEach(Nav.getElementsByTagName('*'))(Ele => Ele.removeAttribute('style')); NavContent.appendChild(Nav); }); } else { const makeNavULTree = (Ele) => { const ChildNodes = Ele.childNodes; let UL = undefined; for(let l = ChildNodes.length, i = 0; i < l; i++) { if(ChildNodes[i].nodeType != 1 || !/^navPoint$/i.test(ChildNodes[i].tagName)) continue; const NavPoint = ChildNodes[i]; const NavLabel = NavPoint.getElementsByTagName('navLabel')[0]; const Content = NavPoint.getElementsByTagName('content')[0]; const Text = NavPoint.getElementsByTagName('text')[0]; if(!UL) UL = document.createElement('ul'); const LI = sML.create('li', { id: NavPoint.getAttribute('id') }); LI.setAttribute('playorder', NavPoint.getAttribute('playorder')); const A = sML.create('a', { href: Content.getAttribute('src'), innerHTML: Text.innerHTML.trim() }); UL.appendChild(LI).appendChild(A); const ChildUL = makeNavULTree(NavPoint); if(ChildUL) LI.appendChild(ChildUL); } return UL; }; const NavUL = makeNavULTree(Doc.getElementsByTagName('navMap')[0]); if(NavUL) NavContent.appendChild(document.createElement('nav')).appendChild(NavUL); } PNav.appendChild(NavContent); L.coordinateLinkages(B.Nav.Source.Path, PNav, 'InNav'); if(B.Nav.Source.Of.length == 1) B.Nav.Source.Content = ''; return PNav; }); L.coordinateLinkages = (BasePath, RootElement, InNav) => { const As = RootElement.getElementsByTagName('a'); if(!As) return; for(let l = As.length, i = 0; i < l; i++) { const A = As[i]; if(InNav) { A.NavANumber = i + 1; A.addEventListener(E['pointerdown'], Eve => Eve.stopPropagation()); A.addEventListener(E['pointerup'], Eve => Eve.stopPropagation()); } let HRefPathInSource = A.getAttribute('href'), HRefAttribute = 'href'; if(!HRefPathInSource) { HRefPathInSource = A.getAttribute('xlink:href'); if(HRefPathInSource) { HRefAttribute = 'xlink:href'; } else { if(InNav) { A.addEventListener('click', Eve => { Eve.preventDefault(); Eve.stopPropagation(); return false; }); A.classList.add('bibi-bookinfo-inactive-link'); } continue; } } if(/^[a-zA-Z]+:/.test(HRefPathInSource)) { if(HRefPathInSource.split('#')[0] == location.href.split('#')[0]) { const HRefHashInSource = HRefPathInSource.split('#')[1]; HRefPathInSource = (HRefHashInSource ? '#' + HRefHashInSource : R.Items[0].AnchorPath) } else { A.addEventListener('click', Eve => { Eve.preventDefault(); Eve.stopPropagation(); window.open(A.href); return false; }); continue; } } const HRefPath = O.getPath(BasePath.replace(/\/?([^\/]+)$/, ''), (!/^\.*\/+/.test(HRefPathInSource) ? './' : '') + (/^#/.test(HRefPathInSource) ? BasePath.replace(/^.+?([^\/]+)$/, '$1') : '') + HRefPathInSource); const HRefFnH = HRefPath.split('#'); const HRefFile = HRefFnH[0] ? HRefFnH[0] : BasePath; const HRefHash = HRefFnH[1] ? HRefFnH[1] : ''; sML.forEach(R.Items)(Item => { if(HRefFile == Item.AnchorPath) { A.setAttribute('data-bibi-original-href', HRefPathInSource); A.setAttribute(HRefAttribute, B.Path + '/' + HRefPath); A.InNav = InNav; A.Destination = { ItemIndex: Item.Index }; // not IIPP. ElementSelector may be added. if(Item['rendition:layout'] == 'pre-paginated') A.Destination.PageIndexInItem = 0; else if(HRefHash) A.Destination.ElementSelector = '#' + HRefHash; L.coordinateLinkages.setJump(A); return 'break'; //// break sML.forEach() } }); if(HRefHash && /^epubcfi\(.+?\)$/.test(HRefHash)) { A.setAttribute('data-bibi-original-href', HRefPathInSource); A.setAttribute(HRefAttribute, B.Path + '/#' + HRefHash); if(X['EPUBCFI']) { A.InNav = InNav; A.Destination = X['EPUBCFI'].getDestination(HRefHash); L.coordinateLinkages.setJump(A); } else { A.addEventListener('click', Eve => { Eve.preventDefault(); Eve.stopPropagation(); I.note(O.Language == 'ja' ? 'このリンクの利用には EPUBCFI エクステンションが必要です' : '"EPUBCFI" extension is required to use this link.'); return false; }); } } if(InNav && R.StartOn && R.StartOn.Nav == (i + 1) && A.Destination) R.StartOn = A.Destination; } }; L.coordinateLinkages.setJump = (A) => A.addEventListener('click', Eve => { Eve.preventDefault(); Eve.stopPropagation(); if(A.Destination) new Promise(resolve => A.InNav ? I.Panel.toggle().then(resolve) : resolve()).then(() => { if(L.Opened) { I.History.add(); return R.focusOn({ Destination: A.Destination, Duration: 0 }).then(Destination => I.History.add({ UI: B, SumUp: false, Destination: Destination })); } if(!L.Waiting) return false; if(S['start-in-new-window']) return L.openNewWindow(location.href + (location.hash ? '&' : '#') + 'jo(nav=' + A.NavANumber + ')'); R.StartOn = A.Destination; L.play(); }); return false; }); L.preprocessResources = () => { E.dispatch('bibi:is-going-to:preprocess-resources'); const Promises = [], PreprocessedResources = [], pushItemPreprocessingPromise = (Item, URI) => Promises.push(O.file(Item, { Preprocess: true, URI: URI }).then(() => PreprocessedResources.push(Item))); if(B.ExtractionPolicy) for(const FilePath in B.Package.Manifest) { const Item = B.Package.Manifest[FilePath]; if(/\/(css|javascript)$/.test(Item['media-type'])) { // CSSs & JavaScripts in Manifest if(!Promises.length) O.log(`Preprocessing Resources...`, ''); pushItemPreprocessingPromise(Item, true); } } return Promise.all(Promises).then(() => {/* if(B.ExtractionPolicy != 'at-once' && (S.BRL == 'pre-paginated' || (sML.UA.Chromium || sML.UA.WebKit || sML.UA.Gecko))) return resolve(PreprocessedResources); R.Items.forEach(Item => pushItemPreprocessingPromise(Item, O.isBin(Item))); // Spine Items return Promise.all(Promises).then(() => resolve(PreprocessedResources));*/ if(PreprocessedResources.length) { O.log(`Preprocessed: %O`, PreprocessedResources); O.log(`Preprocessed. (${ PreprocessedResources.length } Resource${ PreprocessedResources.length > 1 ? 's' : '' })`, ''); } E.dispatch('bibi:preprocessed-resources'); }); }; L.loadSpread = (Spread, Opt = {}) => new Promise((resolve, reject) => { Spread.AllowPlaceholderItems = (S['allow-placeholders'] && Opt.AllowPlaceholderItems); let LoadedItemsInSpread = 0, SkippedItemsInSpread = 0; Spread.Items.forEach(Item => { L.loadItem(Item, { IsPlaceholder: Opt.AllowPlaceholderItems }) .then(() => LoadedItemsInSpread++) // Loaded .catch(() => SkippedItemsInSpread++) // Skipped .then(() => { if(LoadedItemsInSpread + SkippedItemsInSpread == Spread.Items.length) /*(SkippedItemsInSpread ? reject : resolve)*/resolve(Spread); }); }); }); L.loadItem = (Item, Opt = {}) => { const IsPlaceholder = (S['allow-placeholders'] && Item['rendition:layout'] == 'pre-paginated' && Opt.IsPlaceholder); if(IsPlaceholder === Item.IsPlaceholder) return Item.Loading ? Item.Loading : Promise.resolve(Item); Item.IsPlaceholder = IsPlaceholder; const ItemBox = Item.Box; ItemBox.classList.remove('loaded'); ItemBox.classList.toggle('placeholder', Item.IsPlaceholder); return Item.Loading = ( // Promise Item.IsPlaceholder ? Promise.reject() : Item.ContentURL ? Promise.resolve() : /\.(html?|xht(ml)?|xml)$/i.test(Item.Source.Path) ? // (X)HTML O.file(Item.Source, { Preprocess: (B.ExtractionPolicy || sML.UA.Gecko), // Preprocess if archived (or Gecko. For such books as styled only with -webkit/epub- prefixed properties. It's NOT Gecko's fault but requires preprocessing.) initialize: () => { if(!S['allow-scripts-in-content']) { Item.Source.Content = Item.Source.Content.replace(//ig, ''); O.sanitizeItemSource(Item.Source, { As: 'HTML' }); } } }).then(ItemSource => ItemSource.Content ) : /\.(gif|jpe?g|png)$/i.test(Item.Source.Path) ? // Bitmap-in-Spine O.file(Item.Source, { URI: true }).then(ItemSource => [ (Item['rendition:layout'] == 'pre-paginated' && B.ICBViewport) ? `` : '', `` // URI is BlobURL or URI ]) : /\.(svg)$/i.test(Item.Source.Path) ? // SVG-in-Spine O.file(Item.Source, { Preprocess: (B.ExtractionPolicy ? true : false), initialize: () => { const StyleSheetRE = /<\?xml-stylesheet\s*(.+?)\s*\?>/g, MatchedStyleSheets = Item.Source.Content.match(StyleSheetRE); if(!S['allow-scripts-in-content']) O.sanitizeItemSource(Item.Source, { As: 'SVG' }); Item.Source.Content = (MatchedStyleSheets ? MatchedStyleSheets.map(SS => SS.replace(StyleSheetRE, ``)).join('') : '') + '' + Item.Source.Content; // Join for preprocessing. } }).then(ItemSource => ItemSource.Content.split('') ) : Item.Skipped = true && Promise.resolve([]) ).then(ItemSourceContent => new Promise(resolve => { const DefaultStyleID = 'bibi-default-style'; if(!Item.ContentURL) { let HTML = typeof ItemSourceContent == 'string' ? ItemSourceContent : [``, ``, ``, ``, `${ B.FullTitle } - #${ Item.Index + 1 }/${ R.Items.length }`, (ItemSourceContent[0] ? ItemSourceContent[0] + '\n' : '') + ``, ``, (ItemSourceContent[1] ? ItemSourceContent[1] + '\n' : '') + ``, `` ].join('\n'); HTML = HTML.replace(/(]+)?>)/i, `$1\n` + (!B.ExtractionPolicy && !Item.Source.Preprocessed ? `\n` : '')); if(O.Local || sML.UA.LINE || sML.UA.Trident || sML.UA.EdgeHTML) { // Legacy Microsoft Browsers do not accept DataURLs for src of