mirror of
https://github.com/miggymofongo/asteroid.git
synced 2024-11-24 14:10:27 +00:00
6116 lines
321 KiB
JavaScript
6116 lines
321 KiB
JavaScript
/*!
|
||
* (℠)
|
||
* ## 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!`, '<b:>');
|
||
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': `<span>Sorry....</span> <span>Your Browser Is</span> <span>Not Compatible.</span>`,
|
||
'ja': `<span>大変申し訳ありません。</span> <span>お使いのブラウザでは、</span><span>動作しません。</span>`
|
||
});
|
||
} else { // Say Welcome!
|
||
I.note(`<span class="non-visual">Welcome!</span>`);
|
||
}
|
||
{ // 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' : '' }...`, '<g:>');
|
||
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.`, '</g>')
|
||
}).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...`, '<g:>');
|
||
return L.initializeBook(BookInfo).then(InitializedAs => {
|
||
O.log(`${ InitializedAs }: %O`, B);
|
||
O.log(`Initialized. (as ${ /^[aiueo]/i.test(InitializedAs) ? 'an' : 'a' } ${ InitializedAs })`, '</g>');
|
||
});
|
||
}).then(() => {
|
||
S.update();
|
||
R.updateOrientation();
|
||
R.resetStage();
|
||
}).then(() => {
|
||
// Create Cover
|
||
O.log(`Creating Cover...`, '<g:>');
|
||
if(B.CoverImage.Source) {
|
||
O.log(`Cover Image: %O`, B.CoverImage.Source);
|
||
O.log(`Will Be Created.`, '</g>');
|
||
} else {
|
||
O.log(`Will Be Created. (w/o Image)`, '</g>');
|
||
}
|
||
return L.createCover(); // ← loading is async
|
||
}).then(() => {
|
||
// Load Navigation
|
||
if(!B.Nav.Source) return O.log(`No Navigation.`)
|
||
O.log(`Loading Navigation...`, '<g:>');
|
||
return L.loadNavigation().then(PNav => {
|
||
O.log(`${ B.Nav.Type }: %O`, B.Nav.Source);
|
||
O.log(`Loaded.`, '</g>');
|
||
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...`, '<g:>');
|
||
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 })`, '</g>');
|
||
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!`, '</b>');
|
||
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(`<strong>This Bibi seems to be a</strong> <strong>Development Version</strong>`);
|
||
Bibi.createDevNote.appendParagraph(`<span>Please don't forget</span> <span>to create a production version</span> <span>before publishing on the Internet.</span>`);
|
||
Bibi.createDevNote.appendParagraph(`<span class="non-visual">(To create a production version, run it on terminal: \`</span><code>npm run build</code><span class="non-visual">\`)</span>`);
|
||
Bibi.createDevNote.appendParagraph(`<em>Close</em>`, '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/>');
|
||
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(`<strong>${ L.createCover.optimizeString(B.Title) }</strong>`);
|
||
if(B.Creator) BookID.push( `<em>${ L.createCover.optimizeString(B.Creator) }</em>` );
|
||
if(B.Publisher) BookID.push( `<span>${ L.createCover.optimizeString(B.Publisher) }</span>` );
|
||
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) => `<span>` + Str.replace(
|
||
/([ ・/]+)/g, '</span><span>$1'
|
||
) + `</span>`;
|
||
|
||
|
||
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' ? '<small>このリンクの利用には EPUBCFI エクステンションが必要です</small>' : '"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...`, '<g:>');
|
||
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' : '' })`, '</g>');
|
||
}
|
||
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(/<script(\s+[\w\-]+(\s*=\s*('[^'']*'|"[^""]*"))?)*\s*\/>/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) ? `<meta name="viewport" content="width=${ B.ICBViewport.Width }, height=${ B.ICBViewport.Height }" />` : '',
|
||
`<img class="bibi-spine-item-image" alt="" src="${ Item.Source.URI }" />` // 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, `<link rel="stylesheet" $1 />`)).join('') : '') + '<bibi:boundary/>' + Item.Source.Content; // Join for preprocessing.
|
||
}
|
||
}).then(ItemSource =>
|
||
ItemSource.Content.split('<bibi:boundary/>')
|
||
)
|
||
: Item.Skipped = true && Promise.resolve([])
|
||
).then(ItemSourceContent => new Promise(resolve => {
|
||
const DefaultStyleID = 'bibi-default-style';
|
||
if(!Item.ContentURL) {
|
||
let HTML = typeof ItemSourceContent == 'string' ? ItemSourceContent : [`<!DOCTYPE html>`,
|
||
`<html>`,
|
||
`<head>`,
|
||
`<meta charset="utf-8" />`,
|
||
`<title>${ B.FullTitle } - #${ Item.Index + 1 }/${ R.Items.length }</title>`,
|
||
(ItemSourceContent[0] ? ItemSourceContent[0] + '\n' : '') +
|
||
`</head>`,
|
||
`<body>`,
|
||
(ItemSourceContent[1] ? ItemSourceContent[1] + '\n' : '') +
|
||
`</body>`,
|
||
`</html>`
|
||
].join('\n');
|
||
HTML = HTML.replace(/(<head(\s[^>]+)?>)/i, `$1\n<link rel="stylesheet" id="${ DefaultStyleID }" href="${ Bibi.BookStyleURL }" />` + (!B.ExtractionPolicy && !Item.Source.Preprocessed ? `\n<base href="${ O.fullPath(Item.Source.Path) }" />` : ''));
|
||
if(O.Local || sML.UA.LINE || sML.UA.Trident || sML.UA.EdgeHTML) { // Legacy Microsoft Browsers do not accept DataURLs for src of <iframe>. Also LINE in-app-browser is probably the same as it.
|
||
HTML = HTML.replace(/^<\?.+?\?>/, '').replace('</head>', `<script id="bibi-onload">window.addEventListener('load', function() { parent.R.Items[${ Item.Index }].onLoaded(); return false; });</script>\n</head>`);
|
||
Item.onLoaded = () => {
|
||
resolve();
|
||
Item.contentDocument.head.removeChild(Item.contentDocument.getElementById('bibi-onload'));
|
||
delete Item.onLoaded;
|
||
};
|
||
Item.src = '';
|
||
ItemBox.insertBefore(Item, ItemBox.firstChild);
|
||
Item.contentDocument.open(); Item.contentDocument.write(HTML); Item.contentDocument.close();
|
||
return;
|
||
}
|
||
Item.ContentURL = O.createBlobURL('Text', HTML, S['allow-scripts-in-content'] && /\.(xht(ml)?|xml)$/i.test(Item.Source.Path) ? 'application/xhtml+xml' : 'text/html'), Item.Source.Content = '';
|
||
}
|
||
Item.onload = resolve;
|
||
Item.src = Item.ContentURL;
|
||
ItemBox.insertBefore(Item, ItemBox.firstChild);
|
||
})).then(() => {
|
||
return L.postprocessItem(Item);
|
||
}).then(() => {
|
||
ItemBox.classList.add('loaded');
|
||
E.dispatch('bibi:loaded-item', Item);
|
||
// Item.stamp('Loaded');
|
||
Item.Loaded = true;
|
||
Item.Turned = 'Up';
|
||
}).catch(() => { // Placeholder
|
||
if(Item.parentElement) Item.parentElement.removeChild(Item);
|
||
Item.onload = Item.onLoaded = undefined;
|
||
Item.src = '';
|
||
Item.HTML = Item.Head = Item.Body = Item.Pages[0];
|
||
Item.Loaded = false;
|
||
Item.Turned = 'Down';
|
||
}).then(() => {
|
||
delete Item.Loading;
|
||
return Promise.resolve(Item);
|
||
});
|
||
};
|
||
|
||
|
||
L.postprocessItem = (Item) => {
|
||
// Item.stamp('Postprocess');
|
||
Item.HTML = Item.contentDocument.getElementsByTagName('html')[0]; Item.HTML.classList.add(...sML.Environments);
|
||
Item.Head = Item.contentDocument.getElementsByTagName('head')[0];
|
||
Item.Body = Item.contentDocument.getElementsByTagName('body')[0];
|
||
Item.HTML.Item = Item.Head.Item = Item.Body.Item = Item;
|
||
const XMLLang = Item.HTML.getAttribute('xml:lang'), Lang = Item.HTML.getAttribute('lang');
|
||
if(!XMLLang && !Lang) Item.HTML.setAttribute('xml:lang', B.Language), Item.HTML.setAttribute('lang', B.Language);
|
||
else if(!XMLLang ) Item.HTML.setAttribute('xml:lang', Lang);
|
||
else if( !Lang) Item.HTML.setAttribute('lang', XMLLang);
|
||
sML.forEach(Item.Body.getElementsByTagName('link'))(Link => Item.Head.appendChild(Link));
|
||
sML.appendCSSRule(Item.contentDocument, 'html', '-webkit-text-size-adjust: 100%;');
|
||
if(sML.UA.Trident) sML.forEach(Item.Body.getElementsByTagName('svg'))(SVG => {
|
||
const ChildImages = SVG.getElementsByTagName('image');
|
||
if(ChildImages.length == 1) {
|
||
const ChildImage = ChildImages[0];
|
||
if(ChildImage.getAttribute('width') && ChildImage.getAttribute('height')) {
|
||
SVG.setAttribute('width', ChildImage.getAttribute('width'));
|
||
SVG.setAttribute('height', ChildImage.getAttribute('height'));
|
||
}
|
||
}
|
||
});
|
||
L.coordinateLinkages(Item.Source.Path, Item.Body);
|
||
const Lv1Eles = Item.contentDocument.querySelectorAll('body>*:not(script):not(style)');
|
||
if(Lv1Eles && Lv1Eles.length == 1) {
|
||
const Lv1Ele = Item.contentDocument.querySelector('body>*:not(script):not(style)');
|
||
if( /^svg$/i.test(Lv1Ele.tagName)) Item.Outsourcing = Item.OnlySingleSVG = true;
|
||
else if( /^img$/i.test(Lv1Ele.tagName)) Item.Outsourcing = Item.OnlySingleIMG = true;
|
||
else if( /^iframe$/i.test(Lv1Ele.tagName)) Item.Outsourcing = true;
|
||
else if(!O.getElementInnerText(Item.Body)) Item.Outsourcing = true;
|
||
}
|
||
return (Item['rendition:layout'] == 'pre-paginated' ? Promise.resolve() : L.patchItemStyles(Item)).then(() => {
|
||
E.dispatch('bibi:postprocessed-item', Item);
|
||
// Item.stamp('Postprocessed');
|
||
return Item;
|
||
});
|
||
};
|
||
|
||
|
||
L.patchItemStyles = (Item) => new Promise(resolve => { // only for reflowable.
|
||
Item.StyleSheets = [];
|
||
sML.forEach(Item.HTML.querySelectorAll('link, style'))(SSEle => {
|
||
if(/^link$/i.test(SSEle.tagName)) {
|
||
if(!SSEle.href) return;
|
||
if(!/^(alternate )?stylesheet$/.test(SSEle.rel)) return;
|
||
if((sML.UA.Safari || sML.OS.iOS) && SSEle.rel == 'alternate stylesheet') return; //// Safari does not count "alternate stylesheet" in document.styleSheets.
|
||
}
|
||
Item.StyleSheets.push(SSEle);
|
||
});
|
||
const checkCSSLoadingAndResolve = () => {
|
||
if(Item.contentDocument.styleSheets.length < Item.StyleSheets.length) return false;
|
||
clearInterval(Item.CSSLoadingTimerID);
|
||
delete Item.CSSLoadingTimerID;
|
||
resolve();
|
||
return true;
|
||
};
|
||
if(!checkCSSLoadingAndResolve()) Item.CSSLoadingTimerID = setInterval(checkCSSLoadingAndResolve, 33);
|
||
}).then(() => {
|
||
if(!Item.Source.Preprocessed) {
|
||
if(B.Package.Metadata['ebpaj:guide-version']) {
|
||
const Versions = B.Package.Metadata['ebpaj:guide-version'].split('.');
|
||
if(Versions[0] * 1 == 1 && Versions[1] * 1 == 1 && Versions[2] * 1 <=3) Item.Body.style.textUnderlinePosition = 'under left';
|
||
}
|
||
if(sML.UA.Trident) {
|
||
//if(B.ExtractionPolicy == 'at-once') return false;
|
||
const IsCJK = /^(zho?|chi|kor?|ja|jpn)$/.test(B.Language);
|
||
O.editCSSRules(Item.contentDocument, CSSRule => {
|
||
if(/(-(epub|webkit)-)?column-count: 1; / .test(CSSRule.cssText)) CSSRule.style.columnCount = CSSRule.style.msColumnCount = 'auto';
|
||
if(/(-(epub|webkit)-)?writing-mode: vertical-rl; / .test(CSSRule.cssText)) CSSRule.style.writingMode = 'tb-rl';
|
||
if(/(-(epub|webkit)-)?writing-mode: vertical-lr; / .test(CSSRule.cssText)) CSSRule.style.writingMode = 'tb-lr';
|
||
if(/(-(epub|webkit)-)?writing-mode: horizontal-tb; / .test(CSSRule.cssText)) CSSRule.style.writingMode = 'lr-tb';
|
||
if(/(-(epub|webkit)-)?(text-combine-upright|text-combine-horizontal): all; /.test(CSSRule.cssText)) CSSRule.style.msTextCombineHorizontal = 'all';
|
||
if(IsCJK && / text-align: justify; / .test(CSSRule.cssText)) CSSRule.style.textJustify = 'inter-ideograph';
|
||
});
|
||
} else {
|
||
O.editCSSRules(Item.contentDocument, CSSRule => {
|
||
if(/(-(epub|webkit)-)?column-count: 1; /.test(CSSRule.cssText)) CSSRule.style.columnCount = CSSRule.style.webkitColumnCount = 'auto';
|
||
});
|
||
}
|
||
}
|
||
const ItemHTMLComputedStyle = getComputedStyle(Item.HTML);
|
||
const ItemBodyComputedStyle = getComputedStyle(Item.Body);
|
||
if(ItemHTMLComputedStyle[O.WritingModeProperty] != ItemBodyComputedStyle[O.WritingModeProperty]) Item.HTML.style.writingMode = ItemBodyComputedStyle[O.WritingModeProperty];
|
||
Item.WritingMode = O.getWritingMode(Item.HTML);
|
||
if(/-rl$/.test(Item.WritingMode)) Item.HTML.classList.add('bibi-vertical-text');
|
||
else if(/-lr$/.test(Item.WritingMode)) Item.HTML.classList.add('bibi-horizontal-text');
|
||
/*
|
||
if(/-rl$/.test(Item.WritingMode)) if(ItemBodyComputedStyle.marginLeft != ItemBodyComputedStyle.marginRight) Item.Body.style.marginLeft = ItemBodyComputedStyle.marginRight;
|
||
else if(/-lr$/.test(Item.WritingMode)) if(ItemBodyComputedStyle.marginRight != ItemBodyComputedStyle.marginLeft) Item.Body.style.marginRight = ItemBodyComputedStyle.marginLeft;
|
||
else if(ItemBodyComputedStyle.marginBottom != ItemBodyComputedStyle.marginTop) Item.Body.style.marginBottom = ItemBodyComputedStyle.marginTop;
|
||
//*/
|
||
[
|
||
[Item.Box, ItemHTMLComputedStyle, Item.HTML],
|
||
[Item, ItemBodyComputedStyle, Item.Body]
|
||
].forEach(Par => {
|
||
[
|
||
'backgroundColor',
|
||
'backgroundImage',
|
||
'backgroundRepeat',
|
||
'backgroundPosition',
|
||
'backgroundSize'
|
||
].forEach(Pro => Par[0].style[Pro] = Par[1][Pro]);
|
||
Par[2].style.background = 'transparent';
|
||
});
|
||
sML.forEach(Item.Body.querySelectorAll('svg, img'))(Img => {
|
||
Img.BibiDefaultStyle = {
|
||
width: (Img.style.width ? Img.style.width : ''),
|
||
height: (Img.style.height ? Img.style.height : ''),
|
||
maxWidth: (Img.style.maxWidth ? Img.style.maxWidth : ''),
|
||
maxHeight: (Img.style.maxHeight ? Img.style.maxHeight : '')
|
||
};
|
||
});
|
||
});
|
||
|
||
|
||
|
||
|
||
//==============================================================================================================================================
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
//-- Reader
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
|
||
export const R = { // Bibi.Reader
|
||
Spreads: [], Items: [], Pages: [],
|
||
NonLinearItems: [],
|
||
IntersectingPages: [], Current: {}
|
||
};
|
||
|
||
|
||
R.createSpine = (SpreadsDocumentFragment) => {
|
||
R.Main = O.Body.insertBefore(sML.create('main', { id: 'bibi-main' }), O.Body.firstElementChild);
|
||
R.Main.Book = R.Main.appendChild(sML.create('div', { id: 'bibi-main-book' }));
|
||
R.Main.Book.appendChild(SpreadsDocumentFragment);
|
||
//R.Sub = O.Body.insertBefore(sML.create('div', { id: 'bibi-sub' }), R.Main.nextSibling);
|
||
};
|
||
|
||
|
||
R.resetBibiHeight = () => {
|
||
const WIH = window.innerHeight;
|
||
if(O.TouchOS) O.HTML.style.height = O.Body.style.height = WIH + 'px'; // for In-App Browsers
|
||
return WIH;
|
||
};
|
||
|
||
|
||
R.resetStage = () => {
|
||
const WIH = R.resetBibiHeight();
|
||
R.Stage = {};
|
||
R.Columned = false;
|
||
R.Main.style.padding = R.Main.style.width = R.Main.style.height = '';
|
||
R.Main.Book.style.padding = R.Main.Book.style.width = R.Main.Book.style.height = '';
|
||
const BookBreadthIsolationStartEnd = (S['use-slider'] && S.RVM == 'paged' && O.Scrollbars[C.A_SIZE_B] ? O.Scrollbars[C.A_SIZE_B] : 0) + S['spread-margin'] * 2;
|
||
sML.style(R.Main.Book, {
|
||
[C.A_SIZE_b]: (BookBreadthIsolationStartEnd > 0 ? 'calc(100% - ' + BookBreadthIsolationStartEnd + 'px)' : ''),
|
||
[C.A_SIZE_l]: ''
|
||
});
|
||
R.Stage.Width = O.Body.clientWidth;
|
||
R.Stage.Height = WIH;
|
||
R.Stage[C.A_SIZE_B] -= (S['use-slider'] || S.RVM != 'paged' ? O.Scrollbars[C.A_SIZE_B] : 0) + S['spread-margin'] * 2;
|
||
window.scrollTo(0, 0);
|
||
if(!S['use-full-height']) R.Stage.Height -= I.Menu.Height;
|
||
if(S['spread-margin'] > 0) R.Main.Book.style['padding' + C.L_BASE_S] = R.Main.Book.style['padding' + C.L_BASE_E] = S['spread-margin'] + 'px';
|
||
//R.Main.style['background'] = S['book-background'] ? S['book-background'] : '';
|
||
};
|
||
|
||
|
||
R.layOutSpreadAndItsItems = (Spread, Opt = {}) => {
|
||
//Spread.style.width = Spread.style.height = '';
|
||
return (Spread.Items.length == 1 ? R.layOutItem(Spread.Items[0])
|
||
: !Opt.Reverse ? R.layOutItem(Spread.Items[0]).then(() => R.layOutItem(Spread.Items[1]))
|
||
: R.layOutItem(Spread.Items[1]).then(() => R.layOutItem(Spread.Items[0]))
|
||
).then(() => R.layOutSpread(Spread, Opt));
|
||
};
|
||
|
||
|
||
R.layOutSpread = (Spread, Opt = {}) => new Promise(resolve => {
|
||
if(Opt.Makeover) {
|
||
Spread.PreviousSpreadBoxLength = Spread.Box['offset' + C.L_SIZE_L];
|
||
Spread.OldPages = Spread.Pages.concat(); // copy
|
||
}
|
||
Spread.Pages = [];
|
||
Spread.Items.forEach(Item => Item.Pages.forEach(Page => Page.IndexInSpread = Spread.Pages.push(Page) - 1));
|
||
const SpreadSize = { Width: 0, Height: 0 }, SpreadBox = Spread.Box;
|
||
if(Spread.Items.length == 1) {
|
||
const Item = Spread.Items[0];
|
||
Spread.Spreaded = Item.Spreaded ? true : false;
|
||
SpreadSize.Width = (Spread.Spreaded && Item['rendition:layout'] == 'pre-paginated' && Item['rendition:page-spread']) ? (B.ICBViewport ? Item.Box.offsetHeight * B.ICBViewport.Width * 2 / B.ICBViewport.Height : R.Stage.Width) : Item.Box.offsetWidth;
|
||
SpreadSize.Height = Item.Box.offsetHeight;
|
||
} else {
|
||
const ItemA = Spread.Items[0], ItemB = Spread.Items[1];
|
||
Spread.Spreaded = (ItemA.Spreaded || ItemB.Spreaded) ? true : false;
|
||
if(ItemA['rendition:layout'] == 'pre-paginated' && ItemB['rendition:layout'] == 'pre-paginated') {
|
||
// Paired Pre-Paginated Items
|
||
if(Spread.Spreaded || S.RVM != 'vertical') {
|
||
// Spreaded
|
||
SpreadSize.Width = ItemA.Box.offsetWidth + ItemB.Box.offsetWidth;
|
||
SpreadSize.Height = Math.max(ItemA.Box.offsetHeight, ItemB.Box.offsetHeight);
|
||
} else {
|
||
// Not Spreaded (Vertical)
|
||
SpreadSize.Width = Math.max(ItemA.Box.offsetWidth, ItemB.Box.offsetWidth);
|
||
SpreadSize.Height = ItemA.Box.offsetHeight + ItemB.Box.offsetHeight;
|
||
}
|
||
} else {
|
||
// Paired Items Including Reflowable // currently not appearable.
|
||
if(S.SLA == 'horizontal') { // if(R.Stage.Width > ItemA.Box.offsetWidth + ItemB.Box.offsetWidth) {
|
||
// horizontal layout
|
||
SpreadSize.Width = ItemA.Box.offsetWidth + ItemB.Box.offsetWidth;
|
||
SpreadSize.Height = Math.max(ItemA.Box.offsetHeight, ItemB.Box.offsetHeight);
|
||
if(Bibi.Dev) {
|
||
O.log(`Paired Items incl/Reflowable (Horizontal)`, '<g:>');
|
||
O.log(`[0] w${ ItemA.Box.offsetWidth }/h${ ItemA.Box.offsetHeight } %O`, ItemA);
|
||
O.log(`[1] w${ ItemB.Box.offsetWidth }/h${ ItemB.Box.offsetHeight } %O`, ItemB);
|
||
O.log(`-=> w${ SpreadSize.Width }/h${ SpreadSize.Height } %O`, Spread, '</g>');
|
||
}
|
||
} else {
|
||
// vertical layout
|
||
SpreadSize.Width = Math.max(ItemA.Box.offsetWidth, ItemB.Box.offsetWidth);
|
||
SpreadSize.Height = ItemA.Box.offsetHeight + ItemB.Box.offsetHeight;
|
||
if(Bibi.Dev) {
|
||
O.log(`Paired Items incl/Reflowable (Vertical)`, '<g:>');
|
||
O.log(`[0] w${ ItemA.Box.offsetWidth }/h${ ItemA.Box.offsetHeight } %O`, ItemA);
|
||
O.log(`[1] w${ ItemB.Box.offsetWidth }/h${ ItemB.Box.offsetHeight } %O`, ItemB);
|
||
O.log(`-=> w${ SpreadSize.Width }/h${ SpreadSize.Height } %O`, Spread, '</g>');
|
||
}
|
||
}
|
||
}
|
||
}
|
||
const MinSpaceOfEdge = (S.RVM != 'paged' && (Spread.Index == 0 || Spread.Index == R.Spreads.length - 1)) ? Math.floor((R.Stage[C.L_SIZE_L] - SpreadSize[C.L_SIZE_L]) / 2) : 0;
|
||
if(MinSpaceOfEdge > 0) {
|
||
if(Spread.Index == 0 ) Spread.style['padding' + C.L_BASE_B] = MinSpaceOfEdge + 'px', SpreadSize[C.L_SIZE_L] += MinSpaceOfEdge;
|
||
if(Spread.Index == R.Spreads.length - 1) Spread.style['padding' + C.L_BASE_A] = MinSpaceOfEdge + 'px', SpreadSize[C.L_SIZE_L] += MinSpaceOfEdge;
|
||
} else {
|
||
Spread.style.padding = '';
|
||
}
|
||
if(O.Scrollbars.Height && S.SLA == 'vertical' && S.ARA != 'vertical') {
|
||
SpreadBox.style.minHeight = S.RVM == 'paged' ? 'calc(100vh - ' + O.Scrollbars.Height + 'px)' : '';
|
||
SpreadBox.style.marginBottom = Spread.Index == R.Spreads.length - 1 ? O.Scrollbars.Height + 'px' : '';
|
||
} else {
|
||
SpreadBox.style.minHeight = SpreadBox.style.marginBottom = ''
|
||
}
|
||
SpreadBox.classList.toggle('spreaded', Spread.Spreaded);
|
||
SpreadBox.style[C.L_SIZE_b] = '', Spread.style[C.L_SIZE_b] = Math.ceil(SpreadSize[C.L_SIZE_B]) + 'px';
|
||
SpreadBox.style[C.L_SIZE_l] = Spread.style[C.L_SIZE_l] = Math.ceil(SpreadSize[C.L_SIZE_L]) + 'px';
|
||
//sML.style(Spread, { 'border-radius': S['spread-border-radius'], 'box-shadow': S['spread-box-shadow'] });
|
||
if(Opt.Makeover) {
|
||
if(!Spread.PrePaginated) R.replacePages(Spread.OldPages, Spread.Pages);
|
||
const ChangedSpreadBoxLength = Spread.Box['offset' + C.L_SIZE_L] - Spread.PreviousSpreadBoxLength;
|
||
if(ChangedSpreadBoxLength != 0) {
|
||
const Correct = (C.L_AXIS_L == 'X' && O.getElementCoord(Spread, R.Main).X + Spread.offsetWidth <= R.Main.scrollLeft + R.Main.offsetWidth);
|
||
R.Main.Book.style[C.L_SIZE_l] = (parseFloat(getComputedStyle(R.Main.Book)[C.L_SIZE_l]) + ChangedSpreadBoxLength) + 'px';
|
||
if(Correct) {
|
||
R.Main.scrollLeft += ChangedSpreadBoxLength;
|
||
I.Slider.resetUISize();
|
||
I.Slider.progress();
|
||
}
|
||
}
|
||
delete Spread.OldPages, delete Spread.PreviousSpreadBoxLength;
|
||
}
|
||
resolve(Spread);
|
||
});
|
||
|
||
|
||
R.layOutItem = (Item) => new Promise(resolve => {
|
||
// Item.stamp('Reset...');
|
||
Item.Scale = 1;
|
||
//Item.Box.style.width = Item.Box.style.height = Item.style.width = Item.style.height = '';
|
||
(Item['rendition:layout'] != 'pre-paginated') ? R.renderReflowableItem(Item) : R.renderPrePaginatedItem(Item);
|
||
// Item.stamp('Reset.');
|
||
resolve(Item);
|
||
});
|
||
|
||
|
||
R.renderReflowableItem = (Item) => {
|
||
const ItemPaddingSE = S['item-padding-' + C.L_BASE_s] + S['item-padding-' + C.L_BASE_e];
|
||
const ItemPaddingBA = S['item-padding-' + C.L_BASE_b] + S['item-padding-' + C.L_BASE_a];
|
||
const PageCB = R.Stage[C.L_SIZE_B] - ItemPaddingSE; // Page "C"ontent "B"readth
|
||
let PageCL = R.Stage[C.L_SIZE_L] - ItemPaddingBA; // Page "C"ontent "L"ength
|
||
const PageGap = ItemPaddingBA;
|
||
['b','a','s','e'].forEach(base => { const trbl = C['L_BASE_' + base]; Item.style['padding-' + trbl] = S['item-padding-' + trbl] + 'px'; });
|
||
sML.style(Item.HTML, { 'width': '', 'height': '' });
|
||
if(Item.WithGutters) {
|
||
Item.HTML.classList.remove('bibi-with-gutters');
|
||
if(Item.Neck.parentNode) Item.Neck.parentNode.removeChild(Item.Neck);
|
||
Item.Neck.innerHTML = '';
|
||
delete Item.Neck;
|
||
}
|
||
const ReverseItemPaginationDirectionIfNecessary = (sML.UA.Trident || sML.UA.EdgeHTML) ? false : true;
|
||
if(Item.Columned) {
|
||
sML.style(Item.HTML, { 'column-fill': '', 'column-width': '', 'column-gap': '', 'column-rule': '' });
|
||
Item.HTML.classList.remove('bibi-columned');
|
||
if(ReverseItemPaginationDirectionIfNecessary && Item.ReversedColumned) {
|
||
Item.HTML.style.direction = '';
|
||
sML.forEach(Item.contentDocument.querySelectorAll('body > *'))(Ele => Ele.style.direction = '');
|
||
}
|
||
}
|
||
Item.WithGutters = false;
|
||
Item.Columned = false, Item.ColumnBreadth = 0, Item.ColumnLength = 0;
|
||
Item.ReversedColumned = false;
|
||
Item.Half = false;
|
||
Item.Spreaded = (
|
||
S.SLA == 'horizontal' && (S['pagination-method'] == 'x' || /-tb$/.test(Item.WritingMode))
|
||
&&
|
||
(Item['rendition:spread'] == 'both' || R.Orientation == Item['rendition:spread'] || R.Orientation == 'landscape')
|
||
);
|
||
if(Item.Spreaded) {
|
||
const HalfL = Math.floor((PageCL - PageGap) / 2);
|
||
if(HalfL >= Math.floor(PageCB * S['orientation-border-ratio'] / 2)) PageCL = HalfL;
|
||
else Item.Spreaded = false;
|
||
}
|
||
sML.style(Item, {
|
||
[C.L_SIZE_b]: PageCB + 'px',
|
||
[C.L_SIZE_l]: PageCL + 'px'
|
||
});
|
||
const WordWrappingStyleSheetIndex = sML.appendCSSRule(Item.contentDocument, '*', 'word-wrap: break-word;'); ////
|
||
sML.forEach(Item.Body.querySelectorAll('img, svg'))(Img => {
|
||
// Fit Image Size
|
||
if(!Img.BibiDefaultStyle) return;
|
||
['width', 'height', 'maxWidth', 'maxHeight'].forEach(Pro => Img.style[Pro] = Img.BibiDefaultStyle[Pro]);
|
||
if(S.RVM == 'horizontal' && /-(rl|lr)$/.test(Item.WritingMode) || S.RVM == 'vertical' && /-tb$/.test(Item.WritingMode)) return;
|
||
const NaturalB = parseFloat(getComputedStyle(Img)[C.L_SIZE_b]), MaxB = Math.floor(Math.min(parseFloat(getComputedStyle(Item.Body)[C.L_SIZE_b]), PageCB));
|
||
const NaturalL = parseFloat(getComputedStyle(Img)[C.L_SIZE_l]), MaxL = Math.floor(Math.min(parseFloat(getComputedStyle(Item.Body)[C.L_SIZE_l]), PageCL));
|
||
if(NaturalB > MaxB || NaturalL > MaxL) sML.style(Img, {
|
||
[C.L_SIZE_b]: Math.floor(parseFloat(getComputedStyle(Img)[C.L_SIZE_b]) * Math.min(MaxB / NaturalB, MaxL / NaturalL)) + 'px',
|
||
[C.L_SIZE_l]: 'auto',
|
||
maxWidth: '100vw',
|
||
maxHeight: '100vh'
|
||
});
|
||
});
|
||
let PaginateWith = '';
|
||
if(!Item.Outsourcing) {
|
||
if(S['pagination-method'] == 'x') {
|
||
if(S.RVM == 'paged' && Item.HTML['offset'+ C.L_SIZE_L] > PageCL) PaginateWith = 'S'; // VM:Paged WM:Vertical LA:Horizontal
|
||
else if( Item.HTML['offset'+ C.L_SIZE_B] > PageCB) PaginateWith = 'C'; // VM:Paged/Horizontal WM:Horizontal LA:Horizontal // VM: Vertical WM:Vertical LA:Vertical
|
||
} else if(S.RVM == 'paged' || Item.HTML['offset'+ C.L_SIZE_B] > PageCB) PaginateWith = 'C'; // VM:Paged/Horizontal WM:Horizontal LA:Horizontal // VM:Paged/Vertical WM:Vertical LA:Vertical
|
||
}
|
||
switch(PaginateWith) {
|
||
case 'S':
|
||
Item.HTML.classList.add('bibi-columned');
|
||
sML.style(Item.HTML, {
|
||
[C.L_SIZE_b]: 'auto',
|
||
[C.L_SIZE_l]: PageCL + 'px',
|
||
'column-fill': 'auto',
|
||
'column-width': PageCB + 'px',
|
||
'column-gap': 0,
|
||
'column-rule': ''
|
||
});
|
||
const HowManyPages = Math.ceil((sML.UA.Trident ? Item.Body.clientHeight : Item.HTML.scrollHeight) / PageCB);
|
||
sML.style(Item.HTML, { 'width': '', 'height': '', 'column-fill': '', 'column-width': '', 'column-gap': '', 'column-rule': '' });
|
||
Item.HTML.classList.remove('bibi-columned');
|
||
Item.HTML.classList.add('bibi-with-gutters');
|
||
const ItemLength = (PageCL + PageGap) * HowManyPages - PageGap;
|
||
Item.HTML.style[C.L_SIZE_L] = ItemLength + 'px';
|
||
const Points = [];//[0, 0];
|
||
for(let i = 1; i < HowManyPages; i++) {
|
||
const Start = 0, End = PageCB, After = (PageCL + PageGap) * i, Before = After - PageGap;
|
||
Points.push(Start), Points.push(Before);
|
||
Points.push( End), Points.push(Before);
|
||
Points.push( End), Points.push(After );
|
||
Points.push(Start), Points.push(After );
|
||
}
|
||
if(/^tb-/.test(Item.WritingMode)) Points.reverse();
|
||
const Polygon = [];
|
||
for(let Pt = '', l = Points.length, i = 0; i < l; i++) {
|
||
const Px = Points[i] + (Points[i] ? 'px' : '');
|
||
if(i % 2 == 0) Pt = Px;
|
||
else Polygon.push(Pt + ' ' + Px);
|
||
}
|
||
const Neck = Bibi.createElement('bibi-neck'), Throat = Neck.appendChild(Bibi.createElement('bibi-throat')), ShadowOrThroat = Throat.attachShadow ? Throat.attachShadow({ mode: 'open' }) : Throat.createShadowRoot ? Throat.createShadowRoot() : Throat;
|
||
ShadowOrThroat.appendChild(document.createElement('style')).textContent = (ShadowOrThroat != Throat ? ':host' : 'bibi-throat') + ` { ${C.L_SIZE_b}: ${PageCB}px; ${C.L_SIZE_l}: ${ItemLength}px; shape-outside: polygon(${ Polygon.join(', ') }); }`;
|
||
Item.Neck = Item.Head.appendChild(Neck);
|
||
Item.WithGutters = true;
|
||
break;
|
||
case 'C':
|
||
Item.HTML.classList.add('bibi-columned');
|
||
sML.style(Item.HTML, {
|
||
[C.L_SIZE_b]: PageCB + 'px',
|
||
//[C.L_SIZE_l]: PageCL + 'px',
|
||
'column-fill': 'auto',
|
||
'column-width': PageCL + 'px',
|
||
'column-gap': PageGap + 'px',
|
||
'column-rule': ''
|
||
});
|
||
R.Columned = true;
|
||
Item.Columned = true, Item.ColumnBreadth = PageCB, Item.ColumnLength = PageCL;
|
||
if(ReverseItemPaginationDirectionIfNecessary) {
|
||
let ToBeReversedColumnAxis = false;
|
||
switch(Item.WritingMode) {
|
||
case 'lr-tb': case 'tb-lr': if(S['page-progression-direction'] != 'ltr') ToBeReversedColumnAxis = true; break;
|
||
case 'rl-tb': case 'tb-rl': if(S['page-progression-direction'] != 'rtl') ToBeReversedColumnAxis = true; break;
|
||
}
|
||
if(ToBeReversedColumnAxis) {
|
||
Item.ReversedColumned = true;
|
||
sML.forEach(Item.contentDocument.querySelectorAll('body > *'))(Ele => Ele.style.direction = getComputedStyle(Ele).direction);
|
||
Item.HTML.style.direction = S['page-progression-direction'];
|
||
//if(sML.UA.Chromium) Item.HTML.style.transform = 'translateX(' + (Item.HTML['scroll'+ C.L_SIZE_L] - Item.HTML['offset'+ C.L_SIZE_L]) * (S['page-progression-direction'] == 'rtl' ? 1 : -1) + 'px)';
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
sML.deleteCSSRule(Item.contentDocument, WordWrappingStyleSheetIndex); ////
|
||
let ItemL = sML.UA.Trident ? Item.Body['client' + C.L_SIZE_L] : Item.HTML['scroll' + C.L_SIZE_L];
|
||
const HowManyPages = Math.ceil((ItemL + PageGap) / (PageCL + PageGap));
|
||
ItemL = (PageCL + PageGap) * HowManyPages - PageGap;
|
||
Item.style[C.L_SIZE_l] = Item.HTML.style[C.L_SIZE_l] = ItemL + 'px';
|
||
if(sML.UA.Trident) Item.HTML.style[C.L_SIZE_l] = '100%';
|
||
let ItemBoxB = PageCB + ItemPaddingSE;
|
||
let ItemBoxL = ItemL + ItemPaddingBA;// + ((S.RVM == 'paged' && Item.Spreaded && HowManyPages % 2) ? (PageGap + PageCL) : 0);
|
||
Item.Box.style[C.L_SIZE_b] = ItemBoxB + 'px';
|
||
Item.Box.style[C.L_SIZE_l] = ItemBoxL + 'px';
|
||
Item.Pages.forEach(Page => {
|
||
I.PageObserver.unobservePageIntersection(Page);
|
||
Item.Box.removeChild(Page);
|
||
});
|
||
Item.Pages = [];
|
||
for(let i = 0; i < HowManyPages; i++) {
|
||
const Page = Item.Box.appendChild(sML.create('span', { className: 'page' }));
|
||
//Page.style[C.L_SIZE_l] = (PageCL + ItemPaddingBA) + 'px';//R.Stage[C.L_SIZE_L] + 'px';
|
||
Page.Item = Item, Page.Spread = Item.Spread;
|
||
Page.IndexInItem = Item.Pages.length;
|
||
Item.Pages.push(Page);
|
||
I.PageObserver.observePageIntersection(Page);
|
||
}
|
||
/*
|
||
if(Item.Index == 2) { //setTimeout(() => {
|
||
console.log(`R.Items[${ Item.Index }]:`, Item);
|
||
O.logSets(`R.Items[${ Item.Index }]`, '.', ['HTML', 'Body'], '.', ['offset', 'client', 'scroll'], ['Width', 'Height', 'Left', 'Top']); //}, 100);
|
||
}
|
||
//*/
|
||
return Item;
|
||
};
|
||
|
||
|
||
R.renderPrePaginatedItem = (Item) => {
|
||
sML.style(Item, { width: '', height: '', transform: '' });
|
||
let StageB = R.Stage[C.L_SIZE_B];
|
||
let StageL = R.Stage[C.L_SIZE_L];
|
||
Item.Spreaded = (
|
||
(S.RVM == 'paged' || !S['full-breadth-layout-in-scroll'])
|
||
&&
|
||
(Item['rendition:spread'] == 'both' || R.Orientation == Item['rendition:spread'] || R.Orientation == 'landscape')
|
||
);
|
||
if(!Item.Viewport) Item.Viewport = R.getItemViewport(Item);
|
||
//if( Item.Viewport && !B.ICBViewport) B.ICBViewport = Item.Viewport;
|
||
let ItemLoVp = null; // ItemLayoutViewport
|
||
if(Item.Spreaded) {
|
||
ItemLoVp = R.getItemLayoutViewport(Item);
|
||
if(Item.SpreadPair) {
|
||
const PairItem = Item.SpreadPair;
|
||
PairItem.Spreaded = true;
|
||
if(!PairItem.Viewport) PairItem.Viewport = R.getItemViewport(PairItem);
|
||
const PairItemLoVp = R.getItemLayoutViewport(PairItem);
|
||
let LoBaseItem = null, LoBaseItemLoVp = null; // LayoutBaseItem, LayoutBaseItemLayoutViewport
|
||
let LoPairItem = null, LoPairItemLoVp = null; // LayoutPairItem, LayoutPairItemLayoutViewport
|
||
if(PairItem.Index > Item.Index) LoBaseItem = Item, LoBaseItemLoVp = ItemLoVp, LoPairItem = PairItem, LoPairItemLoVp = PairItemLoVp;
|
||
else LoBaseItem = PairItem, LoBaseItemLoVp = PairItemLoVp, LoPairItem = Item, LoPairItemLoVp = ItemLoVp;
|
||
LoPairItem.Scale = LoBaseItemLoVp.Height / LoPairItemLoVp.Height;
|
||
const SpreadViewPort = {
|
||
Width: LoBaseItemLoVp.Width + LoPairItemLoVp.Width * LoPairItem.Scale,
|
||
Height: LoBaseItemLoVp.Height
|
||
};
|
||
LoBaseItem.Scale = Math.min(
|
||
StageB / SpreadViewPort[C.L_SIZE_B],
|
||
StageL / SpreadViewPort[C.L_SIZE_L]
|
||
);
|
||
LoPairItem.Scale *= LoBaseItem.Scale;
|
||
} else {
|
||
const SpreadViewPort = {
|
||
Width: ItemLoVp.Width * (/^(left|right)$/.test(Item['rendition:page-spread']) ? 2 : 1),
|
||
Height: ItemLoVp.Height
|
||
};
|
||
Item.Scale = Math.min(
|
||
StageB / SpreadViewPort[C.L_SIZE_B],
|
||
StageL / SpreadViewPort[C.L_SIZE_L]
|
||
);
|
||
}
|
||
} else {
|
||
ItemLoVp = R.getItemLayoutViewport(Item);
|
||
if(S.RVM == 'paged' || !S['full-breadth-layout-in-scroll']) {
|
||
Item.Scale = Math.min(
|
||
StageB / ItemLoVp[C.L_SIZE_B],
|
||
StageL / ItemLoVp[C.L_SIZE_L]
|
||
);
|
||
} else {
|
||
Item.Scale = StageB / ItemLoVp[C.L_SIZE_B];
|
||
}
|
||
}
|
||
let PageL = Math.floor(ItemLoVp[C.L_SIZE_L] * Item.Scale);
|
||
let PageB = Math.floor(ItemLoVp[C.L_SIZE_B] * (PageL / ItemLoVp[C.L_SIZE_L]));
|
||
Item.Box.style[C.L_SIZE_l] = PageL + 'px';
|
||
Item.Box.style[C.L_SIZE_b] = PageB + 'px';
|
||
sML.style(Item, {
|
||
width: ItemLoVp.Width + 'px',
|
||
height: ItemLoVp.Height + 'px',
|
||
transform: 'scale(' + Item.Scale + ')'
|
||
});
|
||
return Item;
|
||
};
|
||
|
||
|
||
R.getItemViewport = (Item) => Item.IsPlaceholder ? null : (() => {
|
||
const ViewportMeta = Item.Head.querySelector('meta[name="viewport"]');
|
||
if(ViewportMeta) return O.getViewportByMetaContent( ViewportMeta.getAttribute('content'));
|
||
if(Item.OnlySingleSVG) return O.getViewportByViewBox(Item.Body.firstElementChild.getAttribute('viewBox')); // It's also for Item of SVGs-in-Spine, or Fixed-Layout Item including SVG without Viewport.
|
||
if(Item.OnlySingleIMG) return O.getViewportByImage( Item.Body.firstElementChild ); // It's also for Item of Bitmaps-in-Spine.
|
||
return null ;
|
||
})();
|
||
|
||
|
||
R.getItemLayoutViewport = (Item) => Item.Viewport ? Item.Viewport : B.ICBViewport ? B.ICBViewport : {
|
||
Width: R.Stage.Height * S['orientation-border-ratio'] / (/*Item.Spreaded &&*/ /^(left|right)$/.test(Item['rendition:page-spread']) ? 2 : 1),
|
||
Height: R.Stage.Height
|
||
};
|
||
|
||
|
||
R.organizePages = () => R.Pages = R.Spreads.reduce((NewPages, Spread) => Spread.Pages.reduce((NewPages, Page) => { Page.Index = NewPages.push(Page) - 1; return NewPages; }, NewPages), []);
|
||
|
||
|
||
R.replacePages = (OldPages, NewPages) => {
|
||
const StartIndex = OldPages[0].Index, OldLength = OldPages.length, NewLength = NewPages.length;
|
||
for(let l = NewPages.length, i = 0; i < l; i++) NewPages[i].Index = StartIndex + i;
|
||
if(NewLength != OldLength) {
|
||
const Dif = NewLength - OldLength;
|
||
let i = OldPages[OldLength - 1].Index + 1;
|
||
while(R.Pages[i]) R.Pages[i].Index += Dif, i++;
|
||
}
|
||
R.Pages.splice(StartIndex, OldLength, ...NewPages);
|
||
return R.Pages;
|
||
};
|
||
|
||
|
||
R.layOutStage = () => {
|
||
//E.dispatch('bibi:is-going-to:lay-out-stage');
|
||
let MainContentLayoutLength = 0;
|
||
R.Spreads.forEach(Spread => MainContentLayoutLength += Spread.Box['offset' + C.L_SIZE_L]);
|
||
if(S['book-rendition-layout'] == 'pre-paginated' || S['reader-view-mode'] != 'paged') MainContentLayoutLength += S['spread-gap'] * (R.Spreads.length - 1);
|
||
R.Main.Book.style[C.L_SIZE_l] = MainContentLayoutLength + 'px';
|
||
//E.dispatch('bibi:laid-out-stage');
|
||
};
|
||
|
||
|
||
R.layOutBook = (Opt) => new Promise((resolve, reject) => {
|
||
// Opt: {
|
||
// Destination: BibiDestination,
|
||
// Reset: Boolean, (default: false)
|
||
// DoNotCloseUtilities: Boolean, (default: false)
|
||
// Setting: BibiSetting,
|
||
// before: Function
|
||
// }
|
||
if(R.LayingOut) return reject();
|
||
I.ScrollObserver.History = [];
|
||
R.LayingOut = true;
|
||
O.log(`Laying out...`, '<g:>');
|
||
if(Opt) O.log(`Option: %O`, Opt); else Opt = {};
|
||
if(!Opt.DoNotCloseUtilities) E.dispatch('bibi:closes-utilities');
|
||
E.dispatch('bibi:is-going-to:lay-out', Opt);
|
||
O.Busy = true;
|
||
O.HTML.classList.add('busy');
|
||
O.HTML.classList.add('laying-out');
|
||
if(!Opt.NoNotification) I.note(`Laying out...`);
|
||
if(!Opt.Destination) Opt.Destination = { IIPP: I.PageObserver.getIIPP() };
|
||
if(Opt.Setting) S.update(Opt.Setting);
|
||
const Layout = {}; ['reader-view-mode', 'spread-layout-direction', 'apparent-reading-direction'].forEach(Pro => Layout[Pro] = S[Pro]);
|
||
O.log(`Layout: %O`, Layout);
|
||
if(typeof Opt.before == 'function') Opt.before();
|
||
if(!Opt.Reset) {
|
||
resolve();
|
||
} else {
|
||
R.resetStage();
|
||
const Promises = [];
|
||
R.Spreads.forEach(Spread => Promises.push(R.layOutSpreadAndItsItems(Spread)));
|
||
Promise.all(Promises).then(() => {
|
||
R.organizePages();
|
||
R.layOutStage();
|
||
resolve();
|
||
});
|
||
}
|
||
}).then(() => {
|
||
return R.focusOn({ Destination: Opt.Destination, Duration: 0 });
|
||
}).then(() => {
|
||
O.Busy = false;
|
||
O.HTML.classList.remove('busy');
|
||
O.HTML.classList.remove('laying-out');
|
||
if(!Opt.NoNotification) I.note('');
|
||
R.LayingOut = false;
|
||
E.dispatch('bibi:laid-out');
|
||
O.log(`Laid out.`, '</g>');
|
||
});
|
||
|
||
|
||
R.updateOrientation = () => {
|
||
const PreviousOrientation = R.Orientation;
|
||
if(typeof window.orientation != 'undefined') {
|
||
R.Orientation = (window.orientation == 0 || window.orientation == 180) ? 'portrait' : 'landscape';
|
||
} else {
|
||
const W = window.innerWidth - (S.ARA == 'vertical' ? O.Scrollbars.Width : 0);
|
||
const H = window.innerHeight - (S.ARA == 'horizontal' ? O.Scrollbars.Height : 0);
|
||
R.Orientation = (W / H) < S['orientation-border-ratio'] ? 'portrait' : 'landscape';
|
||
}
|
||
if(R.Orientation != PreviousOrientation) {
|
||
if(PreviousOrientation) E.dispatch('bibi:changes-orientation', R.Orientation);
|
||
O.HTML.classList.remove('orientation-' + PreviousOrientation);
|
||
O.HTML.classList.add('orientation-' + R.Orientation);
|
||
if(PreviousOrientation) E.dispatch('bibi:changed-orientation', R.Orientation);
|
||
}
|
||
};
|
||
|
||
|
||
R.changeView = (Par) => {
|
||
if(
|
||
S['fix-reader-view-mode'] ||
|
||
!Par || typeof Par.Mode != 'string' || !/^(paged|horizontal|vertical)$/.test(Par.Mode) ||
|
||
S.RVM == Par.Mode && !Par.Force
|
||
) return false;
|
||
if(L.Opened) {
|
||
E.dispatch('bibi:changes-view');
|
||
O.Busy = true;
|
||
O.HTML.classList.add('busy');
|
||
O.HTML.classList.add('changing-view');
|
||
const Delay = [
|
||
O.TouchOS ? 99 : 3,
|
||
O.TouchOS ? 99 : 33
|
||
];
|
||
setTimeout(() => {
|
||
E.dispatch('bibi:closes-utilities');
|
||
}, Delay[0]);
|
||
setTimeout(() => {
|
||
R.layOutBook({
|
||
Reset: true,
|
||
NoNotification: Par.NoNotification,
|
||
Setting: {
|
||
'reader-view-mode': Par.Mode
|
||
}
|
||
}).then(() => {
|
||
O.HTML.classList.remove('changing-view');
|
||
O.HTML.classList.remove('busy');
|
||
O.Busy = false;
|
||
setTimeout(() => E.dispatch('bibi:changed-view', Par.Mode), 0);
|
||
});
|
||
}, Delay[0] + Delay[1]);
|
||
} else {
|
||
S.update({
|
||
'reader-view-mode': Par.Mode
|
||
});
|
||
L.play();
|
||
}
|
||
if(S['keep-settings'] && O.Biscuits) O.Biscuits.memorize('Book', { RVM: Par.Mode });
|
||
};
|
||
|
||
|
||
Object.defineProperties(R, { // To ensure backward compatibility.
|
||
Current: { get: () => I.PageObserver.Current },
|
||
updateCurrent: { get: () => I.PageObserver.updateCurrent }
|
||
});
|
||
|
||
|
||
R.focusOn = (Par) => new Promise((resolve, reject) => {
|
||
if(R.Moving) return reject();
|
||
if(typeof Par == 'number' || /^\d+$/.test(Par)) Par = { Destination: Par * 1 };
|
||
if(!Par) return reject();
|
||
const _ = Par.Destination = R.hatchDestination(Par.Destination);
|
||
if(!_) return reject();
|
||
E.dispatch('bibi:is-going-to:focus-on', Par);
|
||
R.Moving = true;
|
||
Par.FocusPoint = 0;
|
||
if(S['book-rendition-layout'] == 'reflowable') {
|
||
if(typeof _.Point == 'number') {
|
||
Par.FocusPoint = _.Point;
|
||
} else {
|
||
Par.FocusPoint = O.getElementCoord(_.Page)[C.L_AXIS_L];
|
||
if(_.Side == 'after') Par.FocusPoint += (_.Page['offset' + C.L_SIZE_L] - R.Stage[C.L_SIZE_L]) * C.L_AXIS_D;
|
||
if(S.SLD == 'rtl') Par.FocusPoint += _.Page.offsetWidth;
|
||
}
|
||
if(S.SLD == 'rtl') Par.FocusPoint -= R.Stage.Width;
|
||
} else {
|
||
if(R.Stage[C.L_SIZE_L] >= _.Page.Spread['offset' + C.L_SIZE_L]) {
|
||
Par.FocusPoint = O.getElementCoord(_.Page.Spread)[C.L_AXIS_L];
|
||
Par.FocusPoint -= Math.floor((R.Stage[C.L_SIZE_L] - _.Page.Spread['offset' + C.L_SIZE_L]) / 2);
|
||
} else {
|
||
Par.FocusPoint = O.getElementCoord(_.Page)[C.L_AXIS_L];
|
||
if(R.Stage[C.L_SIZE_L] > _.Page['offset' + C.L_SIZE_L]) Par.FocusPoint -= Math.floor((R.Stage[C.L_SIZE_L] - _.Page['offset' + C.L_SIZE_L]) / 2);
|
||
else if(_.Side == 'after') Par.FocusPoint += (_.Page['offset' + C.L_SIZE_L] - R.Stage[C.L_SIZE_L]) * C.L_AXIS_D;
|
||
}
|
||
}
|
||
if(typeof _.TextNodeIndex == 'number') R.selectTextLocation(_); // Colorize Destination with Selection
|
||
const ScrollTarget = { Frame: R.Main, X: 0, Y: 0 };
|
||
ScrollTarget[C.L_AXIS_L] = Par.FocusPoint; if(!S['use-full-height'] && S.RVM == 'vertical') ScrollTarget.Y -= I.Menu.Height;
|
||
return sML.scrollTo(ScrollTarget, {
|
||
ForceScroll: true,
|
||
Duration: typeof Par.Duration == 'number' ? Par.Duration : (S.SLA == S.ARA && S.RVM != 'paged') ? 222 : 0,
|
||
Easing: (Pos) => (Pos === 1) ? 1 : Math.pow(2, -10 * Pos) * -1 + 1
|
||
}).then(() => {
|
||
resolve(_);
|
||
E.dispatch('bibi:focused-on', Par);
|
||
}).catch(reject).then(() => {
|
||
R.Moving = false;
|
||
});
|
||
}).catch(() => Promise.resolve());
|
||
|
||
R.hatchDestination = (_) => {
|
||
if(!_) return null;
|
||
if(
|
||
S.BRL == 'reflowable'
|
||
&&
|
||
(S.SLA == 'vertical' && /-tb$/.test(B.WritingMode) || S.SLA == 'horizontal' && /^tb-/.test(B.WritingMode))
|
||
&&
|
||
((_.P & /\./.test(_.P)) || _.Element || _.ElementSelector)
|
||
) {
|
||
_.Point = R.hatchPoint(_);
|
||
return _;
|
||
}
|
||
delete _.Point;
|
||
if(_.Page) {
|
||
if(R.Pages[_.Page.Index] != _.Page) delete _.Page; // Pages of the Item have been replaced.
|
||
else return _;
|
||
}
|
||
if(typeof _ == 'number' || (typeof _ == 'string' && /^\d+$/.test(_))) {
|
||
return { Page: R.Items[_].Pages[0] };
|
||
} else if(typeof _ == 'string') {
|
||
if(_ == 'head' || _ == 'foot') _ = { Edge: _ };
|
||
else if(X['EPUBCFI']) _ = X['EPUBCFI'].getDestination(_);
|
||
} else if(typeof _.IndexInItem == 'number') {
|
||
if(R.Pages[_.Index] == _) return { Page: _ }; // Page (If Pages of the Item have not been replaced)
|
||
} else if(typeof _.Index == 'number') {
|
||
return { Page: _.Pages[0] }; // Item or Spread
|
||
} else if(_.tagName) {
|
||
_ = { Element: _ };
|
||
}
|
||
_.Page = R.hatchPage(_);
|
||
return _;
|
||
};
|
||
|
||
R.hatchPage = (_) => {
|
||
if(typeof _.P == 'string' && _.P) Object.assign(_, R.getPDestination(_.P));
|
||
if(_.Page) return _.Page;
|
||
if(typeof _.PageIndex == 'number') return R.Pages[_.PageIndex];
|
||
if(typeof _.SIPP == 'number' && ((typeof _.PageIndexInSpread != 'number' && typeof _.PageProgressInSpread != 'number') || (typeof _.SpreadIndex != 'number' && !_.Spread))) _.SpreadIndex = Math.floor(_.SIPP), _.PageProgressInSpread = String(_.SIPP).replace(/^\d*\./, '0.') * 1;
|
||
if(typeof _.IIPP == 'number' && ((typeof _.PageIndexInItem != 'number' && typeof _.PageProgressInItem != 'number') || (typeof _.ItemIndex != 'number' && !_.Item ))) _.ItemIndex = Math.floor(_.IIPP), _.PageProgressInItem = String(_.IIPP).replace(/^\d*\./, '0.') * 1;
|
||
if(_.Edge == 'head') return R.Pages[0];
|
||
if(_.Edge == 'foot') return R.Pages[R.Pages.length - 1];
|
||
try {
|
||
if(typeof _.ElementSelector == 'string') {
|
||
if(!_.Item) _.Item = R.hatchItem(_);
|
||
if( _.Item) _.Element = _.Item.contentDocument.querySelector(_.ElementSelector);
|
||
delete _.ElementSelector;
|
||
}
|
||
if(_.Element) {
|
||
const NPOE = R.hatchNearestPageOfElement(_.Element);
|
||
if(NPOE) return NPOE;
|
||
}
|
||
if(typeof _.PageIndexInItem == 'number') return R.hatchItem(_).Pages[_.PageIndexInItem];
|
||
if(typeof _.PageIndexInSpread == 'number') return R.hatchSpread(_).Pages[_.PageIndexInSpread];
|
||
if(typeof _.PageProgressInItem == 'number') return (Item => Item.Pages[sML.limitMax(Math.floor( Item.Pages.length * _.PageProgressInItem ), Item.Pages.length - 1)])(R.hatchItem( _));
|
||
if(typeof _.PageProgressInSpread == 'number') return (Spread => Spread.Pages[sML.limitMax(Math.floor(Spread.Pages.length * _.PageProgressInSpread), Spread.Pages.length - 1)])(R.hatchSpread(_));
|
||
return (R.hatchItem(_) || R.hatchSpread(_)).Pages[0];
|
||
} catch(Err) {}
|
||
return null;
|
||
};
|
||
|
||
R.hatchItem = (_) => {
|
||
if(_.Item) return _.Item;
|
||
if(typeof _.ItemIndex == 'number') return R.Items[_.ItemIndex];
|
||
if(typeof _.ItemIndexInSpine == 'number') return B.Package.Spine[_.ItemIndexInSpine];
|
||
if(typeof _.ItemIndexInSpread == 'number') try { return R.hatchSpread(_).Items[_.ItemIndexInSpread]; } catch(_) { return null; }
|
||
//if(_.Element && _.Element.ownerDocument.body.Item && _.Element.ownerDocument.body.Item.Pages) return _.Element.ownerDocument.body.Item;
|
||
return null;
|
||
};
|
||
|
||
R.hatchSpread = (_) => {
|
||
if(_.Spread) return _.Spread;
|
||
if(typeof _.SpreadIndex == 'number') return R.Spreads[_.SpreadIndex];
|
||
return null;
|
||
};
|
||
|
||
R.hatchNearestPageOfElement = (Ele) => {
|
||
if(!Ele || !Ele.tagName) return null;
|
||
const Item = Ele.ownerDocument.body.Item;
|
||
if(!Item || !Item.Pages) return null;
|
||
let NearestPage, ElementCoordInItem;
|
||
if(Item.Columned) {
|
||
ElementCoordInItem = O.getElementCoord(Ele)[C.L_AXIS_B];
|
||
if(S.PPD == 'rtl' && S.SLA == 'vertical') ElementCoordInItem = /* !!!! Use offsetWidth of **Body** >>>> */ Item.Body.offsetWidth - ElementCoordInItem - Ele.offsetWidth;
|
||
NearestPage = Item.Pages[ElementCoordInItem <= 0 ? 0 : Math.floor(ElementCoordInItem / Item.ColumnBreadth)];
|
||
} else {
|
||
ElementCoordInItem = O.getElementCoord(Ele)[C.L_AXIS_L];
|
||
if(S.PPD == 'rtl' && S.SLA == 'horizontal') ElementCoordInItem = /* !!!! Use offsetWidth of **HTML** >>>> */ Item.HTML.offsetWidth - ElementCoordInItem - Ele.offsetWidth;
|
||
NearestPage = Item.Pages[Math.floor(ElementCoordInItem / R.Stage[C.L_SIZE_L])];
|
||
/*
|
||
NearestPage = Item.Pages[0];
|
||
for(let l = Item.Pages.length, i = 0; i < l; i++) {
|
||
ElementCoordInItem -= Item.Pages[i]['offset' + C.L_SIZE_L];
|
||
if(ElementCoordInItem <= 0) {
|
||
NearestPage = Item.Pages[i];
|
||
break;
|
||
}
|
||
}
|
||
//*/
|
||
}
|
||
return NearestPage;
|
||
};
|
||
|
||
R.hatchPoint = (_) => {
|
||
try {
|
||
if(typeof _.P == 'string' && _.P) Object.assign(_, R.getPDestination(_.P));
|
||
if(typeof _.ElementSelector == 'string') { _.Element = R.hatchItem(_).contentDocument.querySelector(_.ElementSelector); delete _.ElementSelector; }
|
||
if(_.Element) return R.hatchPointOfElement(_.Element);
|
||
} catch(_) {}
|
||
return null;
|
||
};
|
||
|
||
R.hatchPointOfElement = (Ele) => {
|
||
if(!Ele || !Ele.tagName) return null;
|
||
const Item = Ele.ownerDocument.body.Item;
|
||
if(!Item || !Item.Pages) return null;
|
||
let ElementCoordInItem;
|
||
if(Item.Columned) {
|
||
ElementCoordInItem = O.getElementCoord(Ele)[C.L_AXIS_B];
|
||
if(S.PPD == 'rtl' && S.SLA == 'vertical') ElementCoordInItem = Item.Body.offsetWidth - ElementCoordInItem - Ele.offsetWidth; // Use offsetWidth of **Body**
|
||
} else {
|
||
ElementCoordInItem = O.getElementCoord(Ele)[C.L_AXIS_L];
|
||
if(S.PPD == 'rtl' && S.SLA == 'horizontal') ElementCoordInItem += Ele.offsetWidth; // Use offsetWidth of **Body**
|
||
}
|
||
return O.getElementCoord(Item)[C.L_AXIS_L] + S['item-padding-' + C.L_OOBL_l] + ElementCoordInItem;
|
||
};
|
||
|
||
|
||
R.getPDestination = (PString) => {
|
||
PString = Bibi.verifySettingValue('string', 'p', PString);
|
||
if(!PString) return null;
|
||
if(/^[a-z]+$/.test(PString)) return (PString == 'head' || PString == 'foot') ? { Edge: PString } : null;
|
||
const Steps = PString.split(/-[a-z]+$/.test(PString) ? '-' : '.');
|
||
const ItemIndex = parseInt(Steps.shift()) - 1;
|
||
if(ItemIndex < 0) return { Edge: 'head' };
|
||
if(ItemIndex > R.Items.length - 1) return { Edge: 'foot' };
|
||
const Item = R.Items[ItemIndex];
|
||
if(Steps.length) {
|
||
if(/^[a-z]+$/.test(Steps[0])) {
|
||
if(Steps[0] == 'end') return { Page: Item.Pages[Item.Pages.length - 1] };
|
||
} else {
|
||
let ElementSelector = `body`;
|
||
Steps.forEach(Step => ElementSelector += `>*:nth-child(` + Step + `)`);
|
||
const Element = Item.contentDocument.querySelector(ElementSelector);
|
||
if(Element) return { Element: Element };
|
||
}
|
||
}
|
||
return { Page: Item.Pages[0] };
|
||
};
|
||
|
||
|
||
R.selectTextLocation = (_) => {
|
||
if(typeof _.TextNodeIndex != 'number' || !_.Element) return false;
|
||
const _Node = _.Element.childNodes[_.TextNodeIndex];
|
||
if(!_Node || !_Node.textContent) return;
|
||
const Sides = { Start: { Node: _Node, Index: 0 }, End: { Node: _Node, Index: _Node.textContent.length } };
|
||
if(_.TermStep) {
|
||
if(_.TermStep.Preceding || _.TermStep.Following) {
|
||
Sides.Start.Index = _.TermStep.Index, Sides.End.Index = _.TermStep.Index;
|
||
if(_.TermStep.Preceding) Sides.Start.Index -= _.TermStep.Preceding.length;
|
||
if(_.TermStep.Following) Sides.End.Index += _.TermStep.Following.length;
|
||
if(Sides.Start.Index < 0 || _Node.textContent.length < Sides.End.Index) return;
|
||
if(_Node.textContent.substr(Sides.Start.Index, Sides.End.Index - Sides.Start.Index) != _.TermStep.Preceding + _.TermStep.Following) return;
|
||
} else if(_.TermStep.Side && _.TermStep.Side == 'a') {
|
||
Sides.Start.Node = _Node.parentNode.firstChild; while(Sides.Start.Node.childNodes.length) Sides.Start.Node = Sides.Start.Node.firstChild;
|
||
Sides.End.Index = _.TermStep.Index - 1;
|
||
} else {
|
||
Sides.Start.Index = _.TermStep.Index;
|
||
Sides.End.Node = _Node.parentNode.lastChild; while(Sides.End.Node.childNodes.length) Sides.End.Node = Sides.End.Node.lastChild;
|
||
Sides.End.Index = Sides.End.Node.textContent.length;
|
||
}
|
||
}
|
||
return sML.Ranges.selectRange(sML.Ranges.getRange(Sides));
|
||
};
|
||
|
||
|
||
R.moveBy = (Par) => new Promise((resolve, reject) => {
|
||
if(R.Moving || !L.Opened) return reject();
|
||
if(!Par) return reject();
|
||
switch(typeof Par) {
|
||
case 'string':
|
||
case 'number': Par = { Distance: Par };
|
||
case 'object': Par.Distance *= 1;
|
||
}
|
||
if(typeof Par.Distance != 'number' || !isFinite(Par.Distance) || !Par.Distance) return reject();
|
||
//Par.Distance = Par.Distance < 0 ? -1 : 1;
|
||
E.dispatch('bibi:is-going-to:move-by', Par);
|
||
const Current = (Par.Distance > 0 ? I.PageObserver.Current.List.slice(-1) : I.PageObserver.Current.List)[0], CurrentPage = Current.Page, CurrentItem = CurrentPage.Item;
|
||
let Promised = null;
|
||
if(
|
||
true ||
|
||
R.Columned ||
|
||
S.BRL == 'pre-paginated' ||
|
||
S.BRL == 'reflowable' && S.RVM == 'paged' ||
|
||
CurrentItem['rendition:layout'] == 'pre-paginated' ||
|
||
CurrentItem.Outsourcing ||
|
||
CurrentItem.Pages.length == 1 ||
|
||
Par.Distance < 0 && CurrentPage.IndexInItem == 0 ||
|
||
Par.Distance > 0 && CurrentPage.IndexInItem == CurrentItem.Pages.length - 1
|
||
) {
|
||
let Side = Par.Distance > 0 ? 'before' : 'after';
|
||
if(Current.PageIntersectionStatus.Oversized) {
|
||
if(Par.Distance > 0) {
|
||
if(Current.PageIntersectionStatus.Entering) Par.Distance = 0, Side = 'before';
|
||
else if(Current.PageIntersectionStatus.Headed ) Par.Distance = 0, Side = 'after';
|
||
} else {
|
||
if(Current.PageIntersectionStatus.Footed ) Par.Distance = 0, Side = 'before';
|
||
else if(Current.PageIntersectionStatus.Passing ) Par.Distance = 0, Side = 'before';
|
||
}
|
||
} else {
|
||
if(Par.Distance > 0) {
|
||
if(Current.PageIntersectionStatus.Entering) Par.Distance = 0, Side = 'before';
|
||
} else {
|
||
if(Current.PageIntersectionStatus.Passing ) Par.Distance = 0, Side = 'before';
|
||
}
|
||
}
|
||
let DestinationPageIndex = CurrentPage.Index + Par.Distance;
|
||
if(DestinationPageIndex < 0) DestinationPageIndex = 0, Side = 'before';
|
||
else if(DestinationPageIndex > R.Pages.length - 1) DestinationPageIndex = R.Pages.length - 1, Side = 'after';
|
||
let DestinationPage = R.Pages[DestinationPageIndex];
|
||
if(S.BRL == 'pre-paginated' && DestinationPage.Item.SpreadPair) {
|
||
if(S.SLA == 'horizontal' && R.Stage[C.L_SIZE_L] > DestinationPage.Spread['offset' + C.L_SIZE_L]) {
|
||
if(Par.Distance < 0 && DestinationPage.IndexInSpread == 0) DestinationPage = DestinationPage.Spread.Pages[1];
|
||
if(Par.Distance > 0 && DestinationPage.IndexInSpread == 1) DestinationPage = DestinationPage.Spread.Pages[0];
|
||
}
|
||
}
|
||
Par.Destination = { Page: DestinationPage, Side: Side };
|
||
Promised = R.focusOn(Par).then(() => Par.Destination);
|
||
} else {
|
||
Promised = R.scrollBy(Par);
|
||
}
|
||
Promised.then(Returned => {
|
||
resolve(Returned);
|
||
E.dispatch('bibi:moved-by', Par);
|
||
});
|
||
}).catch(() => Promise.resolve());
|
||
|
||
|
||
R.scrollBy = (Par) => new Promise((resolve, reject) => {
|
||
if(!Par) return reject();
|
||
if(typeof Par == 'number') Par = { Distance: Par };
|
||
if(!Par.Distance || typeof Par.Distance != 'number') return reject();
|
||
E.dispatch('bibi:is-going-to:scroll-by', Par);
|
||
R.Moving = true;
|
||
const ScrollTarget = { Frame: R.Main, X: 0, Y: 0 };
|
||
switch(S.SLD) {
|
||
case 'ttb': ScrollTarget.Y = R.Main.scrollTop + R.Stage.Height * Par.Distance ; break;
|
||
case 'ltr': ScrollTarget.X = R.Main.scrollLeft + R.Stage.Width * Par.Distance ; break;
|
||
case 'rtl': ScrollTarget.X = R.Main.scrollLeft + R.Stage.Width * Par.Distance * -1; break;
|
||
}
|
||
sML.scrollTo(ScrollTarget, {
|
||
Duration: typeof Par.Duration == 'number' ? Par.Duration : (S.RVM != 'paged' && S.SLA == S.ARA) ? 100 : 0,
|
||
ForceScroll: Par.Cancelable ? false : true,
|
||
ease: typeof Par.ease == 'function' ? Par.ease : null
|
||
}).catch(() => true).then(() => {
|
||
R.Moving = false;
|
||
resolve();
|
||
});
|
||
}).then(() =>
|
||
E.dispatch('bibi:scrolled-by', Par)
|
||
);
|
||
|
||
|
||
|
||
|
||
//==============================================================================================================================================
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
//-- User Interfaces
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
|
||
export const I = {}; // Bibi.UserInterfaces
|
||
|
||
|
||
I.initialize = () => {
|
||
I.Utilities.create();
|
||
I.TouchObserver.create();
|
||
I.Notifier.create();
|
||
I.Veil.create();
|
||
E.bind('bibi:readied', () => {
|
||
I.ScrollObserver.create();
|
||
I.ResizeObserver.create();
|
||
I.PageObserver.create();
|
||
I.Catcher.create();
|
||
I.Menu.create();
|
||
I.Panel.create();
|
||
I.Help.create();
|
||
I.PoweredBy.create();
|
||
I.FontSizeChanger.create();
|
||
I.Loupe.create();
|
||
});
|
||
E.bind('bibi:initialized-book', () => {
|
||
I.BookmarkManager.create();
|
||
});
|
||
E.bind('bibi:prepared', () => {
|
||
I.FlickObserver.create();
|
||
I.WheelObserver.create();
|
||
I.PinchObserver.create();
|
||
I.KeyObserver.create();
|
||
I.EdgeObserver.create();
|
||
I.Nombre.create();
|
||
I.Slider.create();
|
||
I.Flipper.create();
|
||
I.Arrows.create();
|
||
I.AxisSwitcher.create();
|
||
I.Spinner.create();
|
||
});
|
||
};
|
||
|
||
|
||
I.Utilities = { create: () => {
|
||
const Utilities = I.Utilities = I.setToggleAction({
|
||
openGracefuly: () => R.Moving || R.Breaking || Utilities.UIState == 'active' ? false : Utilities.open(),
|
||
closeGracefuly: () => R.Moving || R.Breaking || Utilities.UIState == 'default' ? false : Utilities.close(),
|
||
toggleGracefuly: () => R.Moving || R.Breaking ? false : Utilities.toggle()
|
||
}, {
|
||
onopened: () => E.dispatch('bibi:opens-utilities'),
|
||
onclosed: () => E.dispatch('bibi:closes-utilities')
|
||
});
|
||
E.add('bibi:commands:open-utilities', () => I.Utilities.open());
|
||
E.add('bibi:commands:close-utilities', () => I.Utilities.close());
|
||
E.add('bibi:commands:toggle-utilities', () => I.Utilities.toggleGracefuly());
|
||
}};
|
||
|
||
|
||
I.ScrollObserver = { create: () => {
|
||
const ScrollObserver = I.ScrollObserver = {
|
||
History: [],
|
||
Count: 0,
|
||
onScroll: (Eve) => { if(R.LayingOut || !L.Opened) return;
|
||
if(!ScrollObserver.Scrolling) {
|
||
ScrollObserver.Scrolling = true;
|
||
O.HTML.classList.add('scrolling');
|
||
}
|
||
E.dispatch('bibi:is-scrolling');
|
||
ScrollObserver.History.unshift(Math.ceil(R.Main['scroll' + C.L_OOBL_L])); // Android Chrome returns scrollLeft/Top value of an element with slightly less float than actual.
|
||
if(ScrollObserver.History.length > 2) ScrollObserver.History.pop();
|
||
if(++ScrollObserver.Count == 8) {
|
||
ScrollObserver.Count = 0;
|
||
E.dispatch('bibi:scrolled');
|
||
}
|
||
clearTimeout(R.Timer_onScrollEnd);
|
||
R.Timer_onScrollEnd = setTimeout(() => {
|
||
ScrollObserver.Scrolling = false;
|
||
ScrollObserver.Count = 0;
|
||
O.HTML.classList.remove('scrolling');
|
||
E.dispatch('bibi:scrolled');
|
||
}, 123);
|
||
},
|
||
observe: () => {
|
||
R.Main.addEventListener('scroll', ScrollObserver.onScroll);
|
||
},
|
||
breakCurrentScrolling: () => {
|
||
try { R.Breaking = true; sML.Scroller.Scrolling.cancel(); setTimeout(() => R.Breaking = false, 333); } catch(Err) { R.Breaking = false; }
|
||
},
|
||
forceStopScrolling: () => {
|
||
ScrollObserver.breakCurrentScrolling();
|
||
R.Main.style.overflow = 'hidden', R.Main.scrollLeft = R.Main.scrollLeft, R.Main.scrollTop = R.Main.scrollTop, R.Main.style.overflow = '';
|
||
}
|
||
}
|
||
E.bind('bibi:opened', ScrollObserver.observe);
|
||
E.dispatch('bibi:created-scroll-observer');
|
||
}};
|
||
|
||
|
||
I.PageObserver = { create: () => {
|
||
const PageObserver = I.PageObserver = {
|
||
// ---- Intersection
|
||
IntersectingPages: [],
|
||
PagesToBeObserved: [],
|
||
observePageIntersection: (Page) => !PageObserver.PagesToBeObserved.includes(Page) ? PageObserver.PagesToBeObserved.push(Page) : PageObserver.PagesToBeObserved.length,
|
||
unobservePageIntersection: (Page) => (PageObserver.PagesToBeObserved = PageObserver.PagesToBeObserved.filter(PageToBeObserved => PageToBeObserved != Page)).length,
|
||
observeIntersection: () => {
|
||
const Glasses = new IntersectionObserver(Ents => Ents.forEach(Ent => {
|
||
const Page = Ent.target;
|
||
let IntersectionChanging = false;
|
||
//const IntersectionRatio = Math.round(Ent.intersectionRatio * 10000) / 100;
|
||
if(Ent.isIntersecting) {
|
||
if(!PageObserver.IntersectingPages.includes(Page)) {
|
||
IntersectionChanging = true;
|
||
PageObserver.IntersectingPages.push(Page);
|
||
}
|
||
} else {
|
||
if( PageObserver.IntersectingPages.includes(Page)) {
|
||
IntersectionChanging = true;
|
||
PageObserver.IntersectingPages = PageObserver.IntersectingPages.filter(IntersectingPage => IntersectingPage != Page);
|
||
}
|
||
}
|
||
if(IntersectionChanging) {
|
||
if(PageObserver.IntersectingPages.length) PageObserver.IntersectingPages.sort((A, B) => A.Index - B.Index);
|
||
E.dispatch('bibi:changes-intersection', PageObserver.IntersectingPages);
|
||
clearTimeout(PageObserver.Timer_IntersectionChange);
|
||
PageObserver.Timer_IntersectionChange = setTimeout(() => {
|
||
E.dispatch('bibi:changed-intersection', PageObserver.IntersectingPages);
|
||
}, 9);
|
||
}
|
||
}), {
|
||
root: R.Main,
|
||
rootMargin: '0px',
|
||
threshold: 0
|
||
});
|
||
PageObserver.observePageIntersection = (Page) => Glasses.observe(Page);
|
||
PageObserver.unobservePageIntersection = (Page) => Glasses.unobserve(Page);
|
||
PageObserver.PagesToBeObserved.forEach(PageToBeObserved => Glasses.observe(PageToBeObserved));
|
||
delete PageObserver.PagesToBeObserved;
|
||
},
|
||
// ---- Current
|
||
Current: { List: [], Pages: [], Frame: {} },
|
||
updateCurrent: () => {
|
||
const Frame = PageObserver.getFrame();
|
||
if(Frame) {
|
||
PageObserver.Current.Frame = Frame;
|
||
const List = PageObserver.getList();
|
||
if(List) {
|
||
PageObserver.Current.List = List;
|
||
PageObserver.Current.Pages = List.map(CE => CE.Page);
|
||
PageObserver.classify();
|
||
}
|
||
}
|
||
return PageObserver.Current;
|
||
},
|
||
getFrame: () => {
|
||
const Frame = {};
|
||
Frame.Length = R.Main['offset' + C.L_SIZE_L];
|
||
Frame[C.L_OOBL_L ] = Math.ceil(R.Main['scroll' + C.L_OOBL_L]); // Android Chrome returns scrollLeft/Top value of an element with slightly less float than actual.
|
||
Frame[C.L_OOBL_L == 'Top' ? 'Bottom' : 'Right'] = Frame[C.L_OOBL_L] + Frame.Length;
|
||
//if(PageObserver.Current.List.length && Frame[C.L_BASE_B] == PageObserver.Current.Frame.Before && Frame[C.L_BASE_A] == PageObserver.Current.Frame.After) return false;
|
||
return { Before: Frame[C.L_BASE_B], After: Frame[C.L_BASE_A], Length: Frame.Length };
|
||
},
|
||
getCandidatePageList: () => {
|
||
const QSW = Math.ceil((R.Stage.Width - 1) / 4), QSH = Math.ceil((R.Stage.Height - 1) / 4), CheckRoute = [5, 6, 4, 2, 8]; // 4x4 matrix
|
||
for(let l = CheckRoute.length, i = 0; i < l; i++) { const CheckPoint = CheckRoute[i];
|
||
const Ele = document.elementFromPoint(QSW * (CheckPoint % 3 || 3), QSH * Math.ceil(CheckPoint / 3));
|
||
if(Ele) {
|
||
if(Ele.IndexInItem) return [Ele]; // Page
|
||
else if(Ele.Pages) return Ele.Pages; // Item or Spread
|
||
else if(Ele.Inside) return Ele.Inside.Pages; // ItemBox or SpreadBox
|
||
}
|
||
}
|
||
return PageObserver.IntersectingPages.length ? PageObserver.IntersectingPages : [];
|
||
},
|
||
getList: () => {
|
||
let List = [], List_SpreadContained = [];
|
||
const PageList = PageObserver.getCandidatePageList();
|
||
if(!PageList.length || typeof PageList[0].Index != 'number') return null;
|
||
const FirstIndex = sML.limitMin(PageList[ 0].Index - 2, 0);
|
||
const LastIndex = sML.limitMax(PageList[PageList.length - 1].Index + 2, R.Pages.length - 1);
|
||
for(let BiggestPageIntersectionRatio = 0, i = FirstIndex; i <= LastIndex; i++) { const Page = R.Pages[i];
|
||
const PageIntersectionStatus = PageObserver.getIntersectionStatus(Page, 'WithDetail');
|
||
if(PageIntersectionStatus.Ratio < BiggestPageIntersectionRatio) {
|
||
if(List.length) break;
|
||
} else {
|
||
const CurrentEntry = { Page: Page, PageIntersectionStatus: PageIntersectionStatus };
|
||
if(List.length) {
|
||
const Prev = List[List.length - 1];
|
||
if(Prev.Page.Item == Page.Item ) CurrentEntry.ItemIntersectionStatus = Prev.ItemIntersectionStatus;
|
||
if(Prev.Page.Spread == Page.Spread) CurrentEntry.SpreadIntersectionStatus = Prev.SpreadIntersectionStatus;
|
||
}
|
||
if( !CurrentEntry.ItemIntersectionStatus) CurrentEntry.ItemIntersectionStatus = PageObserver.getIntersectionStatus(Page.Item.Box); // Item is scaled.
|
||
if(!CurrentEntry.SpreadIntersectionStatus) CurrentEntry.SpreadIntersectionStatus = PageObserver.getIntersectionStatus(Page.Spread); // SpreadBox has margin.
|
||
if(CurrentEntry.SpreadIntersectionStatus.Ratio == 1) List_SpreadContained.push(CurrentEntry);
|
||
if(PageIntersectionStatus.Ratio > BiggestPageIntersectionRatio) List = [CurrentEntry], BiggestPageIntersectionRatio = PageIntersectionStatus.Ratio;
|
||
else List.push(CurrentEntry);
|
||
}
|
||
}
|
||
return List_SpreadContained.length ? List_SpreadContained : List.length ? List : null;
|
||
},
|
||
getIntersectionStatus: (Ele, WithDetail) => {
|
||
const Coord = sML.getCoord(Ele), _D = C.L_AXIS_D;
|
||
const LengthInside = Math.min(PageObserver.Current.Frame.After * _D, Coord[C.L_BASE_A] * _D) - Math.max(PageObserver.Current.Frame.Before * _D, Coord[C.L_BASE_B] * _D);
|
||
const Ratio = (LengthInside <= 0 || !Coord[C.L_SIZE_L] || isNaN(LengthInside)) ? 0 : LengthInside / Coord[C.L_SIZE_L];
|
||
const IntersectionStatus = { Ratio: Ratio };
|
||
if(Ratio <= 0) {} else if(WithDetail) {
|
||
if(Ratio >= 1) {
|
||
IntersectionStatus.Contained = true;
|
||
} else {
|
||
const FC_B = PageObserver.Current.Frame.Before * _D, FC_A = PageObserver.Current.Frame.After * _D;
|
||
const PC_B = Coord[C.L_BASE_B] * _D, PC_A = Coord[C.L_BASE_A] * _D;
|
||
if(FC_B < PC_B ) IntersectionStatus.Entering = true;
|
||
else if(FC_B == PC_B ) IntersectionStatus.Headed = true;
|
||
else if( PC_A == FC_A) IntersectionStatus.Footed = true;
|
||
else if( PC_A < FC_A) IntersectionStatus.Passing = true;
|
||
if(R.Main['offset' + L] < Coord[C.L_SIZE_L]) IntersectionStatus.Oversized = true;
|
||
}
|
||
}
|
||
return IntersectionStatus;
|
||
},
|
||
classify: () => {
|
||
const CurrentElements = [], PastCurrentElements = R.Main.Book.querySelectorAll('.current');
|
||
PageObserver.Current.List.forEach(CurrentEntry => {
|
||
const Page = CurrentEntry.Page, ItemBox = Page.Item.Box, SpreadBox = Page.Spread.Box;
|
||
if(!CurrentElements.includes(SpreadBox)) SpreadBox.classList.add('current'), CurrentElements.push(SpreadBox);
|
||
if(!CurrentElements.includes( ItemBox)) ItemBox.classList.add('current'), CurrentElements.push( ItemBox);
|
||
Page.classList.add('current'), CurrentElements.push(Page);
|
||
});
|
||
sML.forEach(PastCurrentElements)(PastCurrentElement => {
|
||
if(!CurrentElements.includes(PastCurrentElement)) {
|
||
PastCurrentElement.classList.remove('current');
|
||
}
|
||
});
|
||
},
|
||
observeCurrent: () => {
|
||
E.bind(['bibi:changed-intersection', 'bibi:scrolled'], PageObserver.updateCurrent);
|
||
},
|
||
// ---- PageChange
|
||
Past: { List: [{ Page: null, PageIntersectionStatus: null }] },
|
||
observePageMove: () => {
|
||
E.bind('bibi:scrolled', () => {
|
||
const CS = PageObserver.Current.List[0], CE = PageObserver.Current.List.slice(-1)[0], CSP = CS.Page, CEP = CE.Page, CSPIS = CS.PageIntersectionStatus, CEPIS = CE.PageIntersectionStatus;
|
||
const PS = PageObserver.Past.List[0], PE = PageObserver.Past.List.slice(-1)[0], PSP = PS.Page, PEP = PE.Page;
|
||
const FPI = 0, LPI = R.Pages.length - 1;
|
||
let Flipped = false, AtTheBeginning = false, AtTheEnd = false;
|
||
if(CSP != PSP || CEP != PEP) {
|
||
Flipped = true;
|
||
if(CSP.Index == FPI && (CSPIS.Contained || CSPIS.Headed)) AtTheBeginning = true;
|
||
if(CEP.Index == LPI && (CEPIS.Contained || CEPIS.Footed)) AtTheEnd = true;
|
||
} else {
|
||
const PSPIS = PS.PageIntersectionStatus, PEPIS = PE.PageIntersectionStatus
|
||
if(CSP.Index == FPI && (CSPIS.Contained || CSPIS.Headed) && !(PSPIS.Contained || PSPIS.Headed)) AtTheBeginning = true;
|
||
if(CEP.Index == LPI && (CEPIS.Contained || CEPIS.Footed) && !(PEPIS.Contained || PEPIS.Footed)) AtTheEnd = true;
|
||
}
|
||
const ReturnValue = { Past: PageObserver.Past, Current: PageObserver.Current };
|
||
if(Flipped ) E.dispatch('bibi:flipped', ReturnValue);
|
||
if(AtTheBeginning) E.dispatch('bibi:got-to-the-beginning', ReturnValue);
|
||
if(AtTheEnd ) E.dispatch('bibi:got-to-the-end', ReturnValue);
|
||
Object.assign(PageObserver.Past, PageObserver.Current);
|
||
});
|
||
},
|
||
getIIPP: (Par = {}) => {
|
||
const Page = (Par.Page && typeof Par.Page.IndexInItem == 'number') ? Par.Page : (PageObserver.Current.Pages[0] || (Par.Item && Par.Item.IndexInSpine ? Par.Item.Pages[0] : R.Pages[0]));
|
||
return Page.Item.Index + Page.IndexInItem / Page.Item.Pages.length;
|
||
},
|
||
getP: (Par = {}) => {
|
||
let Item = null, Element = null;
|
||
if(Par.Element && Par.Element.ownerDocument && Par.Element.ownerDocument.body.Item) {
|
||
Item = Par.Element.ownerDocument.body.Item;
|
||
Element = Par.Element;
|
||
} else {
|
||
if(B.Package.Metadata['rendition:layout'] == 'pre-paginated') return { P: String((PageObserver.Current.Pages[0] || R.Pages[0]).Item.Index + 1) };
|
||
if(!Par.Page || typeof Par.Page.IndexInItem != 'number') Par.Page = null;
|
||
const Page = Par.Page ? Par.Page : (
|
||
B.WritingMode.split('-')[0] == 'tb' && S.SLA == 'vertical'
|
||
|| B.WritingMode.split('-')[1] == 'tb' && S.SLA == 'horizontal'
|
||
) ? PageObserver.Current.Pages[0] : PageObserver.IntersectingPages.filter(ISP => R.Stage[C.L_SIZE_L] * PageObserver.getIntersectionStatus(ISP).Ratio > 3)[0];
|
||
if(!Page) return '';
|
||
Item = Page.Item;
|
||
const PageCoord = O.getElementCoord(Page, R.Main);
|
||
const PageArea = { Left: PageCoord.X, Right: PageCoord.X + Page.offsetWidth, Top: PageCoord.Y, Bottom: PageCoord.Y + Page.offsetHeight };
|
||
PageArea[C.L_OOBL_B] += S['item-padding-' + C.L_OOBL_b];
|
||
PageArea[C.L_OEBL_B] -= S['item-padding-' + C.L_OEBL_b];
|
||
if(Item.Columned) {
|
||
PageArea[C.L_OOBL_L] += S['item-padding-' + C.L_OOBL_l];
|
||
PageArea[C.L_OEBL_L] -= S['item-padding-' + C.L_OEBL_l];
|
||
}
|
||
const ItemCoord = O.getElementCoord(Item, R.Main);
|
||
const HTMLArea = { Left: ItemCoord.X + S['item-padding-left'], Top: ItemCoord.Y + S['item-padding-top'] }; HTMLArea.Right = HTMLArea.Left + Item.HTML.offsetWidth, HTMLArea.Bottom = HTMLArea.Top + Item.HTML.offsetHeight;
|
||
const ViewArea = { Left: R.Main.scrollLeft, Right: R.Main.scrollLeft + R.Stage.Width, Top: R.Main.scrollTop, Bottom: R.Main.scrollTop + R.Stage.Height };
|
||
const PagedHTMLArea = Par.Page ? {
|
||
Left: Math.max(PageArea.Left, HTMLArea.Left ) - HTMLArea.Left, Right: Math.min(PageArea.Right, HTMLArea.Right ) - HTMLArea.Left,
|
||
Top: Math.max(PageArea.Top, HTMLArea.Top ) - HTMLArea.Top , Bottom: Math.min(PageArea.Bottom, HTMLArea.Bottom ) - HTMLArea.Top
|
||
} : {
|
||
Left: Math.max(PageArea.Left, HTMLArea.Left, ViewArea.Left) - HTMLArea.Left, Right: Math.min(PageArea.Right, HTMLArea.Right, ViewArea.Right ) - HTMLArea.Left,
|
||
Top: Math.max(PageArea.Top, HTMLArea.Top, ViewArea.Top ) - HTMLArea.Top , Bottom: Math.min(PageArea.Bottom, HTMLArea.Bottom, ViewArea.Bottom) - HTMLArea.Top
|
||
};
|
||
PagedHTMLArea.Right--, PagedHTMLArea.Bottom--;
|
||
const AbsoluteDistance = 10;
|
||
let LPM = 1, BPM = 1, getFirstElement = (l, b) => Item.contentDocument.elementFromPoint(l, b);
|
||
if(S.ARD == 'rtl') {
|
||
LPM *= -1;
|
||
} else if(S.ARD == 'ttb') {
|
||
if(S.PPD == 'rtl') BPM *= -1;
|
||
getFirstElement = (l, b) => Item.contentDocument.elementFromPoint(b, l);
|
||
}
|
||
if(Par.TagNames) Par.TagNames = !Array.isArray(Par.TagNames) ? null : Par.TagNames.map(TN => typeof TN != 'string' ? '' : TN.replace(/\s+/, '').toLowerCase()).filter(TN => TN ? true : false);
|
||
if(!Par.TagNames || !Par.TagNames.length) delete Par.TagNames;
|
||
__: for(let i = 0, l = PagedHTMLArea[C.A_BASE_B], lp = Math.round((PagedHTMLArea[C.A_BASE_A] - PagedHTMLArea[C.A_BASE_B]) / 9); l * LPM < PagedHTMLArea[C.A_BASE_A] * LPM; l += lp) {
|
||
for(let j = 0, b = PagedHTMLArea[C.A_BASE_S], bp = Math.round((PagedHTMLArea[C.A_BASE_E] - PagedHTMLArea[C.A_BASE_S]) / 3); b * BPM < PagedHTMLArea[C.A_BASE_E] * BPM; b += bp) {
|
||
const Ele = getFirstElement(l, b);
|
||
if(Par.TagNames) {
|
||
if(Par.TagNames.includes(Ele.tagName.toLowerCase())) Element = Ele;
|
||
} else if(Ele != Item.HTML && Ele != Item.Body) {
|
||
Element = Ele;
|
||
}
|
||
if(Element) break __;
|
||
}
|
||
}
|
||
if(!Element) return (Page.IndexInItem < Item.Pages.length - 1) ? PageObserver.getP({ Page: Item.Pages[Page.IndexInItem + 1], TagNames: Par.TagNames }) : Item.Index < R.Items.length - 1 ? (Item.Index + 2) : 'foot';
|
||
}
|
||
const Steps = [];
|
||
let Ele = Element; while(Ele != Item.Body) {
|
||
let Nth = 0;
|
||
sML.forEach(Ele.parentElement.childNodes)((CN, i) => {
|
||
if(CN.nodeType != 1) return;
|
||
Nth++;
|
||
if(CN == Ele) {
|
||
Steps.unshift(Nth);
|
||
Ele = Ele.parentElement;
|
||
return 'break';
|
||
}
|
||
});
|
||
}
|
||
Steps.unshift(Item.Index + 1);
|
||
return Steps.join('.');/*
|
||
return {
|
||
P: Steps.join('.'),
|
||
ItemIndex: Item.Index,
|
||
ElementSelector: 'body'; Steps.forEach(Step => ElementSelector += `>*:nth-child(` + Step + `)`),
|
||
Item: Item,
|
||
Element: Element
|
||
};*/
|
||
},
|
||
// ---- Turning Face-up/down
|
||
MaxTurning: 4,
|
||
TurningItems_FaceUp: [],
|
||
TurningItems_FaceDown: [],
|
||
getTurningOriginItem: (Dir = 1) => {
|
||
const List = PageObserver.Current.List.length ? PageObserver.Current.List : PageObserver.IntersectingPages.length ? PageObserver.IntersectingPages : null;
|
||
try { return (Dir > 0 ? List[0] : List[List.length - 1]).Page.Item; } catch(Err) {} return null;
|
||
},
|
||
turnItems: (Opt) => {
|
||
if(R.DoNotTurn || !S['allow-placeholders']) return;
|
||
if(typeof Opt != 'object') Opt = {};
|
||
const Dir = (I.ScrollObserver.History.length > 1) && (I.ScrollObserver.History[1] * C.L_AXIS_D > I.ScrollObserver.History[0] * C.L_AXIS_D) ? -1 : 1;
|
||
const Origin = Opt.Origin || PageObserver.getTurningOriginItem(Dir); if(!Origin) return;
|
||
const MUST = [Origin], NEW_ = [], KEEP = []; // const ___X = [];
|
||
const Next = R.Items[Origin.Index + Dir]; if(Next) MUST.push(Next);
|
||
const Prev = R.Items[Origin.Index - Dir]; if(Prev) MUST.push(Prev);
|
||
MUST.forEach(Item => {
|
||
if(Item.Turned != 'Up') (PageObserver.TurningItems_FaceUp.includes(Item) ? KEEP : NEW_).push(Item);
|
||
});
|
||
let i = 1/*, x = 0*/; while(++i < 9/* && x < 2*/ && KEEP.length + NEW_.length < PageObserver.MaxTurning) {
|
||
const Item = R.Items[Origin.Index + i * Dir]; if(!Item) break;
|
||
if(MUST.includes(Item)) continue;
|
||
if(Item.Turned != 'Up') (PageObserver.TurningItems_FaceUp.includes(Item) ? KEEP : NEW_).push(Item);/* x++;*/
|
||
}
|
||
PageObserver.TurningItems_FaceUp.forEach(Item => { // use `forEach`, not `for`.
|
||
if(KEEP.includes(Item)) return;
|
||
if(KEEP.length + NEW_.length < PageObserver.MaxTurning) return KEEP.push(Item);
|
||
clearTimeout(Item.Timer_Turn);
|
||
PageObserver.turnItem(Item, { Down: true }); // ___X.push(Item);
|
||
});
|
||
NEW_.forEach((Item, i) => PageObserver.turnItem(Item, { Up: true, Delay: (MUST.includes(Item) ? 99 : 999) * i }));
|
||
/*
|
||
console.log('--------');
|
||
console.log('Origin', Origin.Index);
|
||
console.log('MUST', MUST.map(Item => Item.Index));
|
||
console.log('KEEP', KEEP.map(Item => Item.Index));
|
||
console.log('NEW_', NEW_.map(Item => Item.Index));
|
||
// console.log('___X', ___X.map(Item => Item.Index));
|
||
//*/
|
||
},
|
||
turnItem: (Item, Opt) => new Promise(resolve => {
|
||
if(R.DoNotTurn || !S['allow-placeholders']) return;
|
||
if(!Item) Item = PageObserver.getTurningOriginItem();
|
||
if(typeof Opt != 'object') Opt = { Up: true };
|
||
if(Opt.Up) {
|
||
if(PageObserver.TurningItems_FaceUp.includes(Item)) return resolve();
|
||
PageObserver.TurningItems_FaceUp.push(Item), PageObserver.TurningItems_FaceDown = PageObserver.TurningItems_FaceDown.filter(_ => _ != Item);
|
||
} else {
|
||
if(PageObserver.TurningItems_FaceDown.includes(Item)) return resolve();
|
||
PageObserver.TurningItems_FaceDown.push(Item), PageObserver.TurningItems_FaceUp = PageObserver.TurningItems_FaceUp.filter(_ => _ != Item);
|
||
if(O.RangeLoader) O.cancelExtraction(Item.Source);
|
||
}
|
||
Item.Timer_Turn = setTimeout(() => L.loadItem(Item, { IsPlaceholder: !(Opt.Up) }).then(() => {
|
||
if(Opt.Up) PageObserver.TurningItems_FaceUp = PageObserver.TurningItems_FaceUp.filter(_ => _ != Item);
|
||
else PageObserver.TurningItems_FaceDown = PageObserver.TurningItems_FaceDown.filter(_ => _ != Item);
|
||
R.layOutItem(Item).then(() => R.layOutSpread(Item.Spread, { Makeover: true, Reverse: Opt.Reverse })).then(() => resolve(Item));
|
||
}), Opt.Delay || 0);
|
||
})
|
||
}
|
||
E.bind('bibi:laid-out-for-the-first-time', LayoutOption => {
|
||
PageObserver.IntersectingPages = [R.Spreads[LayoutOption.TargetSpreadIndex].Pages[0]];
|
||
PageObserver.observeIntersection();
|
||
});
|
||
E.bind('bibi:opened', () => {
|
||
PageObserver.updateCurrent();
|
||
PageObserver.observeCurrent();
|
||
PageObserver.observePageMove();
|
||
});
|
||
E.dispatch('bibi:created-page-observer');
|
||
}};
|
||
|
||
|
||
I.ResizeObserver = { create: () => {
|
||
const ResizeObserver = I.ResizeObserver = {
|
||
Resizing: false,
|
||
TargetPageAfterResizing: null,
|
||
onResize: (Eve) => { if(R.LayingOut || !L.Opened) return;
|
||
if(!ResizeObserver.Resizing) {
|
||
ResizeObserver.Resizing = true;
|
||
//ResizeObserver.TargetPageAfterResizing = I.PageObserver.Current.List && I.PageObserver.Current.List[0] && I.PageObserver.Current.List[0].Page ? I.PageObserver.Current.List[0].Page : I.PageObserver.IntersectingPages[0];
|
||
ResizeObserver.TargetPageAfterResizing = I.PageObserver.Current.List[0] ? I.PageObserver.Current.List[0].Page : null;
|
||
////////R.Main.removeEventListener('scroll', I.ScrollObserver.onScroll);
|
||
O.Busy = true;
|
||
O.HTML.classList.add('busy');
|
||
O.HTML.classList.add('resizing');
|
||
};
|
||
clearTimeout(ResizeObserver.Timer_onResizeEnd);
|
||
ResizeObserver.Timer_onResizeEnd = setTimeout(() => {
|
||
R.updateOrientation();
|
||
const Page = ResizeObserver.TargetPageAfterResizing || (I.PageObserver.Current.List[0] ? I.PageObserver.Current.List[0].Page : null);
|
||
R.layOutBook({
|
||
Reset: true,
|
||
Destination: Page ? { ItemIndex: Page.Item.Index, PageProgressInItem: Page.IndexInItem / Page.Item.Pages.length } : null
|
||
}).then(() => {
|
||
E.dispatch('bibi:resized', Eve);
|
||
O.HTML.classList.remove('resizing');
|
||
O.HTML.classList.remove('busy');
|
||
O.Busy = false;
|
||
////////R.Main.addEventListener('scroll', I.ScrollObserver.onScroll);
|
||
//I.ScrollObserver.onScroll();
|
||
ResizeObserver.Resizing = false;
|
||
});
|
||
}, sML.UA.Trident ? 1200 : O.TouchOS ? 600 : 300);
|
||
},
|
||
observe: () => {
|
||
window.addEventListener(E['resize'], ResizeObserver.onResize);
|
||
}
|
||
};
|
||
E.bind('bibi:opened', ResizeObserver.observe);
|
||
E.dispatch('bibi:created-resize-observer');
|
||
}};
|
||
|
||
|
||
I.TouchObserver = { create: () => {
|
||
const TouchObserver = I.TouchObserver = {
|
||
observeElementHover: (Ele) => {
|
||
if(!Ele.BibiHoverObserver) {
|
||
Ele.BibiHoverObserver = {
|
||
onHover: (Eve) => E.dispatch(Ele, 'bibi:hovered', Eve),
|
||
onUnHover: (Eve) => E.dispatch(Ele, 'bibi:unhovered', Eve)
|
||
};
|
||
Ele.addEventListener(E['pointerover'], Eve => Ele.BibiHoverObserver.onHover(Eve));
|
||
Ele.addEventListener(E['pointerout'], Eve => Ele.BibiHoverObserver.onUnHover(Eve));
|
||
}
|
||
return Ele;
|
||
},
|
||
setElementHoverActions: (Ele) => {
|
||
E.add(Ele, 'bibi:hovered', Eve => { if(Ele.Hover || (Ele.isAvailable && !Ele.isAvailable(Eve))) return Ele;
|
||
Ele.Hover = true;
|
||
Ele.classList.add('hover');
|
||
if(Ele.showHelp) Ele.showHelp();
|
||
return Ele;
|
||
});
|
||
E.add(Ele, 'bibi:unhovered', Eve => { if(!Ele.Hover) return Ele;
|
||
Ele.Hover = false;
|
||
Ele.classList.remove('hover');
|
||
if(Ele.hideHelp) Ele.hideHelp();
|
||
return Ele;
|
||
});
|
||
return Ele;
|
||
},
|
||
observeElementTap: (Ele, Opt = {}) => {
|
||
if(!Ele.BibiTapObserver) { const TimeLimit = { D2U: 300, U2D: 300 };
|
||
Ele.BibiTapObserver = {
|
||
care: Opt.PreventDefault ? (Opt.StopPropagation ? _ => _.preventDefault() || _.stopPropagation() : _ => _.preventDefault()) : (Opt.StopPropagation ? _ => _.stopPropagation() : () => {}),
|
||
onPointerDown: function(Eve) {
|
||
if((typeof Eve.buttons == 'number' && Eve.buttons !== 1) || Eve.ctrlKey) return true;
|
||
this.care(Eve);
|
||
clearTimeout(this.Timer_forgetFloating);
|
||
clearTimeout(this.Timer_fireTap);
|
||
const DownTime = Date.now();
|
||
this.Touching = { Time: DownTime, Coord: O.getBibiEventCoord(Eve), Event: Eve };
|
||
if(!this.Floating) return;
|
||
if((DownTime - this.Floating.Time) < TimeLimit.U2D) {
|
||
this.Floating.PreviousTouching.Event.preventDefault();
|
||
this.Floating.Event.preventDefault();
|
||
Eve.preventDefault();
|
||
} else {
|
||
delete this.Floating;
|
||
}
|
||
},
|
||
onPointerUp: function(Eve) {
|
||
this.care(Eve);
|
||
if(!this.Touching) return;
|
||
const UpTime = Date.now();
|
||
if((UpTime - this.Touching.Time) < TimeLimit.D2U) {
|
||
const TouchEndCoord = O.getBibiEventCoord(Eve);
|
||
if(Math.abs(TouchEndCoord.X - this.Touching.Coord.X) < 3 && Math.abs(TouchEndCoord.Y - this.Touching.Coord.Y) < 3) {
|
||
let SDT = 0;
|
||
const Floating = { Time: UpTime, Event: Eve, PreviousTouching: this.Touching };
|
||
if(!this.Floating) {
|
||
SDT = 1;
|
||
this.Floating = Object.assign(Floating, { WaitingFor: 2 });
|
||
} else {
|
||
SDT = this.Floating.WaitingFor;
|
||
this.Floating = Object.assign(Floating, { WaitingFor: ++this.Floating.WaitingFor });
|
||
}
|
||
this.Timer_forgetFloating = setTimeout(() => { delete this.Floating; }, TimeLimit.U2D);
|
||
this.Timer_fireTap = setTimeout(() => { switch(SDT) {
|
||
case 1: return Ele.BibiTapObserver.onTap( Eve);
|
||
case 2: return Ele.BibiTapObserver.onDoubleTap(Eve);
|
||
case 3: return Ele.BibiTapObserver.onTripleTap(Eve);
|
||
}}, TimeLimit.U2D);
|
||
} else {
|
||
delete this.Floating;
|
||
}
|
||
}
|
||
delete this.Touching;
|
||
},
|
||
onTap: (Eve) => E.dispatch(Ele, 'bibi:tapped' , Eve),
|
||
onDoubleTap: (Eve) => E.dispatch(Ele, 'bibi:doubletapped', Eve),
|
||
onTripleTap: (Eve) => E.dispatch(Ele, 'bibi:tripletapped', Eve)
|
||
};
|
||
Ele.addEventListener(E['pointerdown'], Eve => Ele.BibiTapObserver.onPointerDown(Eve));
|
||
Ele.addEventListener(E['pointerup'], Eve => Ele.BibiTapObserver.onPointerUp(Eve));
|
||
}
|
||
return Ele;
|
||
},
|
||
setElementTapActions: (Ele) => {
|
||
const onTap = (() => { switch(Ele.Type) {
|
||
case 'toggle': return (Eve) => { if(Ele.UIState == 'disabled') return false;
|
||
I.setUIState(Ele, Ele.UIState == 'default' ? 'active' : 'default');
|
||
};
|
||
case 'radio': return (Eve) => { if(Ele.UIState == 'disabled') return false;
|
||
Ele.ButtonGroup.Buttons.forEach(Button => { if(Button != Ele) I.setUIState(Button, ''); });
|
||
I.setUIState(Ele, 'active');
|
||
};
|
||
default: return (Eve) => { if(Ele.UIState == 'disabled') return false;
|
||
I.setUIState(Ele, 'active');
|
||
clearTimeout(Ele.Timer_deactivate);
|
||
Ele.Timer_deactivate = setTimeout(() => I.setUIState(Ele, Ele.UIState == 'disabled' ? 'disabled' : ''), 200);
|
||
};
|
||
} })();
|
||
E.add(Ele, 'bibi:tapped', Eve => { if((Ele.isAvailable && !Ele.isAvailable(Eve)) || (Ele.UIState == 'disabled') || (Ele.UIState == 'active' && Ele.Type == 'radio')) return Ele;
|
||
onTap(Eve);
|
||
if(Ele.hideHelp) Ele.hideHelp();
|
||
if(Ele.note) setTimeout(Ele.note, 0);
|
||
return Ele;
|
||
});
|
||
return Ele;
|
||
},
|
||
PointerEventNames: O.TouchOS ? [['touchstart', 'mousedown'], ['touchend', 'mouseup'], ['touchmove', 'mousemove']] : document.onpointermove !== undefined ? ['pointerdown', 'pointerup', 'pointermove'] : ['mousedown', 'mouseup', 'mousemove'],
|
||
PreviousPointerCoord: { X: 0, Y: 0 },
|
||
activateHTML: (HTML) => {
|
||
TouchObserver.observeElementTap(HTML); const TOPENs = TouchObserver.PointerEventNames;
|
||
E.add(HTML, 'bibi:tapped', Eve => E.dispatch('bibi:tapped', Eve));
|
||
E.add(HTML, 'bibi:doubletapped', Eve => E.dispatch('bibi:doubletapped', Eve)); //HTML.ownerDocument.addEventListener('dblclick', Eve => { Eve.preventDefault(); Eve.stopPropagation(); return false; });
|
||
E.add(HTML, 'bibi:tripletapped', Eve => E.dispatch('bibi:tripletapped', Eve));
|
||
E.add(HTML, TOPENs[0], Eve => E.dispatch('bibi:downed-pointer', Eve), E.Cpt1Psv0);
|
||
E.add(HTML, TOPENs[1], Eve => E.dispatch('bibi:upped-pointer', Eve), E.Cpt1Psv0);
|
||
E.add(HTML, TOPENs[2], Eve => {
|
||
const CC = O.getBibiEventCoord(Eve), PC = TouchObserver.PreviousPointerCoord;
|
||
E.dispatch((PC.X != CC.X || PC.Y != CC.Y) ? 'bibi:moved-pointer' : 'bibi:stopped-pointer', Eve);
|
||
TouchObserver.PreviousPointerCoord = CC;
|
||
//Eve.preventDefault();
|
||
Eve.stopPropagation();
|
||
}, E.Cpt1Psv0);
|
||
}
|
||
}
|
||
const checkTapAvailability = (BibiEvent) => {
|
||
switch(S.RVM) {
|
||
case 'horizontal': if(BibiEvent.Coord.Y > window.innerHeight - O.Scrollbars.Height) return false; else break;
|
||
case 'vertical': if(BibiEvent.Coord.X > window.innerWidth - O.Scrollbars.Width) return false; else break;
|
||
}
|
||
if(BibiEvent.Target.ownerDocument) {
|
||
if(O.isPointableContent(BibiEvent.Target)) return false;
|
||
if(I.Slider.ownerDocument && (BibiEvent.Target == I.Slider || I.Slider.contains(BibiEvent.Target))) return false;
|
||
}
|
||
return true;
|
||
};
|
||
E.bind('bibi:readied', ( ) => TouchObserver.activateHTML( O.HTML));
|
||
E.bind('bibi:postprocessed-item', (Item) => TouchObserver.activateHTML(Item.HTML));
|
||
E.add('bibi:tapped', Eve => { if(I.isPointerStealth()) return false;
|
||
if(I.OpenedSubpanel) return I.OpenedSubpanel.close() && false;
|
||
const BibiEvent = O.getBibiEvent(Eve);
|
||
if(!checkTapAvailability(BibiEvent)) return false;
|
||
return BibiEvent.Division.X == 'center' && BibiEvent.Division.Y == 'middle' ? E.dispatch('bibi:tapped-center', Eve) : E.dispatch('bibi:tapped-not-center', Eve);
|
||
});
|
||
E.add('bibi:doubletapped', Eve => { if(I.isPointerStealth() || !L.Opened) return false;
|
||
if(I.OpenedSubpanel) return I.OpenedSubpanel.close() && false;
|
||
const BibiEvent = O.getBibiEvent(Eve);
|
||
if(!checkTapAvailability(BibiEvent)) return false;
|
||
return BibiEvent.Division.X == 'center' && BibiEvent.Division.Y == 'middle' ? E.dispatch('bibi:doubletapped-center', Eve) : E.dispatch('bibi:doubletapped-not-center', Eve);
|
||
});
|
||
E.add('bibi:tapped-center', () => I.Utilities.toggleGracefuly());
|
||
E.dispatch('bibi:created-touch-observer');
|
||
}};
|
||
|
||
|
||
I.FlickObserver = { create: () => {
|
||
const FlickObserver = I.FlickObserver = {
|
||
Moving: 0,
|
||
getDegree: (_) => (Deg => Deg < 0 ? Deg + 360 : Deg)(Math.atan2(_.Y * -1, _.X) * 180 / Math.PI),
|
||
onTouchStart: (Eve) => {
|
||
if(!L.Opened) return;
|
||
//if(S.RVM != 'paged' && O.TouchOS) return;
|
||
if(FlickObserver.LastEvent) return FlickObserver.onTouchEnd();
|
||
if(I.Loupe.Transforming) return;
|
||
FlickObserver.LastEvent = Eve;
|
||
const EventCoord = O.getBibiEventCoord(Eve);
|
||
FlickObserver.StartedAt = {
|
||
X: EventCoord.X,
|
||
Y: EventCoord.Y,
|
||
Item: Eve.target.ownerDocument.body.Item || null,
|
||
TimeStamp: Eve.timeStamp,
|
||
ScrollLeft: R.Main.scrollLeft,
|
||
ScrollTop: R.Main.scrollTop,
|
||
OriginList: I.PageObserver.updateCurrent().List
|
||
};
|
||
//Eve.preventDefault();
|
||
E.add('bibi:moved-pointer', FlickObserver.onTouchMove);
|
||
E.add('bibi:upped-pointer', FlickObserver.onTouchEnd);
|
||
},
|
||
cancel: () => {
|
||
delete FlickObserver.StartedAt;
|
||
delete FlickObserver.LastEvent;
|
||
E.remove('bibi:moved-pointer', FlickObserver.onTouchMove);
|
||
E.remove('bibi:upped-pointer', FlickObserver.onTouchEnd);
|
||
FlickObserver.Moving = 0;
|
||
},
|
||
onTouchMove: (Eve) => {
|
||
//if(Eve.touches && Eve.touches.length == 1 && O.getViewportZooming() <= 1) Eve.preventDefault();
|
||
I.ScrollObserver.breakCurrentScrolling();
|
||
if(FlickObserver.StartedAt) {
|
||
if(!FlickObserver.Moving) {
|
||
const TimeFromTouchStarted = Eve.timeStamp - FlickObserver.StartedAt.TimeStamp;
|
||
if(!O.TouchOS && (Eve.type == 'mousemove' || Eve.pointerType == 'mouse')) { if(TimeFromTouchStarted < 234) return FlickObserver.cancel(); }
|
||
else { if(TimeFromTouchStarted > 234) return FlickObserver.cancel(); }
|
||
FlickObserver.StartedAt.TimeStamp = Eve.timeStamp;
|
||
}
|
||
const EventCoord = O.getBibiEventCoord(Eve);
|
||
const Passage = { X: EventCoord.X - FlickObserver.StartedAt.X, Y: EventCoord.Y - FlickObserver.StartedAt.Y };
|
||
if(++FlickObserver.Moving <= 3) {
|
||
const Deg = FlickObserver.getDegree(Passage);
|
||
FlickObserver.StartedAt.LaunchingAxis = (315 <= Deg || Deg <= 45) || (135 <= Deg && Deg <= 225) ? 'X' :
|
||
( 45 < Deg && Deg < 135) || (225 < Deg && Deg < 315) ? 'Y' : '';
|
||
}
|
||
if(FlickObserver.StartedAt.LaunchingAxis == C.A_AXIS_B) {
|
||
// Orthogonal
|
||
} else {
|
||
// Natural
|
||
if(S.RVM != 'paged' && Eve.type == 'touchmove') return FlickObserver.cancel();
|
||
if(S['content-draggable'][S.RVM == 'paged' ? 0 : 1] && I.isScrollable()) R.Main['scroll' + C.L_OOBL_L] = FlickObserver.StartedAt['Scroll' + C.L_OOBL_L] + Passage[C.L_AXIS_L] * -1;
|
||
}
|
||
Eve.preventDefault();
|
||
if(FlickObserver.StartedAt.Item) {
|
||
FlickObserver.StartedAt.Item.HTML.classList.add('bibi-flick-hot');
|
||
FlickObserver.StartedAt.Item.contentWindow.getSelection().empty();
|
||
}
|
||
FlickObserver.LastEvent = Eve;
|
||
if(EventCoord[C.A_AXIS_L] <= 0 || EventCoord[C.A_AXIS_L] >= R.Stage[C.A_SIZE_L] || EventCoord[C.A_AXIS_B] <= 0 || EventCoord[C.A_AXIS_B] >= R.Stage[C.A_SIZE_B]) return FlickObserver.onTouchEnd(Eve, { Swipe: true });
|
||
}
|
||
},
|
||
onTouchEnd: (Eve, Opt) => {
|
||
if(!Eve) Eve = FlickObserver.LastEvent;
|
||
E.remove('bibi:moved-pointer', FlickObserver.onTouchMove);
|
||
E.remove('bibi:upped-pointer', FlickObserver.onTouchEnd);
|
||
FlickObserver.Moving = 0;
|
||
let cb = undefined, Par = {};
|
||
if(FlickObserver.StartedAt) {
|
||
const EventCoord = O.getBibiEventCoord(Eve);
|
||
const Passage = { X: EventCoord.X - FlickObserver.StartedAt.X, Y: EventCoord.Y - FlickObserver.StartedAt.Y };
|
||
if(FlickObserver.StartedAt.Item) FlickObserver.StartedAt.Item.HTML.classList.remove('bibi-flick-hot');
|
||
if(!I.Loupe.Transforming) {
|
||
if(FlickObserver.StartedAt.LaunchingAxis == C.A_AXIS_B && Math.abs(Passage[C.A_AXIS_B] / 100) >= 1) {
|
||
// Orthogonal Pan/Releace
|
||
cb = FlickObserver.getOrthogonalTouchMoveFunction();
|
||
}
|
||
if(!cb && (Math.abs(Passage.X) >= 3 || Math.abs(Passage.Y) >= 3)) {
|
||
// Moved (== not Tap)
|
||
Eve.preventDefault();
|
||
Par.Speed = Math.sqrt(Math.pow(Passage.X, 2) + Math.pow(Passage.Y, 2)) / (Eve.timeStamp - FlickObserver.StartedAt.TimeStamp);
|
||
Par.Deg = FlickObserver.getDegree(Passage);
|
||
if(O.getViewportZooming() <= 1 && (Eve.timeStamp - FlickObserver.StartedAt.TimeStamp) <= 300) {
|
||
Par.OriginList = FlickObserver.StartedAt.OriginList;
|
||
cb = (Opt && Opt.Swipe) ? FlickObserver.onSwipe : FlickObserver.onFlick;
|
||
} else if(I.isScrollable()) {
|
||
cb = FlickObserver.onPanRelease;
|
||
}
|
||
} else {
|
||
// Not Moved (== Tap)
|
||
// [[[[ Do Nothing ]]]] (to avoid conflicts with other tap events on other UIs like Arrows.)
|
||
}
|
||
}
|
||
delete FlickObserver.StartedAt;
|
||
}
|
||
delete FlickObserver.LastEvent;
|
||
return (cb ? cb(Eve, Par) : Promise.resolve());
|
||
},
|
||
onFlick: (Eve, Par) => {
|
||
if(S.RVM != 'paged' && !S['content-draggable'][S.RVM == 'paged' ? 0 : 1]) return Promise.resolve();
|
||
if(typeof Par.Deg != 'number') return Promise.resolve();
|
||
const Deg = Par.Deg;
|
||
const Dir = (330 <= Deg || Deg <= 30) ? 'left' /* to right */ :
|
||
( 60 <= Deg && Deg <= 120) ? 'bottom' /* to top */ :
|
||
(150 <= Deg && Deg <= 210) ? 'right' /* to left */ :
|
||
(240 <= Deg && Deg <= 300) ? 'top' /* to bottom */ : '';
|
||
const Dist = C.d2d(Dir, I.orthogonal('touch-moves') == 'move');
|
||
if(!Dist) {
|
||
// Orthogonal (not for "move")
|
||
return new Promise(resolve => {
|
||
FlickObserver.getOrthogonalTouchMoveFunction()();
|
||
resolve();
|
||
});
|
||
} else if(S.RVM == 'paged' || S.RVM != (/^[lr]/.test(Dir) ? 'horizontal' : /^[tb]/.test(Dir) ? 'vertical' : '')) {
|
||
// Paged || Scrolling && Orthogonal
|
||
const PageIndex = (Dist > 0 ? Par.OriginList.slice(-1)[0].Page.Index : Par.OriginList[0].Page.Index);
|
||
return R.focusOn({
|
||
Destination: { Page: R.Pages[PageIndex + Dist] ? R.Pages[PageIndex + Dist] : R.Pages[PageIndex] },
|
||
Duration: !I.isScrollable() ? 0 : S.RVM != 'paged' || S['content-draggable'][0] ? 123 : 0
|
||
});
|
||
} else {
|
||
// Scrolling && Natural
|
||
return R.scrollBy({
|
||
Distance: Dist * (Par.Speed ? sML.limitMinMax(Math.round(Par.Speed * 100) * 0.08, 0.33, 10) * 333 / (S.SLD == 'ttb' ? R.Stage.Height : R.Stage.Width) : 1),
|
||
Duration: 1234,
|
||
Cancelable: true,
|
||
ease: (_) => (Math.pow((_ - 1), 4) - 1) * -1
|
||
});
|
||
}
|
||
},
|
||
onSwipe: (Eve, Par) => FlickObserver.onFlick(Eve, Par),
|
||
onPanRelease: (Eve, Par) => {
|
||
if(S.RVM != 'paged' || !S['content-draggable'][0]) return Promise.resolve(); // Only for Paged View ====
|
||
const Deg = Par.Deg;
|
||
const Dir = (270 < Deg || Deg < 90) ? 'left' /* to right */ :
|
||
( 90 < Deg && Deg < 270) ? 'right' /* to left */ : '';
|
||
const Dist = C.d2d(Dir);
|
||
const CurrentList = I.PageObserver.updateCurrent().List;
|
||
return R.focusOn({
|
||
Destination: { Page: (Dist >= 0 ? CurrentList.slice(-1)[0].Page : CurrentList[0].Page) },
|
||
Duration: !I.isScrollable() ? 0 : S['content-draggable'][0] ? 123 : 0
|
||
});
|
||
},
|
||
getOrthogonalTouchMoveFunction: () => { switch(I.orthogonal('touch-moves')) {
|
||
case 'switch': if(I.AxisSwitcher) return I.AxisSwitcher.switchAxis; break;
|
||
case 'utilities': return I.Utilities.toggleGracefuly; break;
|
||
} },
|
||
getCNPf: (Ele) => Ele.ownerDocument == document ? '' : 'bibi-',
|
||
activateElement: (Ele) => { if(!Ele) return false;
|
||
Ele.addEventListener(E['pointerdown'], FlickObserver.onTouchStart, E.Cpt1Psv0);
|
||
const CNPf = FlickObserver.getCNPf(Ele);
|
||
/**/ Ele.ownerDocument.documentElement.classList.add(CNPf + 'flick-active');
|
||
if(I.isScrollable()) Ele.ownerDocument.documentElement.classList.add(CNPf + 'flick-scrollable');
|
||
},
|
||
deactivateElement: (Ele) => { if(!Ele) return false;
|
||
Ele.removeEventListener(E['pointerdown'], FlickObserver.onTouchStart, E.Cpt1Psv0);
|
||
const CNPf = FlickObserver.getCNPf(Ele);
|
||
Ele.ownerDocument.documentElement.classList.remove(CNPf + 'flick-active');
|
||
Ele.ownerDocument.documentElement.classList.remove(CNPf + 'flick-scrollable');
|
||
}
|
||
};
|
||
FlickObserver.activateElement(R.Main);
|
||
E.add('bibi:loaded-item', Item => FlickObserver.activateElement(Item.HTML));
|
||
E.dispatch('bibi:created-flick-observer');
|
||
}};
|
||
|
||
|
||
I.WheelObserver = { create: () => {
|
||
const WheelObserver = I.WheelObserver = {
|
||
TotalDelta: 0,
|
||
Turned: false,
|
||
Wheels: [],
|
||
reset: () => {
|
||
WheelObserver.TotalDelta = 0;
|
||
WheelObserver.Progress = 0;
|
||
WheelObserver.Turned = false;
|
||
WheelObserver.Wheels = [];
|
||
},
|
||
reserveResetWith: (fn) => {
|
||
clearTimeout(WheelObserver.Timer_resetWheeling);
|
||
try { fn(); } catch(Err) {}
|
||
WheelObserver.Timer_resetWheeling = setTimeout(WheelObserver.reset, 234);
|
||
},
|
||
careTurned: () => {
|
||
WheelObserver.reserveResetWith(() => WheelObserver.Turned = true);
|
||
},
|
||
heat: () => {
|
||
clearTimeout(WheelObserver.Timer_coolDown);
|
||
WheelObserver.Hot = true;
|
||
WheelObserver.Timer_coolDown = setTimeout(() => WheelObserver.Hot = false, 234);
|
||
},
|
||
onWheel: (Eve) => {
|
||
//if(WheelObserver.Turned) return WheelObserver.careTurned();
|
||
const WA /* WheelAxis */ = Math.abs(Eve.deltaX) > Math.abs(Eve.deltaY) ? 'X' : 'Y';
|
||
const CW /* CurrentWheel */ = {}, Ws = WheelObserver.Wheels, Wl = Ws.length;
|
||
//if(Wl && Ws[Wl - 1].Axis != WA) WheelObserver.Wheels = [];
|
||
CW.Axis = WA;
|
||
CW.Direction = WA == 'X' ? (Eve.deltaX < 0 ? 'left' : 'right') : (Eve.deltaY < 0 ? 'top' : 'bottom');
|
||
CW.Distance = C.d2d(CW.Direction, 'Allow Orthogonal Direction');
|
||
CW.Delta = Math.abs(Eve['delta' + WA]);
|
||
if(!Ws[Wl - 1]) CW.Accel = 1, CW.Wheeled = 'start';
|
||
else if(CW.Axis != Ws[Wl - 1].Axis ) return WheelObserver.careTurned(); ////////
|
||
else if(CW.Distance != Ws[Wl - 1].Distance) CW.Accel = 1, CW.Wheeled = (Wl >= 3 && Ws[Wl - 2].Distance != CW.Distance && Ws[Wl - 3].Distance != CW.Distance) ? 'reverse' : '';
|
||
else if(CW.Delta > Ws[Wl - 1].Delta ) CW.Accel = 1, CW.Wheeled = (Wl >= 3 && Ws[Wl - 1].Accel == -1 && Ws[Wl - 2].Accel == -1 && Ws[Wl - 3].Accel == -1 ) ? 'serial' : '';
|
||
else if(CW.Delta < Ws[Wl - 1].Delta ) CW.Accel = -1, CW.Wheeled = '';
|
||
else CW.Accel = Ws[Wl - 1].Accel, CW.Wheeled = '';
|
||
WheelObserver.reserveResetWith(() => {
|
||
Ws.push(CW); if(Wl > 3) Ws.shift();
|
||
WheelObserver.Progress = (WheelObserver.TotalDelta += Eve['delta' + WA]) / 3 / 100;
|
||
});
|
||
const ToDo = WA != C.A_AXIS_L ? I.orthogonal('wheelings') : S.RVM == 'paged' ? 'move' : WheelObserver.OverlaidUIs.filter(OUI => OUI.contains(Eve.target)).length ? 'simulate' : '';
|
||
if(!ToDo) return;
|
||
//Eve.preventDefault(); // Must not prevent.
|
||
//Eve.stopPropagation(); // No need to stop.
|
||
if(WheelObserver.Hot) return;
|
||
switch(ToDo) {
|
||
case 'simulate': return WheelObserver.scrollNatural(Eve, WA);
|
||
case 'across' : return WheelObserver.scrollAcross(Eve, WA);
|
||
case 'move': return WheelObserver.move(CW);
|
||
case 'utilities': return WheelObserver.toggleUtilities(CW);
|
||
case 'switch': return WheelObserver.switchAxis(CW);
|
||
}
|
||
},
|
||
scrollNatural: (Eve, Axis) => { switch(Axis) {
|
||
case 'X': R.Main.scrollLeft += Eve.deltaX; break;
|
||
case 'Y': R.Main.scrollTop += Eve.deltaY; break;
|
||
} },
|
||
scrollAcross: (Eve, Axis) => { switch(Axis) {
|
||
case 'X': R.Main.scrollTop += Eve.deltaX; break;
|
||
case 'Y': R.Main.scrollLeft += Eve.deltaY * (S.ARD == 'rtl' ? -1 : 1); break;
|
||
} },
|
||
move: (CW) => {
|
||
if(!CW.Wheeled) return;
|
||
WheelObserver.heat();
|
||
R.moveBy({ Distance: CW.Distance, Duration: I.isScrollable() && S['content-draggable'][0] ? 123 : 0 });
|
||
},
|
||
toggleUtilities: (CW) => {
|
||
if(!CW.Wheeled) return;
|
||
WheelObserver.heat();
|
||
I.Utilities.toggleGracefuly();
|
||
},
|
||
switchAxis: () => {
|
||
if(!I.AxisSwitcher || Math.abs(WheelObserver.Progress) < 1) return;
|
||
WheelObserver.heat();
|
||
I.AxisSwitcher.switchAxis();
|
||
},
|
||
OverlaidUIs: []
|
||
};
|
||
document.addEventListener('wheel', Eve => E.dispatch('bibi:is-wheeling', Eve), E.Cpt1Psv0);
|
||
E.add('bibi:loaded-item', Item => Item.contentDocument.addEventListener('wheel', Eve => E.dispatch('bibi:is-wheeling', Eve), E.Cpt1Psv0));
|
||
E.add('bibi:opened', () => {
|
||
[I.Menu, I.Slider].forEach(UI => {
|
||
if(!UI.ownerDocument) return;
|
||
UI.addEventListener('wheel', Eve => { Eve.preventDefault(); Eve.stopPropagation(); }, E.Cpt1Psv0);
|
||
WheelObserver.OverlaidUIs.push(UI);
|
||
});
|
||
E.add('bibi:is-wheeling', WheelObserver.onWheel);
|
||
O.HTML.classList.add('wheel-active');
|
||
});
|
||
E.dispatch('bibi:created-wheel-observer');
|
||
}};
|
||
|
||
|
||
I.PinchObserver = { create: () => {
|
||
const PinchObserver = I.PinchObserver = {
|
||
Pinching: 0,
|
||
getEventCoords: (Eve) => {
|
||
const T0 = Eve.touches[0], T1 = Eve.touches[1], Doc = Eve.target.ownerDocument;
|
||
const T0CoordInViewport = { X: T0.screenX, Y: T0.screenY };
|
||
const T1CoordInViewport = { X: T1.screenX, Y: T1.screenY };
|
||
return {
|
||
Center: { X: (T0CoordInViewport.X + T1CoordInViewport.X) / 2, Y: (T0CoordInViewport.Y + T1CoordInViewport.Y) / 2 },
|
||
Distance: Math.sqrt(Math.pow(T1.screenX - T0.screenX, 2) + Math.pow(T1.screenY - T0.screenY, 2))
|
||
};
|
||
},
|
||
onTouchStart: (Eve) => {
|
||
if(!L.Opened) return;
|
||
if(Eve.touches.length != 2) return;
|
||
O.HTML.classList.add('pinching');
|
||
PinchObserver.Hot = true;
|
||
Eve.preventDefault(); Eve.stopPropagation();
|
||
PinchObserver.PinchStart = {
|
||
Scale: I.Loupe.CurrentTransformation.Scale,
|
||
Coords: PinchObserver.getEventCoords(Eve)
|
||
};
|
||
},
|
||
onTouchMove: (Eve) => {
|
||
if(Eve.touches.length != 2 || !PinchObserver.PinchStart) return;
|
||
Eve.preventDefault(); Eve.stopPropagation();
|
||
const Ratio = PinchObserver.getEventCoords(Eve).Distance / PinchObserver.PinchStart.Coords.Distance;
|
||
/* // Switch Utilities with Pinch-In/Out
|
||
if(PinchObserver.Pinching++ < 3 && PinchObserver.PinchStart.Scale == 1) switch(I.Utilities.UIState) {
|
||
case 'default': if(Ratio < 1) { PinchObserver.onTouchEnd(); I.Utilities.openGracefuly(); return; } break;
|
||
case 'active': if(Ratio > 1) { PinchObserver.onTouchEnd(); I.Utilities.closeGracefuly(); return; } break;
|
||
} //*/
|
||
clearTimeout(PinchObserver.Timer_TransitionRestore);
|
||
sML.style(R.Main, { transition: 'none' });
|
||
I.Loupe.scale(PinchObserver.PinchStart.Scale * Ratio, { Center: PinchObserver.PinchStart.Coords.Center, Stepless: true });
|
||
PinchObserver.Timer_TransitionRestore = setTimeout(() => sML.style(R.Main, { transition: '' }), 234);
|
||
},
|
||
onTouchEnd: (Eve, Opt) => {
|
||
PinchObserver.Pinching = 0;
|
||
delete PinchObserver.LastScale;
|
||
delete PinchObserver.PinchStart;
|
||
delete PinchObserver.Hot;
|
||
O.HTML.classList.remove('pinching');
|
||
},
|
||
getCNPf: (Doc) => Doc == document ? '' : 'bibi-',
|
||
activateElement: (Ele) => { if(!Ele) return false;
|
||
Ele.addEventListener('touchstart', PinchObserver.onTouchStart, E.Cpt1Psv0);
|
||
Ele.addEventListener('touchmove', PinchObserver.onTouchMove, E.Cpt1Psv0);
|
||
Ele.addEventListener('touchend', PinchObserver.onTouchEnd, E.Cpt1Psv0);
|
||
Ele.ownerDocument.documentElement.classList.add(PinchObserver.getCNPf(Ele) + 'pinch-active');
|
||
},
|
||
deactivateElement: (Ele) => { if(!Ele) return false;
|
||
Ele.removeEventListener('touchstart', PinchObserver.onTouchStart, E.Cpt1Psv0);
|
||
Ele.removeEventListener('touchmove', PinchObserver.onTouchMove, E.Cpt1Psv0);
|
||
Ele.removeEventListener('touchend', PinchObserver.onTouchEnd, E.Cpt1Psv0);
|
||
Ele.ownerDocument.documentElement.classList.remove(PinchObserver.getCNPf(Ele) + 'pinch-active');
|
||
}
|
||
};
|
||
PinchObserver.activateElement(R.Main);
|
||
E.add('bibi:loaded-item', Item => PinchObserver.activateElement(Item.HTML));
|
||
E.dispatch('bibi:created-pinch-observer');
|
||
}};
|
||
|
||
|
||
I.KeyObserver = { create: () => { if(!S['use-keys']) return;
|
||
const KeyObserver = I.KeyObserver = {
|
||
ActiveKeys: {},
|
||
KeyCodes: { 'keydown': {}, 'keyup': {}, 'keypress': {} },
|
||
updateKeyCodes: (EventTypes, KeyCodesToUpdate) => {
|
||
if(typeof EventTypes.join != 'function') EventTypes = [EventTypes];
|
||
if(typeof KeyCodesToUpdate == 'function') KeyCodesToUpdate = KeyCodesToUpdate();
|
||
EventTypes.forEach(EventType => KeyObserver.KeyCodes[EventType] = sML.edit(KeyObserver.KeyCodes[EventType], KeyCodesToUpdate));
|
||
},
|
||
KeyParameters: {},
|
||
initializeKeyParameters: () => {
|
||
let _ = { 'End': 'foot', 'Home': 'head' };
|
||
for(const p in _) _[p.toUpperCase()] = _[p] == 'head' ? 'foot' : _[p] == 'foot' ? 'head' : _[p];
|
||
//Object.assign(_, { 'Space': 1, 'SPACE': -1 }); // Space key is reserved for Loupe.
|
||
KeyObserver.KeyParameters = _;
|
||
},
|
||
updateKeyParameters: () => {
|
||
const _O = I.orthogonal('arrow-keys');
|
||
const _ = (() => { switch(S.ARA) {
|
||
case 'horizontal': return Object.assign({ 'Left Arrow': C.d2d('left'), 'Right Arrow': C.d2d('right' ) }, _O == 'move' ? { 'Up Arrow': C.d2d('top' , 9), 'Down Arrow': C.d2d('bottom', 9) } : { 'Up Arrow': _O, 'Down Arrow': _O });
|
||
case 'vertical': return Object.assign({ 'Up Arrow': C.d2d('top' ), 'Down Arrow': C.d2d('bottom') }, _O == 'move' ? { 'Left Arrow': C.d2d('left', 9), 'Right Arrow': C.d2d('right' , 9) } : { 'Left Arrow': _O, 'Right Arrow': _O });
|
||
} })();
|
||
for(const p in _) _[p.toUpperCase()] = _[p] == -1 ? 'head' : _[p] == 1 ? 'foot' : _[p];
|
||
Object.assign(KeyObserver.KeyParameters, _);
|
||
},
|
||
getBibiKeyName: (Eve) => {
|
||
const KeyName = KeyObserver.KeyCodes[Eve.type][Eve.keyCode];
|
||
return KeyName ? KeyName : '';
|
||
},
|
||
onEvent: (Eve) => {
|
||
if(!L.Opened) return false;
|
||
Eve.BibiKeyName = KeyObserver.getBibiKeyName(Eve);
|
||
Eve.BibiModifierKeys = [];
|
||
if(Eve.shiftKey) Eve.BibiModifierKeys.push('Shift');
|
||
if(Eve.ctrlKey) Eve.BibiModifierKeys.push('Control');
|
||
if(Eve.altKey) Eve.BibiModifierKeys.push('Alt');
|
||
if(Eve.metaKey) Eve.BibiModifierKeys.push('Meta');
|
||
//if(!Eve.BibiKeyName) return false;
|
||
if(Eve.BibiKeyName) Eve.preventDefault();
|
||
return true;
|
||
},
|
||
onKeyDown: (Eve) => {
|
||
if(!KeyObserver.onEvent(Eve)) return false;
|
||
if(Eve.BibiKeyName) {
|
||
if(!KeyObserver.ActiveKeys[Eve.BibiKeyName]) {
|
||
KeyObserver.ActiveKeys[Eve.BibiKeyName] = Date.now();
|
||
} else {
|
||
E.dispatch('bibi:is-holding-key', Eve);
|
||
}
|
||
}
|
||
E.dispatch('bibi:downed-key', Eve);
|
||
},
|
||
onKeyUp: (Eve) => {
|
||
if(!KeyObserver.onEvent(Eve)) return false;
|
||
if(KeyObserver.ActiveKeys[Eve.BibiKeyName] && Date.now() - KeyObserver.ActiveKeys[Eve.BibiKeyName] < 300) {
|
||
E.dispatch('bibi:touched-key', Eve);
|
||
}
|
||
if(Eve.BibiKeyName) {
|
||
if(KeyObserver.ActiveKeys[Eve.BibiKeyName]) {
|
||
delete KeyObserver.ActiveKeys[Eve.BibiKeyName];
|
||
}
|
||
}
|
||
E.dispatch('bibi:upped-key', Eve);
|
||
},
|
||
onKeyPress: (Eve) => {
|
||
if(!KeyObserver.onEvent(Eve)) return false;
|
||
E.dispatch('bibi:pressed-key', Eve);
|
||
},
|
||
observe: (Doc) => {
|
||
['keydown', 'keyup', 'keypress'].forEach(EventName => Doc.addEventListener(EventName, KeyObserver['onKey' + sML.capitalise(EventName.replace('key', ''))], false));
|
||
},
|
||
onKeyTouch: (Eve) => {
|
||
if(!Eve.BibiKeyName) return false;
|
||
const KeyParameter = KeyObserver.KeyParameters[!Eve.shiftKey ? Eve.BibiKeyName : Eve.BibiKeyName.toUpperCase()];
|
||
if(!KeyParameter) return false;
|
||
Eve.preventDefault();
|
||
switch(typeof KeyParameter) {
|
||
case 'number': if(I.Flipper.isAbleToFlip(KeyParameter)) {
|
||
if(I.Arrows) E.dispatch(I.Arrows[KeyParameter], 'bibi:tapped', Eve);
|
||
I.Flipper.flip(KeyParameter);
|
||
} break;
|
||
case 'string': switch(KeyParameter) {
|
||
case 'head': case 'foot': return R.focusOn({ Destination: KeyParameter, Duration: 0 });
|
||
case 'utilities': return I.Utilities.toggleGracefuly();
|
||
case 'switch': return I.AxisSwitcher ? I.AxisSwitcher.switchAxis() : false;
|
||
} break;
|
||
}
|
||
}
|
||
};
|
||
KeyObserver.updateKeyCodes(['keydown', 'keyup', 'keypress'], {
|
||
32: 'Space'
|
||
});
|
||
KeyObserver.updateKeyCodes(['keydown', 'keyup'], {
|
||
33: 'Page Up', 34: 'Page Down',
|
||
35: 'End', 36: 'Home',
|
||
37: 'Left Arrow', 38: 'Up Arrow', 39: 'Right Arrow', 40: 'Down Arrow'
|
||
});
|
||
E.add('bibi:postprocessed-item', Item => Item.IsPlaceholder ? false : KeyObserver.observe(Item.contentDocument));
|
||
E.add('bibi:opened', () => {
|
||
KeyObserver.initializeKeyParameters(), KeyObserver.updateKeyParameters(), E.add('bibi:changed-view', () => KeyObserver.updateKeyParameters());
|
||
KeyObserver.observe(document);
|
||
E.add(['bibi:touched-key', 'bibi:is-holding-key'], Eve => KeyObserver.onKeyTouch(Eve));
|
||
});
|
||
E.dispatch('bibi:created-key-observer');
|
||
}};
|
||
|
||
|
||
I.EdgeObserver = { create: () => {
|
||
const EdgeObserver = I.EdgeObserver = {};
|
||
E.add('bibi:opened', () => {
|
||
E.add(['bibi:tapped-not-center', 'bibi:doubletapped-not-center'], Eve => {
|
||
if(I.isPointerStealth()) return false;
|
||
const BibiEvent = O.getBibiEvent(Eve);
|
||
const Dir = I.Flipper.getDirection(BibiEvent.Division), Ortho = I.orthogonal('edges'), Dist = C.d2d(Dir, Ortho == 'move');
|
||
if(Dist) {
|
||
if(I.Arrows && I.Arrows.areAvailable(BibiEvent)) E.dispatch(I.Arrows[Dist], 'bibi:tapped', Eve);
|
||
if(I.Flipper.isAbleToFlip(Dist)) I.Flipper.flip(Dist);
|
||
} else if(typeof C.DDD[Dir] == 'string') switch(Ortho) {
|
||
case 'utilities': I.Utilities.toggleGracefuly(); break;
|
||
case 'switch': if(I.AxisSwitcher) I.AxisSwitcher.switchAxis(); break;
|
||
}
|
||
});
|
||
if(!O.TouchOS) {
|
||
E.add('bibi:moved-pointer', Eve => {
|
||
if(I.isPointerStealth()) return false;
|
||
const BibiEvent = O.getBibiEvent(Eve);
|
||
if(I.Arrows.areAvailable(BibiEvent)) {
|
||
const Dir = I.Flipper.getDirection(BibiEvent.Division), Ortho = I.orthogonal('edges'), Dist = C.d2d(Dir, Ortho == 'move');
|
||
if(Dist && I.Flipper.isAbleToFlip(Dist)) {
|
||
EdgeObserver.Hovering = true;
|
||
if(I.Arrows) {
|
||
const Arrow = I.Arrows[Dist];
|
||
E.dispatch(Arrow, 'bibi:hovered', Eve);
|
||
E.dispatch(Arrow.Pair, 'bibi:unhovered', Eve);
|
||
}
|
||
const HoveringHTML = BibiEvent.Target.ownerDocument.documentElement;
|
||
if(EdgeObserver.HoveringHTML != HoveringHTML) {
|
||
if(EdgeObserver.HoveringHTML) EdgeObserver.HoveringHTML.removeAttribute('data-bibi-cursor');
|
||
(EdgeObserver.HoveringHTML = HoveringHTML).setAttribute('data-bibi-cursor', Dir);
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
if(EdgeObserver.Hovering) {
|
||
EdgeObserver.Hovering = false;
|
||
if(I.Arrows) E.dispatch([I.Arrows.Back, I.Arrows.Forward], 'bibi:unhovered', Eve);
|
||
if(EdgeObserver.HoveringHTML) EdgeObserver.HoveringHTML.removeAttribute('data-bibi-cursor'), EdgeObserver.HoveringHTML = null;
|
||
}
|
||
});
|
||
sML.forEach(O.Body.querySelectorAll('img'))(Img => Img.addEventListener(E['pointerdown'], O.preventDefault));
|
||
}
|
||
});
|
||
if(!O.TouchOS) {
|
||
E.add('bibi:loaded-item', Item => sML.forEach(Item.Body.querySelectorAll('img'))(Img => Img.addEventListener(E['pointerdown'], O.preventDefault)));
|
||
}
|
||
E.dispatch('bibi:created-edge-observer');
|
||
}};
|
||
|
||
|
||
I.Notifier = { create: () => {
|
||
const Notifier = I.Notifier = O.Body.appendChild(sML.create('div', { id: 'bibi-notifier',
|
||
show: (Msg, Err) => {
|
||
clearTimeout(Notifier.Timer_hide);
|
||
Notifier.P.className = Err ? 'error' : '';
|
||
Notifier.P.innerHTML = Msg;
|
||
O.HTML.classList.add('notifier-shown');
|
||
if(L.Opened && !Err) Notifier.addEventListener(O.TouchOS ? E['pointerdown'] : E['pointerover'], Notifier.hide);
|
||
},
|
||
hide: (Time = 0) => {
|
||
clearTimeout(Notifier.Timer_hide);
|
||
Notifier.Timer_hide = setTimeout(() => {
|
||
if(L.Opened) Notifier.removeEventListener(O.TouchOS ? E['pointerdown'] : E['pointerover'], Notifier.hide);
|
||
O.HTML.classList.remove('notifier-shown');
|
||
}, Time);
|
||
},
|
||
note: (Msg, Time, Err) => {
|
||
if(!Msg) return Notifier.hide();
|
||
Notifier.show(Msg, Err);
|
||
if(typeof Time == 'undefined') Time = O.Busy && !L.Opened ? 8888 : 2222;
|
||
if(typeof Time == 'number') Notifier.hide(Time);
|
||
}
|
||
}));
|
||
Notifier.P = Notifier.appendChild(document.createElement('p'));
|
||
I.note = Notifier.note;
|
||
E.dispatch('bibi:created-notifier');
|
||
}};
|
||
|
||
I.note = () => false;
|
||
|
||
|
||
I.Veil = { create: () => {
|
||
const Veil = I.Veil = I.setToggleAction(O.Body.appendChild(sML.create('div', { id: 'bibi-veil' })), {
|
||
// Translate: 240, /* % */ // Rotate: -48, /* deg */ // Perspective: 240, /* px */
|
||
onopened: () => (O.HTML.classList.add('veil-opened'), Veil.classList.remove('closed')),
|
||
onclosed: () => (Veil.classList.add('closed'), O.HTML.classList.remove('veil-opened'))
|
||
});
|
||
['touchstart', 'pointerdown', 'mousedown', 'click'].forEach(EN => Veil.addEventListener(EN, Eve => Eve.stopPropagation(), E.Cpt0Psv0));
|
||
Veil.open();
|
||
const PlayButtonTitle = (O.TouchOS ? 'Tap' : 'Click') + ' to Open';
|
||
const PlayButton = Veil.PlayButton = Veil.appendChild(
|
||
sML.create('p', { id: 'bibi-veil-play', title: PlayButtonTitle,
|
||
innerHTML: `<span class="non-visual">${ PlayButtonTitle }</span>`,
|
||
play: (Eve) => (Eve.stopPropagation(), L.play(), E.dispatch('bibi:played:by-button')),
|
||
hide: ( ) => sML.style(PlayButton, { opacity: 0, cursor: 'default' }).then(Eve => Veil.removeChild(PlayButton)),
|
||
on: { click: Eve => PlayButton.play(Eve) }
|
||
})
|
||
);
|
||
E.add('bibi:played', () => PlayButton.hide());
|
||
Veil.byebye = (Msg = {}) => {
|
||
Veil.innerHTML = '';
|
||
Veil.ByeBye = Veil.appendChild(sML.create('p', { id: 'bibi-veil-byebye' }));
|
||
['en', 'ja'].forEach(Lang => Veil.ByeBye.innerHTML += `<span lang="${ Lang }">${ Msg[Lang] }</span>`);
|
||
O.HTML.classList.remove('welcome');
|
||
Veil.open();
|
||
return Msg['en'] ? Msg['en'].replace(/<[^>]*>/g, '') : '';
|
||
};
|
||
Veil.Cover = Veil.appendChild( sML.create('div', { id: 'bibi-veil-cover' }));
|
||
Veil.Cover.Info = Veil.Cover.appendChild(sML.create('p', { id: 'bibi-veil-cover-info' }));
|
||
E.dispatch('bibi:created-veil');
|
||
}};
|
||
|
||
|
||
I.Catcher = { create: () => { if(S['book-data'] || S['book'] || !S['accept-local-file']) return;
|
||
const Catcher = I.Catcher = O.Body.appendChild(sML.create('div', { id: 'bibi-catcher' }));
|
||
Catcher.insertAdjacentHTML('afterbegin', I.distillLabels.distillLanguage({
|
||
default: [
|
||
`<div class="pgroup" lang="en">`,
|
||
`<p><strong>Pass Me Your EPUB File!</strong></p>`,
|
||
`<p><em>You Can Open Your Own EPUB.</em></p>`,
|
||
`<p><span>Please ${ O.TouchOS ? 'Tap Screen' : 'Drag & Drop It Here. <br />Or Click Screen' } and Choose It.</span></p>`,
|
||
`<p><small>(Open in Your Device without Uploading)</small></p>`,
|
||
`</div>`
|
||
].join(''),
|
||
ja: [
|
||
`<div class="pgroup" lang="ja">`,
|
||
`<p><strong>EPUBファイルをここにください!</strong></p>`,
|
||
`<p><em>お持ちの EPUB ファイルを<br />開くことができます。</em></p>`,
|
||
`<p><span>${ O.TouchOS ? '画面をタップ' : 'ここにドラッグ&ドロップするか、<br />画面をクリック' }して選択してください。</span></p>`,
|
||
`<p><small>(外部に送信されず、この端末の中で開きます)</small></p>`,
|
||
`</div>`
|
||
].join('')
|
||
})[O.Language]);
|
||
Catcher.title = Catcher.querySelector('span').innerHTML.replace(/<br( ?\/)?>/g, '\n').replace(/<[^>]+>/g, '').trim();
|
||
Catcher.Input = Catcher.appendChild(sML.create('input', { type: 'file' }));
|
||
if(!S['extract-if-necessary'].includes('*') && S['extract-if-necessary'].length) {
|
||
const Accept = [];
|
||
if(S['extract-if-necessary'].includes('.epub')) {
|
||
Accept.push('application/epub+zip');
|
||
}
|
||
if(S['extract-if-necessary'].includes('.zip')) {
|
||
Accept.push('application/zip');
|
||
Accept.push('application/x-zip');
|
||
Accept.push('application/x-zip-compressed');
|
||
}
|
||
if(Accept.length) Catcher.Input.setAttribute('accept', Accept.join(','));
|
||
}
|
||
Catcher.Input.addEventListener('change', Eve => {
|
||
let FileData = {}; try { FileData = Eve.target.files[0]; } catch(_) {}
|
||
Bibi.getBookData.resolve({ BookData: FileData });
|
||
});
|
||
Catcher.addEventListener('click', Eve => Catcher.Input.click(Eve));
|
||
if(!O.TouchOS) {
|
||
Catcher.addEventListener('dragenter', Eve => { Eve.preventDefault(); O.HTML.classList.add( 'dragenter'); }, 1);
|
||
Catcher.addEventListener('dragover', Eve => { Eve.preventDefault(); }, 1);
|
||
Catcher.addEventListener('dragleave', Eve => { Eve.preventDefault(); O.HTML.classList.remove('dragenter'); }, 1);
|
||
Catcher.addEventListener('drop', Eve => { Eve.preventDefault();
|
||
let FileData = {}; try { FileData = Eve.dataTransfer.files[0]; } catch(_) {}
|
||
Bibi.getBookData.resolve({ BookData: FileData });
|
||
}, 1);
|
||
}
|
||
Catcher.appendChild(I.getBookIcon());
|
||
E.dispatch('bibi:created-catcher');
|
||
}};
|
||
|
||
|
||
I.Menu = { create: () => {
|
||
if(!S['use-menubar']) O.HTML.classList.add('without-menubar');
|
||
const Menu = I.Menu = O.Body.appendChild(sML.create('div', { id: 'bibi-menu' }, I.Menu)); delete Menu.create;
|
||
//Menu.addEventListener('click', Eve => Eve.stopPropagation());
|
||
I.TouchObserver.setElementHoverActions(Menu);
|
||
I.setToggleAction(Menu, {
|
||
onopened: () => { O.HTML.classList.add( 'menu-opened'); E.dispatch('bibi:opened-menu'); },
|
||
onclosed: () => { O.HTML.classList.remove('menu-opened'); E.dispatch('bibi:closed-menu'); }
|
||
});
|
||
E.add('bibi:commands:open-menu', Menu.open);
|
||
E.add('bibi:commands:close-menu', Menu.close);
|
||
E.add('bibi:commands:toggle-menu', Menu.toggle);
|
||
E.add('bibi:opens-utilities', Opt => E.dispatch('bibi:commands:open-menu', Opt));
|
||
E.add('bibi:closes-utilities', Opt => E.dispatch('bibi:commands:close-menu', Opt));
|
||
E.add('bibi:opened', Menu.close);/*
|
||
E.add('bibi:changes-intersection', () => {
|
||
clearTimeout(Menu.Timer_cool);
|
||
if(!Menu.Hot) Menu.classList.add('hot');
|
||
Menu.Hot = true;
|
||
Menu.Timer_cool = setTimeout(() => {
|
||
Menu.Hot = false;
|
||
Menu.classList.remove('hot');
|
||
}, 1234);
|
||
});*//*
|
||
if(sML.OS.iOS) {
|
||
Menu.addEventListener('pointerdown', console.log);
|
||
Menu.addEventListener('pointerover', console.log);
|
||
}*/
|
||
if(!O.TouchOS) E.add('bibi:opened', () => {
|
||
E.add('bibi:moved-pointer', Eve => {
|
||
if(I.isPointerStealth()) return false;
|
||
const BibiEvent = O.getBibiEvent(Eve);
|
||
clearTimeout(Menu.Timer_close);
|
||
if(BibiEvent.Division.Y == 'top') { //if(BibiEvent.Coord.Y < Menu.Height * 1.5) {
|
||
E.dispatch(Menu, 'bibi:hovered', Eve);
|
||
} else if(Menu.Hover) {
|
||
Menu.Timer_close = setTimeout(() => E.dispatch(Menu, 'bibi:unhovered', Eve), 123);
|
||
}
|
||
});
|
||
});
|
||
Menu.L = Menu.appendChild(sML.create('div', { id: 'bibi-menu-l' }));
|
||
Menu.R = Menu.appendChild(sML.create('div', { id: 'bibi-menu-r' }));
|
||
[Menu.L, Menu.R].forEach(MenuSide => {
|
||
MenuSide.ButtonGroups = [];
|
||
MenuSide.addButtonGroup = function(Par) {
|
||
const ButtonGroup = I.createButtonGroup(Par);
|
||
if(!ButtonGroup) return null;
|
||
this.ButtonGroups.push(ButtonGroup);
|
||
return this.appendChild(ButtonGroup);
|
||
};
|
||
});
|
||
{ // Optimize to Scrollbar Size
|
||
const _Common = 'html.appearance-vertical:not(.veil-opened):not(.slider-opened)', _M = ' div#bibi-menu';
|
||
sML.appendCSSRule(_Common + _M, 'width: calc(100% - ' + O.Scrollbars.Width + 'px);');
|
||
sML.appendCSSRule([_Common + '.panel-opened' + _M, _Common + '.subpanel-opened' + _M].join(', '), 'padding-right: ' + O.Scrollbars.Width + 'px;');
|
||
}
|
||
I.OpenedSubpanel = null;
|
||
I.Subpanels = [];
|
||
Menu.Config.create();
|
||
E.dispatch('bibi:created-menu');
|
||
}};
|
||
|
||
I.Menu.Config = { create: () => {
|
||
const Menu = I.Menu;
|
||
const Components = [];
|
||
if(!S['fix-reader-view-mode']) Components.push('ViewModeSection');
|
||
if(O.Embedded) Components.push('NewWindowButton');
|
||
if(O.FullscreenTarget && !O.TouchOS) Components.push('FullscreenButton');
|
||
if(S['website-href'] && /^https?:\/\/[^\/]+/.test(S['website-href']) && S['website-name-in-menu']) Components.push('WebsiteLink');
|
||
if(!S['remove-bibi-website-link']) Components.push('BibiWebsiteLink');
|
||
if(!Components.length) {
|
||
delete I.Menu.Config;
|
||
return;
|
||
}
|
||
const Config = Menu.Config = sML.applyRtL(I.createSubpanel({ id: 'bibi-subpanel_config' }), Menu.Config); delete Config.create;
|
||
const Opener = Config.bindOpener(Menu.R.addButtonGroup({ Sticky: true }).addButton({
|
||
Type: 'toggle',
|
||
Labels: {
|
||
default: { default: `Configure Setting`, ja: `設定を変更` },
|
||
active: { default: `Close Setting-Menu`, ja: `設定メニューを閉じる` }
|
||
},
|
||
Help: true,
|
||
Icon: `<span class="bibi-icon bibi-icon-config"></span>`
|
||
}));
|
||
if(Components.includes('ViewModeSection') ) Config.ViewModeSection.create( ); else delete Config.ViewModeSection;
|
||
if(Components.includes('NewWindowButton') || Components.includes('FullscreenButton')) Config.WindowSection.create(Components); else delete Config.WindowSection;
|
||
if(Components.includes('WebsiteLink') || Components.includes('BibiWebsiteLink') ) Config.LinkageSection.create(Components); else delete Config.LinkageSection;
|
||
E.dispatch('bibi:created-config');
|
||
}};
|
||
|
||
I.Menu.Config.ViewModeSection = { create: () => {
|
||
const Config = I.Menu.Config;
|
||
const /* SpreadShapes */ SSs = (/* SpreadShape */ SS => SS + SS + SS)((/* ItemShape */ IS => `<span class="bibi-shape bibi-shape-spread">${ IS + IS }</span>`)(`<span class="bibi-shape bibi-shape-item"></span>`));
|
||
const Section = Config.ViewModeSection = Config.addSection({
|
||
Labels: { default: { default: `View Mode`, ja: `閲覧モード` } },
|
||
ButtonGroups: [{
|
||
ButtonType: 'radio',
|
||
Buttons: [{
|
||
Mode: 'paged',
|
||
Labels: { default: { default: `Spread / Page`, ja: `見開き/ページ` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-view bibi-icon-view-paged"><span class="bibi-shape bibi-shape-spreads bibi-shape-spreads-paged">${ SSs }</span></span>`
|
||
}, {
|
||
Mode: 'horizontal',
|
||
Labels: { default: { default: `<span class="non-visual-in-label">⇄ </span>Horizontal Scroll`, ja: `<span class="non-visual-in-label">⇄ </span>横スクロール` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-view bibi-icon-view-horizontal"><span class="bibi-shape bibi-shape-spreads bibi-shape-spreads-horizontal">${ SSs }</span></span>`
|
||
}, {
|
||
Mode: 'vertical',
|
||
Labels: { default: { default: `<span class="non-visual-in-label">⇅ </span>Vertical Scroll`, ja: `<span class="non-visual-in-label">⇅ </span>縦スクロール` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-view bibi-icon-view-vertical"><span class="bibi-shape bibi-shape-spreads bibi-shape-spreads-vertical">${ SSs }</span></span>`
|
||
}].map(Button => sML.edit(Button, {
|
||
Notes: true,
|
||
action: () => R.changeView({ Mode: Button.Mode, NoNotification: true })
|
||
}))
|
||
}, /*{
|
||
Buttons: []
|
||
}, */{
|
||
Buttons: [{
|
||
Name: 'full-breadth-layout-in-scroll',
|
||
Type: 'toggle',
|
||
Notes: false,
|
||
Labels: { default: { default: `Full Width for Each Page <small>(in Scrolling Mode)</small>`, ja: `スクロール表示で各ページを幅一杯に</small>` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-full-breadth-layout"></span>`,
|
||
action: function() {
|
||
const IsActive = (this.UIState == 'active');
|
||
S.update({ 'full-breadth-layout-in-scroll': IsActive });
|
||
if(IsActive) O.HTML.classList.add( 'book-full-breadth');
|
||
else O.HTML.classList.remove('book-full-breadth');
|
||
if(S.RVM == 'horizontal' || S.RVM == 'vertical') R.changeView({ Mode: S.RVM, Force: true });
|
||
if(S['keep-settings'] && O.Biscuits) O.Biscuits.memorize('Book', { FBL: S['full-breadth-layout-in-scroll'] });
|
||
}
|
||
}]
|
||
}]
|
||
});
|
||
E.add('bibi:updated-settings', () => {
|
||
Section.ButtonGroups[0].Buttons.forEach(Button => I.setUIState(Button, (Button.Mode == S.RVM ? 'active' : 'default')));
|
||
});/*
|
||
E.add('bibi:updated-settings', () => {
|
||
const ButtonGroup = Section.ButtonGroups[1];
|
||
ButtonGroup.style.display = S.BRL == 'reflowable' ? '' : 'none';
|
||
ButtonGroup.Buttons.forEach(Button => I.setUIState(Button, S[Button.Name] ? 'active' : 'default'));
|
||
});*/
|
||
E.add('bibi:updated-settings', () => {
|
||
const ButtonGroup = Section.ButtonGroups[Section.ButtonGroups.length - 1];
|
||
ButtonGroup.style.display = S.BRL == 'pre-paginated' ? '' : 'none';
|
||
ButtonGroup.Buttons.forEach(Button => I.setUIState(Button, S[Button.Name] ? 'active' : 'default'));
|
||
});
|
||
}};
|
||
|
||
I.Menu.Config.WindowSection = { create: (Components) => {
|
||
const Config = I.Menu.Config;
|
||
const Buttons = [];
|
||
if(Components.includes('NewWindowButton')) {
|
||
Buttons.push({
|
||
Type: 'link',
|
||
Labels: {
|
||
default: { default: `Open in New Window`, ja: `あたらしいウィンドウで開く` }
|
||
},
|
||
Icon: `<span class="bibi-icon bibi-icon-open-newwindow"></span>`,
|
||
id: 'bibi-button-open-newwindow',
|
||
href: O.RequestedURL,
|
||
target: '_blank'
|
||
});
|
||
}
|
||
if(Components.includes('FullscreenButton')) {
|
||
Buttons.push({
|
||
Type: 'toggle',
|
||
Labels: {
|
||
default: { default: `Enter Fullscreen`, ja: `フルスクリーンモード` },
|
||
active: { default: `Exit Fullscreen`, ja: `フルスクリーンモード解除` }
|
||
},
|
||
Icon: `<span class="bibi-icon bibi-icon-toggle-fullscreen"></span>`,
|
||
id: 'bibi-button-toggle-fullscreen',
|
||
action: function() {
|
||
!O.Fullscreen ? O.FullscreenTarget.requestFullscreen() : O.FullscreenTarget.ownerDocument.exitFullscreen();
|
||
Config.close();
|
||
}
|
||
});
|
||
O.FullscreenTarget.ownerDocument.addEventListener('fullscreenchange', function() { // care multi-embeddeding
|
||
if(!O.FullscreenButton) O.FullscreenButton = document.getElementById('bibi-button-toggle-fullscreen');
|
||
if(this.fullscreenElement == O.FullscreenTarget) {
|
||
O.Fullscreen = true;
|
||
O.HTML.classList.add('fullscreen');
|
||
I.setUIState(O.FullscreenButton, 'active');
|
||
} else if(O.Fullscreen) {
|
||
O.Fullscreen = false;
|
||
O.HTML.classList.remove('fullscreen');
|
||
I.setUIState(O.FullscreenButton, 'default');
|
||
}
|
||
});
|
||
}
|
||
if(Buttons.length) {
|
||
const Section = Config.WindowSection = Config.addSection({ Labels: { default: { default: `Window Control`, ja: `ウィンドウ制御` } } });
|
||
Section.addButtonGroup({ Buttons: Buttons });
|
||
}
|
||
}};
|
||
|
||
I.Menu.Config.LinkageSection = { create: (Components) => {
|
||
const Config = I.Menu.Config;
|
||
const Buttons = [];
|
||
if(Components.includes('WebsiteLink')) Buttons.push({
|
||
Type: 'link',
|
||
Labels: { default: { default: S['website-name-in-menu'].replace(/&/gi, '&').replace(/</gi, '<').replace(/>/gi, '>') } },
|
||
Icon: `<span class="bibi-icon bibi-icon-open-newwindow"></span>`,
|
||
href: S['website-href'],
|
||
target: '_blank'
|
||
});
|
||
if(Components.includes('BibiWebsiteLink')) Buttons.push({
|
||
Type: 'link',
|
||
Labels: { default: { default: `Bibi | Official Website` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-open-newwindow"></span>`,
|
||
href: Bibi['href'],
|
||
target: '_blank'
|
||
});
|
||
if(Buttons.length) {
|
||
const Section = Config.LinkageSection = Config.addSection({ Labels: { default: { default: `Link${ Buttons.length > 1 ? 's' : '' }`, ja: `リンク` } } });
|
||
Section.addButtonGroup({ Buttons: Buttons });
|
||
}
|
||
}};
|
||
|
||
|
||
I.Panel = { create: () => {
|
||
const Panel = I.Panel = O.Body.appendChild(sML.create('div', { id: 'bibi-panel' }));
|
||
I.setToggleAction(Panel, {
|
||
onopened: () => { O.HTML.classList.add( 'panel-opened'); E.dispatch('bibi:opened-panel'); },
|
||
onclosed: () => { O.HTML.classList.remove('panel-opened'); E.dispatch('bibi:closed-panel'); }
|
||
});
|
||
E.add('bibi:commands:open-panel', Panel.open);
|
||
E.add('bibi:commands:close-panel', Panel.close);
|
||
E.add('bibi:commands:toggle-panel', Panel.toggle);
|
||
E.add('bibi:closes-utilities', Panel.close);
|
||
I.setFeedback(Panel, { StopPropagation: true });
|
||
E.add(Panel, 'bibi:tapped', () => E.dispatch('bibi:commands:toggle-panel'));
|
||
Panel.BookInfo = Panel.appendChild( sML.create('div', { id: 'bibi-panel-bookinfo' }));
|
||
Panel.BookInfo.Cover = Panel.BookInfo.appendChild( sML.create('div', { id: 'bibi-panel-bookinfo-cover' }));
|
||
Panel.BookInfo.Cover.Info = Panel.BookInfo.Cover.appendChild(sML.create('p', { id: 'bibi-panel-bookinfo-cover-info' }));
|
||
const Opener = Panel.Opener = I.Menu.L.addButtonGroup({ Sticky: true }).addButton({
|
||
Type: 'toggle',
|
||
Labels: {
|
||
default: { default: `Open Index`, ja: `目次を開く` },
|
||
active: { default: `Close Index`, ja: `目次を閉じる` }
|
||
},
|
||
Help: true,
|
||
Icon: `<span class="bibi-icon bibi-icon-toggle-panel">${ (Bars => { for(let i = 1; i <= 6; i++) Bars += '<span></span>'; return Bars; })('') }</span>`,
|
||
action: () => Panel.toggle()
|
||
});
|
||
E.add('bibi:opened-panel', () => I.setUIState(Opener, 'active' ));
|
||
E.add('bibi:closed-panel', () => I.setUIState(Opener, '' ));
|
||
E.add('bibi:started', () => sML.style(Opener, { display: 'block' }));
|
||
if(S['on-doubletap'] == 'panel') E.add('bibi:doubletapped', () => Panel.toggle());
|
||
if(S['on-tripletap'] == 'panel') E.add('bibi:tripletapped', () => Panel.toggle());
|
||
//sML.appendCSSRule('div#bibi-panel-bookinfo', 'height: calc(100% - ' + (O.Scrollbars.Height) + 'px);'); // Optimize to Scrollbar Size
|
||
E.dispatch('bibi:created-panel');
|
||
}};
|
||
|
||
|
||
I.Help = { create: () => {
|
||
const Help = I.Help = O.Body.appendChild(sML.create('div', { id: 'bibi-help' }));
|
||
Help.Message = Help.appendChild(sML.create('p', { className: 'hidden', id: 'bibi-help-message' }));
|
||
Help.show = (HelpText) => {
|
||
clearTimeout(Help.Timer_deactivate1);
|
||
clearTimeout(Help.Timer_deactivate2);
|
||
Help.classList.add('active');
|
||
Help.Message.innerHTML = HelpText;
|
||
setTimeout(() => Help.classList.add('shown'), 0);
|
||
};
|
||
Help.hide = () => {
|
||
Help.Timer_deactivate1 = setTimeout(() => {
|
||
Help.classList.remove('shown');
|
||
Help.Timer_deactivate2 = setTimeout(() => Help.classList.remove('active'), 200);
|
||
}, 100);
|
||
};
|
||
/*
|
||
sML.appendCSSRule([ // Optimize to Scrollbar Size
|
||
'html.appearance-horizontal div#bibi-help',
|
||
'html.page-rtl.panel-opened div#bibi-help'
|
||
].join(', '), 'bottom: ' + (O.Scrollbars.Height) + 'px;');
|
||
*/
|
||
}};
|
||
|
||
|
||
I.PoweredBy = { create: () => {
|
||
const PoweredBy = I.PoweredBy = O.Body.appendChild(sML.create('div', { id: 'bibi-poweredby', innerHTML: `<p><a href="${ Bibi['href'] }" target="_blank" title="Bibi | Official Website">Bibi</a></p>` }));
|
||
/*
|
||
sML.appendCSSRule([ // Optimize to Scrollbar Size
|
||
'html.appearance-horizontal div#bibi-poweredby',
|
||
'html.page-rtl.panel-opened div#bibi-poweredby'
|
||
].join(', '), 'bottom: ' + (O.Scrollbars.Height) + 'px;');
|
||
*/
|
||
}};
|
||
|
||
I.FontSizeChanger = { create: () => {
|
||
const FontSizeChanger = I.FontSizeChanger = {};
|
||
if(typeof S['font-size-scale-per-step'] != 'number' || S['font-size-scale-per-step'] <= 1) S['font-size-scale-per-step'] = 1.25;
|
||
if(S['use-font-size-changer'] && S['keep-settings'] && O.Biscuits) {
|
||
const BibiBiscuits = O.Biscuits.remember('Bibi');
|
||
if(BibiBiscuits && BibiBiscuits.FontSize && BibiBiscuits.FontSize.Step != undefined) FontSizeChanger.Step = BibiBiscuits.FontSize.Step * 1;
|
||
}
|
||
if(typeof FontSizeChanger.Step != 'number' || FontSizeChanger.Step < -2 || 2 < FontSizeChanger.Step) FontSizeChanger.Step = 0;
|
||
E.bind('bibi:postprocessed-item', Item => { if(Item['rendition:layout'] == 'pre-paginated') return false;
|
||
Item.changeFontSize = (FontSize) => {
|
||
if(Item.FontSizeStyleRule) sML.deleteCSSRule(Item.contentDocument, Item.FontSizeStyleRule);
|
||
Item.FontSizeStyleRule = sML.appendCSSRule(Item.contentDocument, 'html', 'font-size: ' + FontSize + 'px !important;');
|
||
};
|
||
Item.changeFontSizeStep = (Step) => Item.changeFontSize(Item.FontSize.Base * Math.pow(S['font-size-scale-per-step'], Step));
|
||
Item.FontSize = {
|
||
Default: getComputedStyle(Item.HTML).fontSize.replace(/[^\d]*$/, '') * 1
|
||
};
|
||
Item.FontSize.Base = Item.FontSize.Default;
|
||
if(Item.Source.Preprocessed && (sML.UA.Chrome || sML.UA.Trident)) {
|
||
sML.forEach(Item.HTML.querySelectorAll('body, body *'))(Ele => Ele.style.fontSize = parseInt(getComputedStyle(Ele).fontSize) / Item.FontSize.Base + 'rem');
|
||
} else {
|
||
O.editCSSRules(Item.contentDocument, CSSRule => {
|
||
if(!CSSRule || !CSSRule.selectorText || /^@/.test(CSSRule.selectorText)) return;
|
||
try { if(Item.contentDocument.querySelector(CSSRule.selectorText) == Item.HTML) return; } catch(_) {}
|
||
const REs = {
|
||
'pt': / font-size: (\d[\d\.]*)pt; /,
|
||
'px': / font-size: (\d[\d\.]*)px; /
|
||
};
|
||
if(REs['pt'].test(CSSRule.cssText)) CSSRule.style.fontSize = CSSRule.cssText.match(REs['pt'])[1] * (96/72) / Item.FontSize.Base + 'rem';
|
||
if(REs['px'].test(CSSRule.cssText)) CSSRule.style.fontSize = CSSRule.cssText.match(REs['px'])[1] / Item.FontSize.Base + 'rem';
|
||
});
|
||
}
|
||
if(typeof S['base-font-size'] == 'number' && S['base-font-size'] > 0) {
|
||
let MostPopularFontSize = 0;
|
||
const FontSizeCounter = {};
|
||
sML.forEach(Item.Body.querySelectorAll('p, p *'))(Ele => {
|
||
if(!Ele.innerText.replace(/\s/g, '')) return;
|
||
const FontSize = Math.round(getComputedStyle(Ele).fontSize.replace(/[^\d]*$/, '') * 100) / 100;
|
||
if(!FontSizeCounter[FontSize]) FontSizeCounter[FontSize] = [];
|
||
FontSizeCounter[FontSize].push(Ele);
|
||
});
|
||
let MostPopularFontSizeAmount = 0;
|
||
for(const FontSize in FontSizeCounter) {
|
||
if(FontSizeCounter[FontSize].length > MostPopularFontSizeAmount) {
|
||
MostPopularFontSizeAmount = FontSizeCounter[FontSize].length;
|
||
MostPopularFontSize = FontSize;
|
||
}
|
||
}
|
||
if(MostPopularFontSize) Item.FontSize.Base = Item.FontSize.Base * (S['base-font-size'] / MostPopularFontSize);
|
||
Item.changeFontSizeStep(FontSizeChanger.Step);
|
||
} else if(FontSizeChanger.Step != 0) {
|
||
Item.changeFontSizeStep(FontSizeChanger.Step);
|
||
}
|
||
});
|
||
FontSizeChanger.changeFontSizeStep = (Step, Actions) => {
|
||
if(S.BRL == 'pre-paginated') return;
|
||
if(Step == FontSizeChanger.Step) return;
|
||
if(!Actions) Actions = {};
|
||
E.dispatch('bibi:changes-font-size');
|
||
if(typeof Actions.before == 'function') Actions.before();
|
||
FontSizeChanger.Step = Step;
|
||
if(S['use-font-size-changer'] && S['keep-settings'] && O.Biscuits) {
|
||
O.Biscuits.memorize('Book', { FontSize: { Step: Step } });
|
||
}
|
||
setTimeout(() => {
|
||
R.layOutBook({
|
||
before: () => R.Items.forEach(Item => { if(Item.changeFontSizeStep) Item.changeFontSizeStep(Step); }),
|
||
Reset: true,
|
||
DoNotCloseUtilities: true,
|
||
NoNotification: true
|
||
}).then(() => {
|
||
E.dispatch('bibi:changed-font-size', { Step: Step });
|
||
if(typeof Actions.after == 'function') Actions.after();
|
||
});
|
||
}, 88);
|
||
};
|
||
//E.add('bibi:changes-font-size', () => E.dispatch('bibi:closes-utilities'));
|
||
//E.add('bibi:changes-view', () => FontSizeChanger.changeFontSizeStep(0)); // unnecessary
|
||
if(S['use-font-size-changer']) {
|
||
const changeFontSizeStep = function() {
|
||
const Button = this;
|
||
FontSizeChanger.changeFontSizeStep(Button.Step, {
|
||
before: () => Button.ButtonGroup.Busy = true,
|
||
after: () => Button.ButtonGroup.Busy = false
|
||
});
|
||
};
|
||
I.createSubpanel({
|
||
Opener: I.Menu.R.addButtonGroup({ Sticky: true, id: 'bibi-buttongroup_font-size' }).addButton({
|
||
Type: 'toggle',
|
||
Labels: {
|
||
default: { default: `Change Font Size`, ja: `文字サイズを変更` },
|
||
active: { default: `Close Font Size Menu`, ja: `文字サイズメニューを閉じる` }
|
||
},
|
||
//className: 'bibi-button-font-size bibi-button-font-size-change',
|
||
Icon: `<span class="bibi-icon bibi-icon-change-fontsize"></span>`,
|
||
Help: true
|
||
}),
|
||
id: 'bibi-subpanel_font-size',
|
||
open: () => {}
|
||
}).addSection({
|
||
Labels: { default: { default: `Choose Font Size`, ja: `文字サイズを選択` } }
|
||
}).addButtonGroup({
|
||
//Tiled: true,
|
||
ButtonType: 'radio',
|
||
Buttons: [{
|
||
Labels: { default: { default: `<span class="non-visual-in-label">Font Size:</span> Ex-Large`, ja: `<span class="non-visual-in-label">文字サイズ:</span>最大` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-fontsize bibi-icon-fontsize-exlarge"></span>`,
|
||
action: changeFontSizeStep, Step: 2
|
||
}, {
|
||
Labels: { default: { default: `<span class="non-visual-in-label">Font Size:</span> Large`, ja: `<span class="non-visual-in-label">文字サイズ:</span>大` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-fontsize bibi-icon-fontsize-large"></span>`,
|
||
action: changeFontSizeStep, Step: 1
|
||
}, {
|
||
Labels: { default: { default: `<span class="non-visual-in-label">Font Size:</span> Medium <small>(default)</small>`, ja: `<span class="non-visual-in-label">文字サイズ:</span>中<small>(初期値)</small>` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-fontsize bibi-icon-fontsize-medium"></span>`,
|
||
action: changeFontSizeStep, Step: 0
|
||
}, {
|
||
Labels: { default: { default: `<span class="non-visual-in-label">Font Size:</span> Small`, ja: `<span class="non-visual-in-label">文字サイズ:</span>小` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-fontsize bibi-icon-fontsize-small"></span>`,
|
||
action: changeFontSizeStep, Step: -1
|
||
}, {
|
||
Labels: { default: { default: `<span class="non-visual-in-label">Font Size:</span> Ex-Small`, ja: `<span class="non-visual-in-label">文字サイズ:</span>最小` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-fontsize bibi-icon-fontsize-exsmall"></span>`,
|
||
action: changeFontSizeStep, Step: -2
|
||
}]
|
||
}).Buttons.forEach(Button => { if(Button.Step == FontSizeChanger.Step) I.setUIState(Button, 'active'); });
|
||
}
|
||
E.dispatch('bibi:created-font-size-changer');
|
||
}};
|
||
|
||
|
||
I.Loupe = { create: () => {
|
||
if(S['loupe-max-scale'] <= 1) S['loupe-max-scale'] = 4.0;
|
||
if(S['loupe-scale-per-step'] <= 1) S['loupe-scale-per-step'] = 1.6;
|
||
if(S['loupe-scale-per-step'] > S['loupe-max-scale']) S['loupe-scale-per-step'] = S['loupe-max-scale'];
|
||
const Loupe = I.Loupe = {
|
||
CurrentTransformation: { Scale: 1, TranslateX: 0, TranslateY: 0 },
|
||
defineZoomOutPropertiesForUtilities: () => {
|
||
const BookMargin = {
|
||
Top: S['use-menubar'] && S['use-full-height'] ? I.Menu.Height : 0,
|
||
Right: S.ARA == 'vertical' ? I.Slider.Size : 0,
|
||
Bottom: S.ARA == 'horizontal' ? I.Slider.Size : 0,
|
||
Left: 0
|
||
};
|
||
const Tfm = {};
|
||
if(S.ARA == 'horizontal') {
|
||
Tfm.Scale = (R.Main.offsetHeight - (BookMargin.Top + BookMargin.Bottom)) / (R.Main.offsetHeight - (S.ARA == S.SLA && (S.RVM != 'paged' || I.Slider.ownerDocument) ? O.Scrollbars.Height : 0));
|
||
Tfm.TranslateX = 0;
|
||
} else {
|
||
Tfm.Scale = Math.min(
|
||
(R.Main.offsetWidth - BookMargin.Right) / (R.Main.offsetWidth - O.Scrollbars.Width),
|
||
(R.Main.offsetHeight - BookMargin.Top ) / R.Main.offsetHeight
|
||
);
|
||
Tfm.TranslateX = R.Main.offsetWidth * (1 - Tfm.Scale) / -2;
|
||
//OP.Left = Tfm.TranslateX * -1;
|
||
}
|
||
Tfm.TranslateY = BookMargin.Top - (R.Main.offsetHeight) * (1 - Tfm.Scale) / 2;
|
||
const St = (O.Body['offset' + C.A_SIZE_L] / Tfm.Scale - R.Main['offset' + C.A_SIZE_L]);
|
||
const OP = {};
|
||
OP[C.A_BASE_B] = St / 2 + (!S['use-full-height'] && S.ARA == 'vertical' ? I.Menu.Height : 0);
|
||
OP[C.A_BASE_A] = St / 2;
|
||
const IP = {};
|
||
if(S.ARA == S.SLA) IP[S.ARA == 'horizontal' ? 'Right' : 'Bottom'] = St / 2;
|
||
Loupe.ZoomOutPropertiesForUtilities = {
|
||
Transformation: Tfm,
|
||
Stretch: St,
|
||
OuterPadding: OP,
|
||
InnerPadding: IP
|
||
};
|
||
},
|
||
getNormalizedTransformation: (Tfm) => {
|
||
const NTfm = Object.assign({}, Loupe.CurrentTransformation);
|
||
if(Tfm) {
|
||
if(typeof Tfm.Scale == 'number') NTfm.Scale = Tfm.Scale;
|
||
if(typeof Tfm.TranslateX == 'number') NTfm.TranslateX = Tfm.TranslateX;
|
||
if(typeof Tfm.TranslateY == 'number') NTfm.TranslateY = Tfm.TranslateY;
|
||
}
|
||
return NTfm;
|
||
},
|
||
getActualTransformation: (Tfm) => {
|
||
const ATfm = Loupe.getNormalizedTransformation(Tfm);
|
||
if(ATfm.Scale == 1 && Loupe.IsZoomedOutForUtilities) {
|
||
const Tfm4U = Loupe.ZoomOutPropertiesForUtilities.Transformation;
|
||
ATfm.Scale *= Tfm4U.Scale;
|
||
ATfm.TranslateX += Tfm4U.TranslateX;
|
||
ATfm.TranslateY += Tfm4U.TranslateY;
|
||
}
|
||
return ATfm;
|
||
},
|
||
transform: (Tfm, Opt = {}) => new Promise((resolve, reject) => {
|
||
// Tfm: Transformation
|
||
Tfm = Loupe.getNormalizedTransformation(Tfm);
|
||
const CTfm = Loupe.CurrentTransformation;
|
||
//if(Tfm.Scale == CTfm.Scale && Tfm.TranslateX == CTfm.TranslateX && Tfm.TranslateY == CTfm.TranslateY) return resolve();
|
||
Loupe.Transforming = true;
|
||
clearTimeout(Loupe.Timer_onTransformEnd);
|
||
O.HTML.classList.add('transforming');
|
||
if(Tfm.Scale > 1) {
|
||
const OverflowX = window.innerWidth * (0.5 * (Tfm.Scale - 1)),
|
||
OverflowY = window.innerHeight * (0.5 * (Tfm.Scale - 1));
|
||
Tfm.TranslateX = sML.limitMinMax(Tfm.TranslateX, OverflowX * -1 - (S.RVM != 'vertical' ? 0 : I.Slider.UIState == 'active' ? I.Slider.Size - O.Scrollbars.Width : 0) + O.Scrollbars.Width , OverflowX);
|
||
Tfm.TranslateY = sML.limitMinMax(Tfm.TranslateY, OverflowY * -1 - (S.RVM == 'vertical' ? 0 : I.Slider.UIState == 'active' ? I.Slider.Size - O.Scrollbars.Height : 0) + O.Scrollbars.Height, OverflowY + (I.Menu.UIState == 'active' ? I.Menu.Height : 0));
|
||
}
|
||
Loupe.CurrentTransformation = Tfm;
|
||
const ATfm = Loupe.getActualTransformation(Tfm);
|
||
sML.style(R.Main, {
|
||
transform: (Ps => {
|
||
if(ATfm.TranslateX && ATfm.TranslateY) Ps.push( 'translate(' + ATfm.TranslateX + 'px' + ', ' + ATfm.TranslateY + 'px' + ')');
|
||
else if(ATfm.TranslateX ) Ps.push('translateX(' + ATfm.TranslateX + 'px' + ')');
|
||
else if( ATfm.TranslateY) Ps.push('translateY(' + ATfm.TranslateY + 'px' + ')');
|
||
if(ATfm.Scale != 1 ) Ps.push( 'scale(' + ATfm.Scale + ')');
|
||
return Ps.length ? Ps.join(' ') : '';
|
||
})([])
|
||
});
|
||
Loupe.Timer_onTransformEnd = setTimeout(() => {
|
||
if(Loupe.CurrentTransformation.Scale == 1) O.HTML.classList.remove('zoomed-in'), O.HTML.classList.remove('zoomed-out');
|
||
else if(Loupe.CurrentTransformation.Scale < 1) O.HTML.classList.remove('zoomed-in'), O.HTML.classList.add( 'zoomed-out');
|
||
else O.HTML.classList.add( 'zoomed-in'), O.HTML.classList.remove('zoomed-out');
|
||
O.HTML.classList.remove('transforming');
|
||
Loupe.Transforming = false;
|
||
resolve();
|
||
E.dispatch('bibi:transformed-book', {
|
||
Transformation: Tfm,
|
||
ActualTransformation: ATfm,
|
||
Temporary: Opt.Temporary
|
||
});
|
||
}, 345);
|
||
}),
|
||
scale: (Scl, Opt = {}) => { // Scl: Scale
|
||
Scl = typeof Scl == 'number' ? sML.limitMinMax(Scl, 1, S['loupe-max-scale']) : 1;
|
||
if(!Opt.Stepless) Scl = Math.round(Scl * 100) / 100;
|
||
const CTfm = Loupe.CurrentTransformation;
|
||
if(Scl == CTfm.Scale) return Promise.resolve();
|
||
E.dispatch('bibi:changes-scale', Scl);
|
||
let TX = 0, TY = 0;
|
||
if(Scl < 1) {
|
||
TX = R.Main.offsetWidth * (1 - Scl) / 2;
|
||
TY = R.Main.offsetHeight * (1 - Scl) / 2;
|
||
} else if(Scl > 1) {
|
||
if(Loupe.UIState != 'active') return Promise.resolve();
|
||
if(!Opt.Center) Opt.Center = { X: window.innerWidth / 2, Y: window.innerHeight / 2 };
|
||
TX = CTfm.TranslateX + (Opt.Center.X - window.innerWidth / 2 - CTfm.TranslateX) * (1 - Scl / CTfm.Scale);
|
||
TY = CTfm.TranslateY + (Opt.Center.Y - window.innerHeight / 2 - CTfm.TranslateY) * (1 - Scl / CTfm.Scale);
|
||
/* ↑↑↑↑ SIMPLIFIED ↑↑↑↑
|
||
const CTfmOriginX = window.innerWidth / 2 + CTfm.TranslateX; TX = CTfm.TranslateX + (Opt.Center.X - (CTfmOriginX + (Opt.Center.X - CTfmOriginX) * (Scl / CTfm.Scale)));
|
||
const CTfmOriginY = window.innerHeight / 2 + CTfm.TranslateY; TY = CTfm.TranslateY + (Opt.Center.Y - (CTfmOriginY + (Opt.Center.Y - CTfmOriginY) * (Scl / CTfm.Scale)));
|
||
//*/
|
||
}
|
||
return Loupe.transform({
|
||
Scale: Scl,
|
||
TranslateX: TX,
|
||
TranslateY: TY
|
||
});
|
||
},
|
||
BookStretchingEach: 0,
|
||
transformToDefault: () => Loupe.transform({ Scale: 1, TranslateX: 0, TranslateY: 0 }),
|
||
transformForUtilities: (IO) => {
|
||
if(!Loupe.isAvailable()) return Promise.resolve();
|
||
let cb = () => {};
|
||
if(IO) {
|
||
if(Loupe.IsZoomedOutForUtilities) return Promise.resolve();
|
||
Loupe.IsZoomedOutForUtilities = true;
|
||
const OP4U = Loupe.ZoomOutPropertiesForUtilities.OuterPadding, IP4U = Loupe.ZoomOutPropertiesForUtilities.InnerPadding;
|
||
for(const Dir in OP4U) R.Main.style[ 'padding' + Dir] = OP4U[Dir] + 'px';
|
||
for(const Dir in IP4U) R.Main.Book.style['padding' + Dir] = IP4U[Dir] + 'px';
|
||
Loupe.BookStretchingEach = Loupe.ZoomOutPropertiesForUtilities.Stretch / 2;
|
||
} else {
|
||
if(!Loupe.IsZoomedOutForUtilities) return Promise.resolve();
|
||
Loupe.IsZoomedOutForUtilities = false;
|
||
cb = () => {
|
||
R.Main.style.padding = R.Main.Book.style.padding = '';
|
||
Loupe.BookStretchingEach = 0;
|
||
};
|
||
}
|
||
return Loupe.transform(null, { Temporary: true }).then(cb).then(() => I.Slider.ownerDocument ? I.Slider.progress() : undefined);
|
||
},
|
||
isAvailable: () => {
|
||
if(!L.Opened) return false;
|
||
if(Loupe.UIState != 'active') return false;
|
||
//if(S.BRL == 'reflowable') return false;
|
||
return true;
|
||
},
|
||
checkAndGetBibiEventForTaps: (Eve) => {
|
||
if(!Eve || !Loupe.isAvailable()) return null;
|
||
const BibiEvent = O.getBibiEvent(Eve);
|
||
if(BibiEvent.Target.tagName) {
|
||
if(/bibi-menu|bibi-slider/.test(BibiEvent.Target.id)) return null;
|
||
if(O.isPointableContent(BibiEvent.Target)) return null;
|
||
if(S.RVM == 'horizontal' && BibiEvent.Coord.Y > window.innerHeight - O.Scrollbars.Height) return null;
|
||
}
|
||
return BibiEvent;
|
||
},
|
||
onTap: (Eve, HowManyTaps) => {
|
||
if(HowManyTaps == 1 && (!I.KeyObserver.ActiveKeys || !I.KeyObserver.ActiveKeys['Space'])) return Promise.resolve(); // Requires pressing space-key on single-tap.
|
||
const BibiEvent = Loupe.checkAndGetBibiEventForTaps(Eve);
|
||
if(!BibiEvent) return Promise.resolve();
|
||
//if(HowManyTaps > 1 && (BibiEvent.Division.X != 'center' || BibiEvent.Division.Y != 'middle')) return Promise.resolve();
|
||
Eve.preventDefault();
|
||
try { Eve.target.ownerDocument.body.Item.contentWindow.getSelection().empty(); } catch(Err) {}
|
||
if(Loupe.CurrentTransformation.Scale >= S['loupe-max-scale'] && !Eve.shiftKey) return Loupe.scale(1);
|
||
return Loupe.scale(Loupe.CurrentTransformation.Scale * (Eve.shiftKey ? 1 / S['loupe-scale-per-step'] : S['loupe-scale-per-step']), { Center: BibiEvent.Coord });
|
||
},
|
||
onPointerDown: (Eve) => {
|
||
Loupe.PointerDownCoord = O.getBibiEvent(Eve).Coord;
|
||
Loupe.PointerDownTransformation = {
|
||
Scale: Loupe.CurrentTransformation.Scale,
|
||
TranslateX: Loupe.CurrentTransformation.TranslateX,
|
||
TranslateY: Loupe.CurrentTransformation.TranslateY
|
||
};
|
||
},
|
||
onPointerUp: (Eve) => {
|
||
O.HTML.classList.remove('dragging');
|
||
Loupe.Dragging = false;
|
||
delete Loupe.PointerDownCoord;
|
||
delete Loupe.PointerDownTransformation;
|
||
},
|
||
onPointerMove: (Eve) => {
|
||
if(I.PinchObserver.Hot) return false;
|
||
if(!Loupe.isAvailable()) return false;
|
||
if(Loupe.CurrentTransformation.Scale == 1 || !Loupe.PointerDownCoord) return false;
|
||
Eve.preventDefault();
|
||
Loupe.Dragging = true;
|
||
O.HTML.classList.add('dragging');
|
||
const BibiEvent = O.getBibiEvent(Eve);
|
||
clearTimeout(Loupe.Timer_TransitionRestore);
|
||
sML.style(R.Main, { transition: 'none' }, { cursor: 'move' });
|
||
Loupe.transform({
|
||
Scale: Loupe.CurrentTransformation.Scale,
|
||
TranslateX: Loupe.PointerDownTransformation.TranslateX + (BibiEvent.Coord.X - Loupe.PointerDownCoord.X),
|
||
TranslateY: Loupe.PointerDownTransformation.TranslateY + (BibiEvent.Coord.Y - Loupe.PointerDownCoord.Y)
|
||
});
|
||
Loupe.Timer_TransitionRestore = setTimeout(() => sML.style(R.Main, { transition: '' }, { cursor: '' }), 234);
|
||
}
|
||
};
|
||
I.isPointerStealth.addChecker(() => {
|
||
if(Loupe.Dragging) return true;
|
||
if(!I.KeyObserver.ActiveKeys || !I.KeyObserver.ActiveKeys['Space']) return false;
|
||
return true;
|
||
});
|
||
I.setToggleAction(Loupe, {
|
||
onopened: () => {
|
||
//Loupe.defineZoomOutPropertiesForUtilities();
|
||
O.HTML.classList.add('loupe-active');
|
||
},
|
||
onclosed: () => {
|
||
Loupe.transformToDefault();
|
||
O.HTML.classList.remove('loupe-active');
|
||
}
|
||
});
|
||
E.add('bibi:commands:activate-loupe', ( ) => Loupe.open());
|
||
E.add('bibi:commands:deactivate-loupe', ( ) => Loupe.close());
|
||
E.add('bibi:commands:toggle-loupe', ( ) => Loupe.toggle());
|
||
E.add('bibi:commands:scale', Scale => Loupe.scale(Scale));
|
||
E.add('bibi:tapped', Eve => Loupe.onTap(Eve, 1));
|
||
if(S['on-doubletap'] == 'zoom') E.add('bibi:doubletapped', Eve => Loupe.onTap(Eve, 2));
|
||
if(S['on-tripletap'] == 'zoom') E.add('bibi:tripletapped', Eve => Loupe.onTap(Eve, 3));
|
||
E.add('bibi:downed-pointer', Eve => Loupe.onPointerDown(Eve));
|
||
E.add('bibi:upped-pointer', Eve => Loupe.onPointerUp(Eve));
|
||
E.add('bibi:moved-pointer', Eve => Loupe.onPointerMove(Eve));
|
||
if(S['zoom-out-for-utilities']) {
|
||
E.add('bibi:opens-utilities', () => Loupe.transformForUtilities(true ));
|
||
E.add('bibi:closes-utilities', () => Loupe.transformForUtilities(false));
|
||
}
|
||
E.add('bibi:opened', () => Loupe.open());
|
||
E.add('bibi:laid-out', () => Loupe.defineZoomOutPropertiesForUtilities());
|
||
E.add('bibi:changed-view', () => Loupe.transformToDefault());
|
||
if(S['use-loupe']) {
|
||
const ButtonGroup = I.Menu.R.addButtonGroup({
|
||
Sticky: true,
|
||
Tiled: true,
|
||
id: 'bibi-buttongroup_loupe',
|
||
Buttons: [{
|
||
Labels: { default: { default: `Zoom-in`, ja: `拡大する` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-loupe bibi-icon-loupe-zoomin"></span>`,
|
||
Help: true,
|
||
action: () => Loupe.scale(Loupe.CurrentTransformation.Scale * S['loupe-scale-per-step']),
|
||
updateState: function(State) { I.setUIState(this, typeof State == 'string' ? State : (Loupe.CurrentTransformation.Scale >= S['loupe-max-scale']) ? 'disabled' : 'default'); }
|
||
}, {
|
||
Labels: { default: { default: `Reset Zoom-in/out`, ja: `元のサイズに戻す` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-loupe bibi-icon-loupe-reset"></span>`,
|
||
Help: true,
|
||
action: () => Loupe.scale(1),
|
||
updateState: function(State) { I.setUIState(this, typeof State == 'string' ? State : (Loupe.CurrentTransformation.Scale == 1) ? 'disabled' : 'default'); }
|
||
}, {
|
||
Labels: { default: { default: `Zoom-out`, ja: `縮小する` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-loupe bibi-icon-loupe-zoomout"></span>`,
|
||
Help: true,
|
||
action: () => Loupe.scale(Loupe.CurrentTransformation.Scale / S['loupe-scale-per-step']),
|
||
updateState: function(State) { I.setUIState(this, typeof State == 'string' ? State : (Loupe.CurrentTransformation.Scale <= 1) ? 'disabled' : 'default'); }
|
||
}]
|
||
});
|
||
Loupe.updateButtonState = (State) => ButtonGroup.Buttons.forEach(Button => Button.updateState(State));
|
||
E.add('bibi:opened', () => Loupe.updateButtonState());
|
||
E.add('bibi:transformed-book', () => Loupe.updateButtonState());
|
||
}
|
||
E.dispatch('bibi:created-loupe');
|
||
}};
|
||
|
||
|
||
I.Nombre = { create: () => { if(!S['use-nombre']) return;
|
||
const Nombre = I.Nombre = O.Body.appendChild(sML.create('div', { id: 'bibi-nombre',
|
||
clearTimers: () => {
|
||
clearTimeout(Nombre.Timer_hot);
|
||
clearTimeout(Nombre.Timer_vanish);
|
||
clearTimeout(Nombre.Timer_autohide);
|
||
},
|
||
show: () => {
|
||
Nombre.clearTimers();
|
||
Nombre.classList.add('active');
|
||
Nombre.Timer_hot = setTimeout(() => Nombre.classList.add('hot'), 10);
|
||
},
|
||
hide: () => {
|
||
Nombre.clearTimers();
|
||
Nombre.classList.remove('hot');
|
||
Nombre.Timer_vanish = setTimeout(() => Nombre.classList.remove('active'), 255);
|
||
},
|
||
progress: (PageInfo) => {
|
||
Nombre.clearTimers();
|
||
if(!PageInfo) PageInfo = I.PageObserver.Current;
|
||
if(!PageInfo.List.length) return; ////////
|
||
const StartPageNumber = PageInfo.List[ 0].Page.Index + 1;
|
||
const EndPageNumber = PageInfo.List.slice(-1)[0].Page.Index + 1;
|
||
const Percent = Math.floor((EndPageNumber) / R.Pages.length * 100);
|
||
Nombre.Current.innerHTML = (() => {
|
||
let PageNumber = StartPageNumber; if(StartPageNumber != EndPageNumber) PageNumber += `<span class="delimiter">-</span>` + EndPageNumber;
|
||
return PageNumber;
|
||
})();
|
||
Nombre.Delimiter.innerHTML = `/`;
|
||
Nombre.Total.innerHTML = R.Pages.length;
|
||
Nombre.Percent.innerHTML = `(${ Percent }<span class="unit">%</span>)`;
|
||
Nombre.show();
|
||
if(I.Slider.UIState != 'active') Nombre.Timer_autohide = setTimeout(Nombre.hide, 1234);
|
||
}
|
||
}));
|
||
Nombre.Current = Nombre.appendChild(sML.create('span', { className: 'bibi-nombre-current' }));
|
||
Nombre.Delimiter = Nombre.appendChild(sML.create('span', { className: 'bibi-nombre-delimiter' }));
|
||
Nombre.Total = Nombre.appendChild(sML.create('span', { className: 'bibi-nombre-total' }));
|
||
Nombre.Percent = Nombre.appendChild(sML.create('span', { className: 'bibi-nombre-percent' }));
|
||
E.add('bibi:opened' , () => setTimeout(() => {
|
||
Nombre.progress();
|
||
E.add(['bibi:is-scrolling', 'bibi:scrolled', 'bibi:opened-slider'], () => Nombre.progress());
|
||
E.add('bibi:closed-slider', Nombre.hide);
|
||
}, 321));
|
||
sML.appendCSSRule('html.view-paged div#bibi-nombre', 'bottom: ' + (O.Scrollbars.Height + 2) + 'px;');
|
||
sML.appendCSSRule('html.view-horizontal div#bibi-nombre', 'bottom: ' + (O.Scrollbars.Height + 2) + 'px;');
|
||
sML.appendCSSRule('html.view-vertical div#bibi-nombre', 'right: ' + (O.Scrollbars.Height + 2) + 'px;');
|
||
E.dispatch('bibi:created-nombre');
|
||
}};
|
||
|
||
|
||
I.History = {
|
||
List: [], Updaters: [],
|
||
update: () => I.History.Updaters.forEach(fun => fun()),
|
||
add: (Opt = {}) => {
|
||
if(!Opt.UI) Opt.UI = Bibi;
|
||
const LastPage = R.hatchPage(I.History.List.slice(-1)[0]),
|
||
CurrentPage = Opt.Destination ? R.hatchPage(Opt.Destination) : (() => { I.PageObserver.updateCurrent(); return I.PageObserver.Current.List[0].Page; })();
|
||
if(CurrentPage != LastPage) {
|
||
if(Opt.SumUp && I.History.List.slice(-1)[0].UI == Opt.UI) I.History.List.pop();
|
||
I.History.List.push({ UI: Opt.UI, IIPP: I.PageObserver.getIIPP({ Page: CurrentPage }) });
|
||
if(I.History.List.length - 1 > S['max-history']) { // Not count the first (oldest).
|
||
const First = I.History.List.shift(); // The first (oldest) is the landing point.
|
||
I.History.List.shift(); // Remove the second
|
||
I.History.List.unshift(First); // Restore the first (oldest).
|
||
}
|
||
}
|
||
I.History.update();
|
||
},
|
||
back: () => {
|
||
if(I.History.List.length <= 1) return Promise.reject();
|
||
const CurrentPage = R.hatchPage(I.History.List.pop()),
|
||
LastPage = R.hatchPage(I.History.List.slice(-1)[0]);
|
||
I.History.update();
|
||
return R.focusOn({ Destination: LastPage, Duration: 0 });
|
||
}
|
||
};
|
||
|
||
|
||
I.Slider = { create: () => {
|
||
if(!S['use-slider']) return false;
|
||
const Slider = I.Slider = O.Body.appendChild(sML.create('div', { id: 'bibi-slider',
|
||
RailProgressMode: 'end', // or 'center'
|
||
Size: I.Slider.Size,
|
||
initialize: () => {
|
||
const EdgebarBox = Slider.appendChild(sML.create('div', { id: 'bibi-slider-edgebar-box' }));
|
||
Slider.Edgebar = EdgebarBox.appendChild(sML.create('div', { id: 'bibi-slider-edgebar' }));
|
||
Slider.Rail = EdgebarBox.appendChild(sML.create('div', { id: 'bibi-slider-rail' }));
|
||
Slider.RailGroove = Slider.Rail.appendChild(sML.create('div', { id: 'bibi-slider-rail-groove' }));
|
||
Slider.RailProgress = Slider.RailGroove.appendChild(sML.create('div', { id: 'bibi-slider-rail-progress' }));
|
||
Slider.Thumb = EdgebarBox.appendChild(sML.create('div', { id: 'bibi-slider-thumb', Labels: { default: { default: `Slider Thumb`, ja: `スライダー上の好きな位置からドラッグを始められます` } } })); I.setFeedback(Slider.Thumb);
|
||
if(S['use-history']) {
|
||
Slider.classList.add('bibi-slider-with-history');
|
||
Slider.History = Slider.appendChild(sML.create('div', { id: 'bibi-slider-history' }));
|
||
Slider.History.add = (Destination) => I.History.add({ UI: Slider, SumUp: false, Destination: Destination })
|
||
Slider.History.Button = Slider.History.appendChild(I.createButtonGroup()).addButton({ id: 'bibi-slider-history-button',
|
||
Type: 'normal',
|
||
Labels: { default: { default: `History Back`, ja: `移動履歴を戻る` } },
|
||
Help: false,
|
||
Icon: `<span class="bibi-icon bibi-icon-history"></span>`,
|
||
action: () => I.History.back(),
|
||
update: function() {
|
||
this.Icon.style.transform = `rotate(${ 360 * (I.History.List.length - 1) }deg)`;
|
||
if(I.History.List.length <= 1) I.setUIState(this, 'disabled');
|
||
else if(this.UIState == 'disabled') I.setUIState(this, 'default');
|
||
}
|
||
});
|
||
I.History.Updaters.push(() => Slider.History.Button.update());
|
||
}
|
||
if(S['use-nombre']) {
|
||
E.add(Slider.Edgebar, ['mouseover', 'mousemove'], Eve => { if(!Slider.Touching) I.Nombre.progress({ List: [{ Page: Slider.getPointedPage(O.getBibiEventCoord(Eve)[C.A_AXIS_L]) }] }); });
|
||
E.add(Slider.Edgebar, 'mouseout', Eve => { if(!Slider.Touching) I.Nombre.progress(); });
|
||
}
|
||
},
|
||
resetUISize: () => {
|
||
Slider.MainLength = R.Main['scroll' + C.L_SIZE_L];
|
||
const ThumbLengthPercent = R.Main['offset' + C.L_SIZE_L] / Slider.MainLength * 100;
|
||
Slider.RailGroove.style[C.A_SIZE_b] = Slider.Thumb.style[C.A_SIZE_b] = '';
|
||
Slider.RailGroove.style[C.A_SIZE_l] = (100 - (Slider.RailProgressMode == 'center' ? ThumbLengthPercent : 0)) + '%';
|
||
Slider.Thumb.style[C.A_SIZE_l] = ThumbLengthPercent + '%';
|
||
setTimeout(() => Slider.Thumb.classList.toggle('min', (STACS => STACS.width == STACS.height)(getComputedStyle(Slider.Thumb, '::after'))), 0);
|
||
Slider.Edgebar.Before = O.getElementCoord(Slider.Edgebar)[C.A_AXIS_L];
|
||
Slider.Edgebar.Length = Slider.Edgebar['offset' + C.A_SIZE_L];
|
||
Slider.Edgebar.After = Slider.Edgebar.Before + Slider.Edgebar.Length;
|
||
Slider.RailGroove.Before = O.getElementCoord(Slider.RailGroove)[C.A_AXIS_L];
|
||
Slider.RailGroove.Length = Slider.RailGroove['offset' + C.A_SIZE_L];
|
||
Slider.RailGroove.After = Slider.RailGroove.Before + Slider.RailGroove.Length;
|
||
Slider.Thumb.Length = Slider.Thumb['offset' + C.A_SIZE_L];
|
||
},
|
||
onTouchStart: (Eve) => {
|
||
I.ScrollObserver.forceStopScrolling();
|
||
Eve.preventDefault();
|
||
Slider.Touching = true;
|
||
Slider.StartedAt = {
|
||
ThumbBefore: O.getElementCoord(Slider.Thumb)[C.A_AXIS_L],
|
||
RailProgressLength: Slider.RailProgress['offset' + C.A_SIZE_L],
|
||
MainScrollBefore: Math.ceil(R.Main['scroll' + C.L_OOBL_L]) // Android Chrome returns scrollLeft/Top value of an element with slightly less float than actual.
|
||
};
|
||
Slider.StartedAt.Coord = Eve.target == Slider.Thumb ? O.getBibiEventCoord(Eve)[C.A_AXIS_L] : Slider.StartedAt.ThumbBefore + Slider.Thumb.Length / 2; // ← ? <Move Thumb naturally> : <Bring Thumb's center to the touched coord at the next pointer moving>
|
||
clearTimeout(Slider.Timer_onTouchEnd);
|
||
O.HTML.classList.add('slider-sliding');
|
||
E.add('bibi:moved-pointer', Slider.onTouchMove);
|
||
},
|
||
onTouchMove: (Eve) => {
|
||
clearTimeout(Slider.Timer_move);
|
||
const TouchMoveCoord = O.getBibiEventCoord(Eve)[C.A_AXIS_L];
|
||
if(Slider.Touching) {
|
||
const Translation = sML.limitMinMax(TouchMoveCoord - Slider.StartedAt.Coord,
|
||
Slider.Edgebar.Before - Slider.StartedAt.ThumbBefore,
|
||
Slider.Edgebar.After - (Slider.StartedAt.ThumbBefore + Slider.Thumb.Length)
|
||
);
|
||
sML.style(Slider.Thumb, { transform: 'translate' + C.A_AXIS_L + '(' + Translation + 'px)' });
|
||
sML.style(Slider.RailProgress, { [C.A_SIZE_l]: (Slider.StartedAt.RailProgressLength + Translation * (S.ARD == 'rtl' ? -1 : 1)) + 'px' });
|
||
}
|
||
Slider.flipPagesDuringSliding(TouchMoveCoord);
|
||
},
|
||
flipPagesDuringSliding: (_) => (Slider.flipPagesDuringSliding = !S['flip-pages-during-sliding'] ? // switch and define at the first call.
|
||
() => false :
|
||
(TouchMoveCoord) => {
|
||
R.DoNotTurn = true; Slider.flip(TouchMoveCoord);
|
||
Slider.Timer_move = setTimeout(() => { R.DoNotTurn = false; Slider.flip(TouchMoveCoord, 'FORCE'); }, 333);
|
||
}
|
||
)(_),
|
||
onTouchEnd: (Eve) => {
|
||
if(!Slider.Touching) return;
|
||
clearTimeout(Slider.Timer_move);
|
||
Slider.Touching = false;
|
||
E.remove('bibi:moved-pointer', Slider.onTouchMove);
|
||
const TouchEndCoord = O.getBibiEventCoord(Eve)[C.A_AXIS_L];
|
||
if(TouchEndCoord == Slider.StartedAt.Coord) Slider.StartedAt.Coord = Slider.StartedAt.ThumbBefore + Slider.Thumb.Length / 2;
|
||
R.DoNotTurn = false;
|
||
Slider.flip(TouchEndCoord, 'FORCE').then(() => {
|
||
sML.style(Slider.Thumb, { transform: '' });
|
||
sML.style(Slider.RailProgress, { [C.A_SIZE_l]: '' });
|
||
Slider.progress();
|
||
if(Slider.History) Slider.History.add();
|
||
});
|
||
delete Slider.StartedAt;
|
||
Slider.Timer_onTouchEnd = setTimeout(() => O.HTML.classList.remove('slider-sliding'), 123);
|
||
},
|
||
flip: (TouchedCoord, Force) => new Promise(resolve => { switch(S.RVM) {
|
||
case 'paged':
|
||
const TargetPage = Slider.getPointedPage(TouchedCoord);
|
||
return I.PageObserver.Current.Pages.includes(TargetPage) ? resolve() : R.focusOn({ Destination: TargetPage, Duration: 0 }).then(() => resolve());
|
||
default:
|
||
R.Main['scroll' + C.L_OOBL_L] = Slider.StartedAt.MainScrollBefore + (TouchedCoord - Slider.StartedAt.Coord) * (Slider.MainLength / Slider.Edgebar.Length);
|
||
return resolve();
|
||
} }).then(() => {
|
||
if(Force) I.PageObserver.turnItems();
|
||
}),
|
||
progress: () => {
|
||
if(Slider.Touching) return;
|
||
let MainScrollBefore = Math.ceil(R.Main['scroll' + C.L_OOBL_L]); // Android Chrome returns scrollLeft/Top value of an element with slightly less float than actual.
|
||
if(S.ARA != S.SLA && S.ARD == 'rtl') MainScrollBefore = Slider.MainLength - MainScrollBefore - R.Main.offsetHeight; // <- Paged (HorizontalAppearance) && VerticalText
|
||
switch(S.ARA) {
|
||
case 'horizontal': Slider.Thumb.style.top = '', Slider.RailProgress.style.height = ''; break;
|
||
case 'vertical': Slider.Thumb.style.left = '', Slider.RailProgress.style.width = ''; break;
|
||
}
|
||
Slider.Thumb.style[C.A_OOBL_l] = (MainScrollBefore / Slider.MainLength * 100) + '%';
|
||
Slider.RailProgress.style[C.A_SIZE_l] = Slider.getRailProgressLength(O.getElementCoord(Slider.Thumb)[C.A_AXIS_L] - Slider.RailGroove.Before) / Slider.RailGroove.Length * 100 + '%';
|
||
},
|
||
getRailProgressLength: (_) => (Slider.getRailProgressLength = Slider.RailProgressMode == 'center' ? // switch and define at the first call.
|
||
(ThumbBeforeInRailGroove) => S.ARD != 'rtl' ? ThumbBeforeInRailGroove + Slider.Thumb.Length / 2 : Slider.RailGroove.Length - (ThumbBeforeInRailGroove + Slider.Thumb.Length / 2) :
|
||
(ThumbBeforeInRailGroove) => S.ARD != 'rtl' ? ThumbBeforeInRailGroove + Slider.Thumb.Length : Slider.RailGroove.Length - ThumbBeforeInRailGroove
|
||
)(_),
|
||
getPointedPage: (PointedCoord) => {
|
||
let RatioInSlider = (PointedCoord - Slider.Edgebar.Before) / Slider.Edgebar['offset' + C.A_SIZE_L];
|
||
const OriginPageIndex = sML.limitMinMax(Math.round(R.Pages.length * (S.ARD == 'rtl' ? 1 - RatioInSlider : RatioInSlider)), 0, R.Pages.length - 1);
|
||
const PointedCoordInBook = R.Main['scroll' + C.L_SIZE_L] * (S.ARD == 'rtl' && S.SLD == 'ttb' ? 1 - RatioInSlider : RatioInSlider);
|
||
let ThePage = R.Pages[OriginPageIndex], MinDist = Slider.getPageDistanceFromPoint(ThePage, PointedCoordInBook);
|
||
[-1, 1].forEach(PM => { for(let i = OriginPageIndex + PM; R.Pages[i]; i += PM) {
|
||
const Page = R.Pages[i], Dist = Slider.getPageDistanceFromPoint(Page, PointedCoordInBook);
|
||
if(Dist < MinDist) ThePage = Page, MinDist = Dist; else break;
|
||
} });
|
||
return ThePage;
|
||
},
|
||
getPageDistanceFromPoint: (Page, PointedCoordInBook) => {
|
||
return Math.abs(PointedCoordInBook - (O.getElementCoord(Page, R.Main)[C.L_AXIS_L] + Page['offset' + C.L_SIZE_L] * 0.5));
|
||
}
|
||
}));
|
||
Slider.initialize();
|
||
I.setToggleAction(Slider, {
|
||
onopened: () => {
|
||
O.HTML.classList.add('slider-opened');
|
||
setTimeout(Slider.resetUISize, 0);
|
||
E.dispatch('bibi:opened-slider');
|
||
},
|
||
onclosed: () => {
|
||
new Promise(resolve => setTimeout(resolve, S['zoom-out-for-utilities'] ? 111 : 0));
|
||
O.HTML.classList.remove('slider-opened');
|
||
setTimeout(Slider.resetUISize, 0);
|
||
E.dispatch('bibi:closed-slider');
|
||
}
|
||
});
|
||
E.add('bibi:commands:open-slider', Slider.open);
|
||
E.add('bibi:commands:close-slider', Slider.close);
|
||
E.add('bibi:commands:toggle-slider', Slider.toggle);
|
||
E.add('bibi:opens-utilities', Opt => E.dispatch('bibi:commands:open-slider', Opt));
|
||
E.add('bibi:closes-utilities', Opt => E.dispatch('bibi:commands:close-slider', Opt));
|
||
E.add('bibi:loaded-item', Item => Item.HTML.addEventListener(E['pointerup'], Slider.onTouchEnd));
|
||
E.add('bibi:opened', () => {
|
||
Slider.Edgebar.addEventListener(E['pointerdown'], Slider.onTouchStart);
|
||
Slider.Thumb.addEventListener(E['pointerdown'], Slider.onTouchStart);
|
||
O.HTML.addEventListener(E['pointerup'], Slider.onTouchEnd);
|
||
if(Slider.History) Slider.History.Button.addEventListener(E['pointerup'], Slider.onTouchEnd);
|
||
E.add(['bibi:is-scrolling', 'bibi:scrolled'], Slider.progress);
|
||
Slider.progress();
|
||
});
|
||
E.add(['bibi:opened-slider', 'bibi:closed-slider', 'bibi:laid-out'], () => {
|
||
Slider.resetUISize();
|
||
Slider.progress();
|
||
});
|
||
{ // Optimize to Scrollbar Size
|
||
const _S = 'div#bibi-slider', _TB = '-thumb:before';
|
||
const _HS = 'html.appearance-horizontal ' + _S, _HSTB = _HS + _TB, _SH = O.Scrollbars.Height, _STH = Math.ceil(_SH / 2);
|
||
const _VS = 'html.appearance-vertical ' + _S, _VSTB = _VS + _TB, _SW = O.Scrollbars.Width, _STW = Math.ceil(_SW / 2);
|
||
const _getSliderThumbOffsetStyle = (Offset) => ['top', 'right', 'bottom', 'left'].reduce((Style, Dir) => Style + Dir + ': ' + (Offset * -1) + 'px; ', '').trim();
|
||
sML.appendCSSRule(_HS, 'height: ' + _SH + 'px;'); sML.appendCSSRule(_HSTB, _getSliderThumbOffsetStyle(_STH) + ' border-radius: ' + (_STH / 2) + 'px; min-width: ' + _STH + 'px;');
|
||
sML.appendCSSRule(_VS, 'width: ' + _SW + 'px;'); sML.appendCSSRule(_VSTB, _getSliderThumbOffsetStyle(_STW) + ' border-radius: ' + (_STW / 2) + 'px; min-height: ' + _STW + 'px;');
|
||
}
|
||
E.dispatch('bibi:created-slider');
|
||
}};
|
||
|
||
|
||
I.BookmarkManager = { create: () => { if(!S['use-bookmarks'] || !O.Biscuits) return;
|
||
const BookmarkManager = I.BookmarkManager = {
|
||
Bookmarks: [],
|
||
initialize: () => {
|
||
BookmarkManager.Subpanel = I.createSubpanel({
|
||
Opener: I.Menu.L.addButtonGroup({ Sticky: true, id: 'bibi-buttongroup_bookmarks' }).addButton({
|
||
Type: 'toggle',
|
||
Labels: {
|
||
default: { default: `Manage Bookmarks`, ja: `しおりメニューを開く` },
|
||
active: { default: `Close Bookmarks Menu`, ja: `しおりメニューを閉じる` }
|
||
},
|
||
Icon: `<span class="bibi-icon bibi-icon-manage-bookmarks"></span>`,
|
||
Help: true
|
||
}),
|
||
Position: 'left',
|
||
id: 'bibi-subpanel_bookmarks',
|
||
updateBookmarks: () => BookmarkManager.update(),
|
||
onopened: () => { E.add( 'bibi:scrolled', BookmarkManager.Subpanel.updateBookmarks); BookmarkManager.Subpanel.updateBookmarks (); },
|
||
onclosed: () => { E.remove('bibi:scrolled', BookmarkManager.Subpanel.updateBookmarks); }
|
||
});
|
||
BookmarkManager.ButtonGroup = BookmarkManager.Subpanel.addSection({
|
||
id: 'bibi-subpanel-section_bookmarks',
|
||
Labels: { default: { default: `Bookmarks`, ja: `しおり` } }
|
||
}).addButtonGroup();
|
||
const BookmarkBiscuits = O.Biscuits.remember('Book', 'Bookmarks');
|
||
if(Array.isArray(BookmarkBiscuits) && BookmarkBiscuits.length) BookmarkManager.Bookmarks = BookmarkBiscuits;
|
||
//E.add(['bibi:opened', 'bibi:resized'], () => BookmarkManager.update());
|
||
delete BookmarkManager.initialize;
|
||
},
|
||
exists: (Bookmark) => {
|
||
for(let l = BookmarkManager.Bookmarks.length, i = 0; i < l; i++) if(BookmarkManager.Bookmarks[i].IIPP == Bookmark.IIPP) return BookmarkManager.Bookmarks[i];
|
||
return null;
|
||
},
|
||
add: (Bookmark) => {
|
||
if(BookmarkManager.exists(Bookmark)) return BookmarkManager.update();
|
||
Bookmark.IsHot = true;
|
||
BookmarkManager.Bookmarks.push(Bookmark);
|
||
BookmarkManager.update({ Added: Bookmark });
|
||
},
|
||
remove: (Bookmark) => {
|
||
BookmarkManager.Bookmarks = BookmarkManager.Bookmarks.filter(Bmk => Bmk.IIPP != Bookmark.IIPP);
|
||
BookmarkManager.update({ Removed: Bookmark });
|
||
},
|
||
update: (Opt = {}) => {
|
||
BookmarkManager.Subpanel.Opener.ButtonGroup.style.display = '';
|
||
if(BookmarkManager.ButtonGroup.Buttons) {
|
||
BookmarkManager.ButtonGroup.Buttons = [];
|
||
BookmarkManager.ButtonGroup.innerHTML = '';
|
||
}
|
||
//BookmarkManager.Bookmarks = BookmarkManager.Bookmarks.filter(Bmk => typeof Bmk.IIPP == 'number' && typeof Bmk['%'] == 'number');
|
||
let Bookmarks = [], ExistingBookmarks = [];
|
||
if(Array.isArray(Opt.Bookmarks)) BookmarkManager.Bookmarks = Opt.Bookmarks;
|
||
if(Opt.Added) Bookmarks = [Opt.Added];
|
||
else if(L.Opened) {
|
||
I.PageObserver.updateCurrent();
|
||
Bookmarks = I.PageObserver.Current.Pages.map(Page => ({
|
||
IIPP: I.PageObserver.getIIPP({ Page: Page }),
|
||
'%': Math.floor((Page.Index + 1) / R.Pages.length * 100) // only for showing percentage in waiting status
|
||
}));
|
||
}
|
||
if(BookmarkManager.Bookmarks.length) {
|
||
const UpdatedBookmarks = []
|
||
for(let l = BookmarkManager.Bookmarks.length, i = 0; i < l; i++) {
|
||
let Bmk = BookmarkManager.Bookmarks[i];
|
||
if(typeof Bmk == 'number') Bmk = { IIPP: Bmk };
|
||
else if(!Bmk) continue;
|
||
else if(typeof Bmk.IIPP != 'number') {
|
||
if(Bmk.ItemIndex) Bmk.IIPP = Bmk.ItemIndex + (Bmk.PageProgressInItem ? Bmk.PageProgressInItem : 0);
|
||
else if(B.Package.Metadata['rendition:layout'] == 'pre-paginated') {
|
||
if(typeof Bmk.PageNumber == 'number') Bmk.PageIndex = Bmk.PageNumber - 1;
|
||
if(typeof Bmk.PageIndex == 'number') Bmk.IIPP = Bmk.PageIndex;
|
||
}
|
||
}
|
||
if(typeof Bmk.IIPP != 'number') continue;
|
||
if(/^(\d*\.)?\d+?$/.test(Bmk['%'])) Bmk['%'] *= 1; else delete Bmk['%'];
|
||
let Label = '', ClassName = '';
|
||
const BB = 'bibi-bookmark';
|
||
const Page = R.hatchPage(Bmk);
|
||
let PageNumber = 0;
|
||
if(Page && typeof Page.Index == 'number') PageNumber = Page.Index + 1;
|
||
else if(B.Package.Metadata['rendition:layout'] == 'pre-paginated') PageNumber = Math.floor(Bmk.IIPP) + 1;
|
||
if(PageNumber) {
|
||
Label += `<span class="${BB}-page"><span class="${BB}-unit">P.</span><span class="${BB}-number">${ PageNumber }</span></span>`;
|
||
if(R.Pages.length) {
|
||
if(PageNumber > R.Pages.length) continue;
|
||
Label += `<span class="${BB}-total-pages">/<span class="${BB}-number">${ R.Pages.length }</span></span>`;
|
||
Bmk['%'] = Math.floor(PageNumber / R.Pages.length * 100);
|
||
}
|
||
}
|
||
if(typeof Bmk['%'] == 'number') {
|
||
if(Label) Label += ` <span class="${BB}-percent"><span class="${BB}-parenthesis">(</span><span class="${BB}-number">${ Bmk['%'] }</span><span class="${BB}-unit">%</span><span class="${BB}-parenthesis">)</span></span>`;
|
||
else Label += `<span class="${BB}-percent">` + `<span class="${BB}-number">${ Bmk['%'] }</span><span class="${BB}-unit">%</span>` + `</span>`;
|
||
}
|
||
const Labels = Label ? { default: { default: Label, ja: Label } } : { default: { default: `Bookmark #${ UpdatedBookmarks.length + 1 }`, ja: `しおり #${ UpdatedBookmarks.length + 1 }` } };
|
||
if(Bookmarks.reduce((Exists, Bookmark) => Exists = Bmk.IIPP == Bookmark.IIPP ? true : Exists, false)) {
|
||
ExistingBookmarks.push(Bmk);
|
||
ClassName = `bibi-button-bookmark-is-current`;
|
||
Labels.default.default += ` <span class="${BB}-is-current"></span>`;
|
||
Labels.default.ja += ` <span class="${BB}-is-current ${BB}-is-current-ja"></span>`;
|
||
}
|
||
const Button = BookmarkManager.ButtonGroup.addButton({
|
||
className: ClassName,
|
||
Type: 'normal',
|
||
Labels: Labels,
|
||
Icon: `<span class="bibi-icon bibi-icon-bookmark bibi-icon-a-bookmark"></span>`,
|
||
Bookmark: Bmk,
|
||
action: () => {
|
||
if(L.Opened) return R.focusOn({ Destination: Bmk }).then(Destination => I.History.add({ UI: BookmarkManager, SumUp: false/*true*/, Destination: Destination }));
|
||
if(!L.Waiting) return false;
|
||
if(S['start-in-new-window']) return L.openNewWindow(location.href + (location.hash ? '&' : '#') + 'jo(iipp=' + Bmk.IIPP + ')');
|
||
R.StartOn = { IIPP: Bmk.IIPP };
|
||
L.play();
|
||
},
|
||
remove: () => BookmarkManager.remove(Bmk)
|
||
});
|
||
const Remover = Button.appendChild(sML.create('span', { className: 'bibi-remove-bookmark', title: 'しおりを削除' }));
|
||
I.setFeedback(Remover, { StopPropagation: true });
|
||
E.add(Remover, 'bibi:tapped', () => Button.remove());
|
||
Remover.addEventListener(E['pointer-over'], Eve => Eve.stopPropagation());
|
||
if(Bmk.IsHot) {
|
||
delete Bmk.IsHot;
|
||
I.setUIState(Button, 'active'); setTimeout(() => I.setUIState(Button, ExistingBookmarks.includes(Bmk) ? 'disabled' : 'default'), 234);
|
||
}
|
||
else if(ExistingBookmarks.includes(Bmk)) I.setUIState(Button, 'disabled');
|
||
else I.setUIState(Button, 'default');
|
||
const UpdatedBookmark = { IIPP: Bmk.IIPP };
|
||
if(Bmk['%']) UpdatedBookmark['%'] = Bmk['%'];
|
||
UpdatedBookmarks.push(UpdatedBookmark);
|
||
}
|
||
BookmarkManager.Bookmarks = UpdatedBookmarks;
|
||
} else {
|
||
if(!L.Opened) BookmarkManager.Subpanel.Opener.ButtonGroup.style.display = 'none';
|
||
}
|
||
if(BookmarkManager.Bookmarks.length < S['max-bookmarks']) {
|
||
BookmarkManager.AddButton = BookmarkManager.ButtonGroup.addButton({
|
||
id: 'bibi-button-add-a-bookmark',
|
||
Type: 'normal',
|
||
Labels: { default: { default: `Add a Bookmark to Current Page`, ja: `現在のページにしおりを挟む` } },
|
||
Icon: `<span class="bibi-icon bibi-icon-bookmark bibi-icon-add-a-bookmark"></span>`,
|
||
action: () => Bookmarks.length ? BookmarkManager.add(Bookmarks[0]) : false
|
||
});
|
||
if(!Bookmarks.length || ExistingBookmarks.length) {
|
||
I.setUIState(BookmarkManager.AddButton, 'disabled');
|
||
}
|
||
}
|
||
O.Biscuits.memorize('Book', { Bookmarks: BookmarkManager.Bookmarks });
|
||
/**/ E.dispatch('bibi:updated-bookmarks', BookmarkManager.Bookmarks);
|
||
if(Opt.Added) E.dispatch( 'bibi:added-bookmark', BookmarkManager.Bookmarks);
|
||
if(Opt.Removed) E.dispatch('bibi:removed-bookmark', BookmarkManager.Bookmarks);
|
||
},
|
||
};
|
||
BookmarkManager.initialize();
|
||
E.dispatch('bibi:created-bookmark-manager');
|
||
}};
|
||
|
||
|
||
I.Flipper = { create: () => {
|
||
const Flipper = I.Flipper = {
|
||
PreviousDistance: 0,
|
||
Back: { Distance: -1 }, Forward: { Distance: 1 },
|
||
getDirection: (Division) => { switch(S.ARA) {
|
||
case 'horizontal': return Division.X != 'center' ? Division.X : Division.Y;
|
||
case 'vertical' : return Division.Y != 'middle' ? Division.Y : Division.X; } },
|
||
isAbleToFlip: (Distance) => {
|
||
if(L.Opened && !I.OpenedSubpanel && typeof (Distance * 1) == 'number' && Distance) {
|
||
if(!I.PageObserver.Current.List.length) I.PageObserver.updateCurrent();
|
||
if(I.PageObserver.Current.List.length) {
|
||
let CurrentEdge, BookEdgePage, Edged;
|
||
if(Distance < 0) CurrentEdge = I.PageObserver.Current.List[ 0], BookEdgePage = R.Pages[ 0], Edged = 'Headed';
|
||
else CurrentEdge = I.PageObserver.Current.List.slice(-1)[0], BookEdgePage = R.Pages.slice(-1)[0], Edged = 'Footed';
|
||
if(CurrentEdge.Page != BookEdgePage) return true;
|
||
if(!CurrentEdge.PageIntersectionStatus.Contained && !CurrentEdge.PageIntersectionStatus[Edged]) return true;
|
||
}
|
||
}
|
||
return false;
|
||
},
|
||
flip: (Distance, Opt = {}) => {
|
||
if(typeof (Distance *= 1) != 'number' || !isFinite(Distance) || Distance === 0) return Promise.resolve();
|
||
I.ScrollObserver.forceStopScrolling();
|
||
const SumUpHistory = (I.History.List.slice(-1)[0].UI == Flipper) && ((Distance < 0 ? -1 : 1) === (Flipper.PreviousDistance < 0 ? -1 : 1));
|
||
Flipper.PreviousDistance = Distance;
|
||
if(S['book-rendition-layout'] == 'pre-paginated') { // Preventing flicker.
|
||
const CIs = [
|
||
I.PageObserver.Current.List[ 0].Page.Index,
|
||
I.PageObserver.Current.List.slice(-1)[0].Page.Index
|
||
], TI = CIs[Distance < 0 ? 0 : 1] + Distance;
|
||
CIs.forEach(CI => { try { R.Pages[CI].Spread.Box.classList.remove('current'); } catch(Err) {} });
|
||
try { R.Pages[TI].Spread.Box.classList.add( 'current'); } catch(Err) {}
|
||
}
|
||
return R.moveBy({ Distance: Distance, Duration: Opt.Duration }).then(Destination => I.History.add({ UI: Flipper, SumUp: SumUpHistory, Destination: Destination }));
|
||
}
|
||
};
|
||
Flipper[-1] = Flipper.Back, Flipper[1] = Flipper.Forward;
|
||
}};
|
||
|
||
|
||
I.Arrows = { create: () => { if(!S['use-arrows']) return I.Arrows = null;
|
||
const Arrows = I.Arrows = {
|
||
navigate: () => setTimeout(() => {
|
||
[Arrows.Back, Arrows.Forward].forEach(Arrow => I.Flipper.isAbleToFlip(Arrow.Distance) ? Arrow.classList.add('glowing') : false);
|
||
setTimeout(() => [Arrows.Back, Arrows.Forward].forEach(Arrow => Arrow.classList.remove('glowing')), 1234);
|
||
}, 400),
|
||
toggleState: () => [Arrows.Back, Arrows.Forward].forEach(Arrow => {
|
||
const Availability = I.Flipper.isAbleToFlip(Arrow.Distance);
|
||
Arrow.classList.toggle( 'available', Availability);
|
||
Arrow.classList.toggle('unavailable', !Availability);
|
||
}),
|
||
areAvailable: (BibiEvent) => {
|
||
if(!L.Opened) return false;
|
||
if(I.OpenedSubpanel) return false;
|
||
if(I.Panel && I.Panel.UIState == 'active') return false;
|
||
//if(BibiEvent.Coord.Y < I.Menu.Height/* * 1.5*/) return false;
|
||
const Buf = 3;
|
||
if(BibiEvent.Coord.X <= Buf || BibiEvent.Coord.Y <= Buf) return false;
|
||
else if(S.ARA == 'horizontal') { if(BibiEvent.Coord.X >= window.innerWidth - Buf || BibiEvent.Coord.Y >= window.innerHeight - O.Scrollbars.Height - Buf) return false; }
|
||
else if(S.ARA == 'vertical' ) { if(BibiEvent.Coord.Y >= window.innerHeight - Buf || BibiEvent.Coord.X >= window.innerWidth - O.Scrollbars.Width - Buf) return false; }
|
||
if(BibiEvent.Target.ownerDocument.documentElement == O.HTML) {
|
||
if(BibiEvent.Target == O.HTML || BibiEvent.Target == O.Body || BibiEvent.Target == I.Menu) return true;
|
||
if(/^(bibi-main|bibi-arrow|bibi-help|bibi-poweredby)/.test(BibiEvent.Target.id)) return true;
|
||
if(/^(spread|item|page)( |-|$)/.test(BibiEvent.Target.className)) return true;
|
||
} else {
|
||
return O.isPointableContent(BibiEvent.Target) ? false : true;
|
||
}
|
||
return false;
|
||
}
|
||
};
|
||
O.HTML.classList.add('arrows-active');
|
||
Arrows.Back = O.Body.appendChild(sML.create('div', { className: 'bibi-arrow', id: 'bibi-arrow-back', Labels: { default: { default: `Back`, ja: `戻る` } }, Distance: -1 }));
|
||
Arrows.Forward = O.Body.appendChild(sML.create('div', { className: 'bibi-arrow', id: 'bibi-arrow-forward', Labels: { default: { default: `Forward`, ja: `進む` } }, Distance: 1 }));
|
||
Arrows[-1] = Arrows.Forward.Pair = I.Flipper.Back.Arrow = Arrows.Back;
|
||
Arrows[ 1] = Arrows.Back.Pair = I.Flipper.Forward.Arrow = Arrows.Forward;
|
||
[Arrows.Back, Arrows.Forward].forEach(Arrow => {
|
||
I.setFeedback(Arrow);
|
||
const FunctionsToBeCanceled = [Arrow.showHelp, Arrow.hideHelp, Arrow.BibiTapObserver.onTap, Arrow.BibiTapObserver.onDoubleTap];
|
||
if(!O.TouchOS) FunctionsToBeCanceled.push(Arrow.BibiHoverObserver.onHover, Arrow.BibiHoverObserver.onUnHover);
|
||
FunctionsToBeCanceled.forEach(f2BC => f2BC = () => {});
|
||
});
|
||
E.add('bibi:commands:move-by', Distance => { // indicate direction
|
||
if(!L.Opened || typeof (Distance *= 1) != 'number' || !isFinite(Distance) || !(Distance = Math.round(Distance))) return false;
|
||
return E.dispatch(Distance < 0 ? Arrows.Back : Arrows.Forward, 'bibi:tapped', null);
|
||
});
|
||
E.add('bibi:loaded-item', Item => {/*
|
||
sML.appendCSSRule(Item.contentDocument, 'html[data-bibi-cursor="left"]', 'cursor: w-resize;');
|
||
sML.appendCSSRule(Item.contentDocument, 'html[data-bibi-cursor="right"]', 'cursor: e-resize;');
|
||
sML.appendCSSRule(Item.contentDocument, 'html[data-bibi-cursor="top"]', 'cursor: n-resize;');
|
||
sML.appendCSSRule(Item.contentDocument, 'html[data-bibi-cursor="bottom"]', 'cursor: s-resize;');*/
|
||
sML.appendCSSRule(Item.contentDocument, 'html[data-bibi-cursor]', 'cursor: pointer;');
|
||
});
|
||
E.add('bibi:opened', () => setTimeout(() => { Arrows.toggleState(); Arrows.navigate(); }, 123));
|
||
E.add('bibi:scrolled', () => Arrows.toggleState());
|
||
E.add('bibi:changed-view', () => Arrows.navigate());
|
||
E.dispatch('bibi:created-arrows');
|
||
// Optimize to Scrollbar Size
|
||
(_ => {
|
||
_('html.appearance-horizontal.book-full-height:not(.slider-opened)', 'height', O.Scrollbars.Width);
|
||
_('html.appearance-horizontal:not(.book-full-height):not(.slider-opened)', 'height', O.Scrollbars.Width + I.Menu.Height);
|
||
_('html.appearance-vertical:not(.slider-opened)', 'width', O.Scrollbars.Width);
|
||
})((Context, WidthOrHeight, Margin) => sML.appendCSSRule(
|
||
`${ Context } div#bibi-arrow-back, ${ Context } div#bibi-arrow-forward`,
|
||
`${ WidthOrHeight }: calc(100% - ${ Margin }px); ${ WidthOrHeight }: calc(100v${ WidthOrHeight.charAt(0) } - ${ Margin }px);`
|
||
));
|
||
}};
|
||
|
||
|
||
I.AxisSwitcher = { create: () => { if(S['fix-reader-view-mode']) return I.AxisSwitcher = null;
|
||
const AxisSwitcher = I.AxisSwitcher = {
|
||
switchAxis: () => new Promise(resolve => {
|
||
if(S.RVM == 'paged') return resolve();
|
||
const ViewMode = S.RVM == 'horizontal' ? 'vertical' : 'horizontal';
|
||
I.Menu.Config.ViewModeSection.ButtonGroups[0].Buttons.filter(Button => Button.Mode == ViewMode)[0].BibiTapObserver.onTap();
|
||
resolve();
|
||
})
|
||
};
|
||
E.dispatch('bibi:created-axis-switcher');
|
||
}};
|
||
|
||
|
||
I.Spinner = { create: () => {
|
||
const Spinner = I.Spinner = O.Body.appendChild(sML.create('div', { id: 'bibi-spinner' }));
|
||
for(let i = 1; i <= 12; i++) Spinner.appendChild(document.createElement('span'));
|
||
E.dispatch('bibi:created-spinner');
|
||
}};
|
||
|
||
|
||
I.createButtonGroup = (Par = {}) => {
|
||
if(Par.Area && Par.Area.tagName) {
|
||
const AreaToBeAppended = Par.Area;
|
||
delete Par.Area;
|
||
return AreaToBeAppended.addButtonGroup(Par);
|
||
}
|
||
if(typeof Par.className != 'string' || !Par.className) delete Par.className;
|
||
if(typeof Par.id != 'string' || !Par.id) delete Par.id;
|
||
const ClassNames = ['bibi-buttongroup'];
|
||
if(Par.Tiled) ClassNames.push('bibi-buttongroup-tiled');
|
||
if(Par.Sticky) ClassNames.push('sticky');
|
||
if(Par.className) ClassNames.push(Par.className);
|
||
Par.className = ClassNames.join(' ');
|
||
const ButtonsToAdd = Array.isArray(Par.Buttons) ? Par.Buttons : Par.Button ? [Par.Button] : [];
|
||
delete Par.Buttons;
|
||
delete Par.Button;
|
||
const ButtonGroup = sML.create('ul', Par);
|
||
ButtonGroup.Buttons = [];
|
||
ButtonGroup.addButton = function(Par) {
|
||
const Button = I.createButton(Par);
|
||
if(!Button) return null;
|
||
Button.ButtonGroup = this;
|
||
Button.ButtonBox = Button.ButtonGroup.appendChild(sML.create('li', { className: 'bibi-buttonbox bibi-buttonbox-' + Button.Type }));
|
||
if(!O.TouchOS) {
|
||
I.TouchObserver.observeElementHover(Button.ButtonBox)
|
||
I.TouchObserver.setElementHoverActions(Button.ButtonBox);
|
||
}
|
||
Button.ButtonBox.appendChild(Button)
|
||
Button.ButtonGroup.Buttons.push(Button);
|
||
return Button;
|
||
};
|
||
ButtonsToAdd.forEach(ButtonToAdd => {
|
||
if(!ButtonToAdd.Type && Par.ButtonType) ButtonToAdd.Type = Par.ButtonType;
|
||
ButtonGroup.addButton(ButtonToAdd);
|
||
});
|
||
ButtonGroup.Busy = false;
|
||
return ButtonGroup;
|
||
};
|
||
|
||
|
||
I.createButton = (Par = {}) => {
|
||
if(typeof Par.className != 'string' || !Par.className) delete Par.className;
|
||
if(typeof Par.id != 'string' || !Par.id ) delete Par.id;
|
||
Par.Type = (typeof Par.Type == 'string' && /^(normal|toggle|radio|link)$/.test(Par.Type)) ? Par.Type : 'normal';
|
||
const ClassNames = ['bibi-button', 'bibi-button-' + Par.Type];
|
||
if(Par.className) ClassNames.push(Par.className);
|
||
Par.className = ClassNames.join(' ');
|
||
if(typeof Par.Icon != 'undefined' && !Par.Icon.tagName) {
|
||
if(typeof Par.Icon == 'string' && Par.Icon) {
|
||
Par.Icon = sML.hatch(Par.Icon);
|
||
} else {
|
||
delete Par.Icon;
|
||
}
|
||
}
|
||
const Button = sML.create((typeof Par.href == 'string' ? 'a' : 'span'), Par);
|
||
if(Button.Icon) {
|
||
Button.IconBox = Button.appendChild(sML.create('span', { className: 'bibi-button-iconbox' }));
|
||
Button.IconBox.appendChild(Button.Icon);
|
||
Button.Icon = Button.IconBox.firstChild;
|
||
Button.IconBox.Button = Button.Icon.Button = Button;
|
||
}
|
||
Button.Label = Button.appendChild(sML.create('span', { className: 'bibi-button-label' }));
|
||
I.setFeedback(Button, {
|
||
Help: Par.Help,
|
||
Checked: Par.Checked,
|
||
StopPropagation: true,
|
||
PreventDefault: (Button.href ? false : true)
|
||
});
|
||
Button.isAvailable = () => {
|
||
if(Button.Busy) return false;
|
||
if(Button.ButtonGroup && Button.ButtonGroup.Busy) return false;
|
||
return (Button.UIState != 'disabled');
|
||
};
|
||
if(typeof Button.action == 'function') E.add(Button, 'bibi:tapped', () => Button.isAvailable() ? Button.action.apply(Button, arguments) : null);
|
||
Button.Busy = false;
|
||
return Button;
|
||
};
|
||
|
||
|
||
I.createSubpanel = (Par = {}) => {
|
||
if(typeof Par.className != 'string' || !Par.className) delete Par.className;
|
||
if(typeof Par.id != 'string' || !Par.id ) delete Par.id;
|
||
const ClassNames = ['bibi-subpanel', 'bibi-subpanel-' + (Par.Position == 'left' ? 'left' : 'right')];
|
||
if(Par.className) ClassNames.push(Par.className);
|
||
Par.className = ClassNames.join(' ');
|
||
const SectionsToAdd = Array.isArray(Par.Sections) ? Par.Sections : Par.Section ? [Par.Section] : [];
|
||
delete Par.Sections;
|
||
delete Par.Section;
|
||
const Subpanel = O.Body.appendChild(sML.create('div', Par));
|
||
Subpanel.Sections = [];
|
||
Subpanel.addEventListener(E['pointerdown'], Eve => Eve.stopPropagation());
|
||
Subpanel.addEventListener(E['pointerup'], Eve => Eve.stopPropagation());
|
||
I.setToggleAction(Subpanel, {
|
||
onopened: function(Opt) {
|
||
I.Subpanels.forEach(Sp => Sp == Subpanel ? true : Sp.close({ ForAnotherSubpanel: true }));
|
||
I.OpenedSubpanel = this;
|
||
this.classList.add('opened');
|
||
O.HTML.classList.add('subpanel-opened');
|
||
if(Subpanel.Opener) I.setUIState(Subpanel.Opener, 'active');
|
||
if(Par.onopened) Par.onopened.apply(Subpanel, arguments);
|
||
},
|
||
onclosed: function(Opt) {
|
||
this.classList.remove('opened');
|
||
if(I.OpenedSubpanel == this) setTimeout(() => I.OpenedSubpanel = null, 222);
|
||
if(!Opt || !Opt.ForAnotherSubpanel) {
|
||
O.HTML.classList.remove('subpanel-opened');
|
||
}
|
||
if(Subpanel.Opener) {
|
||
I.setUIState(Subpanel.Opener, 'default');
|
||
}
|
||
if(Par.onclosed) Par.onclosed.apply(Subpanel, arguments);
|
||
}
|
||
});
|
||
Subpanel.bindOpener = (Opener) => {
|
||
E.add(Opener, 'bibi:tapped', () => Subpanel.toggle());
|
||
Subpanel.Opener = Opener;
|
||
return Subpanel.Opener;
|
||
}
|
||
if(Subpanel.Opener) Subpanel.bindOpener(Subpanel.Opener);
|
||
E.add('bibi:opened-panel', Subpanel.close);
|
||
E.add('bibi:closes-utilities', Subpanel.close);
|
||
I.Subpanels.push(Subpanel);
|
||
Subpanel.addSection = function(Par = {}) {
|
||
const SubpanelSection = I.createSubpanelSection(Par);
|
||
if(!SubpanelSection) return null;
|
||
SubpanelSection.Subpanel = this;
|
||
this.appendChild(SubpanelSection)
|
||
this.Sections.push(SubpanelSection);
|
||
return SubpanelSection;
|
||
};
|
||
SectionsToAdd.forEach(SectionToAdd => Subpanel.addSection(SectionToAdd));
|
||
return Subpanel;
|
||
};
|
||
|
||
|
||
I.createSubpanelSection = (Par = {}) => {
|
||
if(typeof Par.className != 'string' || !Par.className) delete Par.className;
|
||
if(typeof Par.id != 'string' || !Par.id ) delete Par.id;
|
||
const ClassNames = ['bibi-subpanel-section'];
|
||
if(Par.className) ClassNames.push(Par.className);
|
||
Par.className = ClassNames.join(' ');
|
||
const PGroupsToAdd = Array.isArray(Par.PGroups) ? Par.PGroups : Par.PGroup ? [Par.PGroup] : [];
|
||
delete Par.PGroups;
|
||
delete Par.PGroup;
|
||
const ButtonGroupsToAdd = Array.isArray(Par.ButtonGroups) ? Par.ButtonGroups : Par.ButtonGroup ? [Par.ButtonGroup] : [];
|
||
delete Par.ButtonGroups;
|
||
delete Par.ButtonGroup;
|
||
const SubpanelSection = sML.create('div', Par);
|
||
if(SubpanelSection.Labels) { // HGroup
|
||
SubpanelSection.Labels = I.distillLabels(SubpanelSection.Labels);
|
||
SubpanelSection
|
||
.appendChild(sML.create('div', { className: 'bibi-hgroup' }))
|
||
.appendChild(sML.create('p', { className: 'bibi-h' }))
|
||
.appendChild(sML.create('span', { className: 'bibi-h-label', innerHTML: SubpanelSection.Labels['default'][O.Language] }));
|
||
}
|
||
SubpanelSection.ButtonGroups = []; // ButtonGroups
|
||
SubpanelSection.addButtonGroup = function(Par = {}) {
|
||
const ButtonGroup = I.createButtonGroup(Par);
|
||
this.appendChild(ButtonGroup);
|
||
this.ButtonGroups.push(ButtonGroup);
|
||
return ButtonGroup;
|
||
};
|
||
ButtonGroupsToAdd.forEach(ButtonGroupToAdd => {
|
||
if(ButtonGroupToAdd) SubpanelSection.addButtonGroup(ButtonGroupToAdd);
|
||
});
|
||
return SubpanelSection;
|
||
};
|
||
|
||
|
||
I.setToggleAction = (Obj, Par = {}) => {
|
||
// Par = {
|
||
// onopened: Function,
|
||
// onclosed: Function
|
||
// };
|
||
return sML.edit(Obj, {
|
||
UIState: 'default',
|
||
open: (Opt) => new Promise(resolve => {
|
||
if(Obj.UIState == 'default') {
|
||
I.setUIState(Obj, 'active');
|
||
if(Par.onopened) Par.onopened.call(Obj, Opt);
|
||
}
|
||
resolve(Opt);
|
||
}),
|
||
close: (Opt) => new Promise(resolve => {
|
||
if(Obj.UIState == 'active') {
|
||
I.setUIState(Obj, 'default');
|
||
if(Par.onclosed) Par.onclosed.call(Obj, Opt);
|
||
}
|
||
resolve(Opt);
|
||
}),
|
||
toggle: (Opt) => Obj.UIState == 'default' ? Obj.open(Opt) : Obj.close(Opt)
|
||
});
|
||
};
|
||
|
||
|
||
I.setFeedback = (Ele, Opt = {}) => {
|
||
Ele.Labels = I.distillLabels(Ele.Labels);
|
||
if(Ele.Labels) {
|
||
if(Opt.Help) {
|
||
Ele.showHelp = () => {
|
||
if(I.Help && Ele.Labels[Ele.UIState]) I.Help.show(Ele.Labels[Ele.UIState][O.Language]);
|
||
return Ele;
|
||
};
|
||
Ele.hideHelp = () => {
|
||
if(I.Help) I.Help.hide();
|
||
return Ele;
|
||
};
|
||
}
|
||
if(Ele.Notes) Ele.note = () => {
|
||
if(Ele.Labels[Ele.UIState]) setTimeout(() => I.note(Ele.Labels[Ele.UIState][O.Language]), 0);
|
||
return Ele;
|
||
}
|
||
}
|
||
if(!O.TouchOS) {
|
||
I.TouchObserver.observeElementHover(Ele);
|
||
I.TouchObserver.setElementHoverActions(Ele);
|
||
}
|
||
I.TouchObserver.observeElementTap(Ele, Opt);
|
||
I.TouchObserver.setElementTapActions(Ele);
|
||
I.setUIState(Ele, Opt.Checked ? 'active' : 'default');
|
||
return Ele;
|
||
};
|
||
|
||
|
||
I.setUIState = (UI, UIState) => {
|
||
if(!UIState) UIState = 'default';
|
||
UI.PreviousUIState = UI.UIState;
|
||
if(UIState == UI.UIState) return;
|
||
UI.UIState = UIState;
|
||
if(UI.tagName) {
|
||
if(UI.Labels && UI.Labels[UI.UIState] && UI.Labels[UI.UIState][O.Language]) {
|
||
UI.title = UI.Labels[UI.UIState][O.Language].replace(/<[^>]+>/g, '');
|
||
if(UI.Label) UI.Label.innerHTML = UI.Labels[UI.UIState][O.Language];
|
||
}
|
||
sML.replaceClass(UI, UI.PreviousUIState, UI.UIState);
|
||
}
|
||
return UI.UIState;
|
||
};
|
||
|
||
|
||
I.isPointerStealth = () => {
|
||
let IsPointerStealth = false;
|
||
I.isPointerStealth.Checkers.forEach(checker => IsPointerStealth = checker() ? true : IsPointerStealth);
|
||
return IsPointerStealth;
|
||
};
|
||
|
||
I.isPointerStealth.Checkers = [];
|
||
|
||
I.isPointerStealth.addChecker = (fun) => typeof fun == 'function' && !I.isPointerStealth.Checkers.includes(fun) ? I.isPointerStealth.Checkers.push(fun) : I.isPointerStealth.Checkers.length;
|
||
|
||
|
||
I.distillLabels = (Labels) => {
|
||
if(typeof Labels != 'object' || !Labels) Labels = {};
|
||
for(const State in Labels) Labels[State] = I.distillLabels.distillLanguage(Labels[State]);
|
||
if(!Labels['default']) Labels['default'] = I.distillLabels.distillLanguage();
|
||
if(!Labels['active'] && Labels['default']) Labels['active'] = Labels['default'];
|
||
if(!Labels['disabled'] && Labels['default']) Labels['disabled'] = Labels['default'];
|
||
return Labels;
|
||
};
|
||
|
||
I.distillLabels.distillLanguage = (Label) => {
|
||
if(typeof Label != 'object' || !Label) Label = { default: Label };
|
||
if(typeof Label['default'] != 'string') {
|
||
if(typeof Label['en'] == 'string') Label['default'] = Label['en'];
|
||
else if(typeof Label[O.Language] == 'string') Label['default'] = Label[O.Language];
|
||
else Label['default'] = '';
|
||
}
|
||
if(typeof Label[O.Language] != 'string') {
|
||
if(typeof Label['default'] == 'string') Label[O.Language] = Label['default'];
|
||
else if(typeof Label['en'] == 'string') Label[O.Language] = Label['en'];
|
||
else Label[O.Language] = '';
|
||
}
|
||
return Label;
|
||
};
|
||
|
||
|
||
I.orthogonal = (InputType) => S['orthogonal-' + InputType][S.RVM == 'paged' ? 0 : 1];
|
||
|
||
I.isScrollable = () => (S.ARA == S.SLA && I.Loupe.CurrentTransformation.Scale == 1) ? true : false;
|
||
|
||
|
||
I.getBookIcon = () => sML.create('div', { className: 'book-icon', innerHTML: `<span></span>` });
|
||
|
||
|
||
|
||
|
||
//==============================================================================================================================================
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
//-- Presets
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
|
||
export const P = {}; // Bibi.Preset
|
||
|
||
|
||
Bibi.preset = (Preset) => Bibi.at1st.List.push(() => {
|
||
Bibi.applyFilteredSettingsTo(P, Preset, [Bibi.SettingTypes, Bibi.SettingTypes_PresetOnly], 'Fill');
|
||
delete P['book'];
|
||
P.Script = document.getElementById('bibi-preset');
|
||
});
|
||
|
||
|
||
P.initialize = () => {
|
||
const DocHRef = location.href.split('?')[0];
|
||
P['bookshelf'] = new URL(P['bookshelf'] || '../../bibi-bookshelf', P.Script.src).href.replace(/\/$/, '');
|
||
P['extensions'] = (() => {
|
||
let Extensions_HTML = document.getElementById('bibi-preset').getAttribute('data-bibi-extensions');
|
||
if(Extensions_HTML) {
|
||
Extensions_HTML = Extensions_HTML.trim().replace(/\s+/, ' ').split(' ').map(EPath => ({ src: new URL(EPath, DocHRef).href }));
|
||
if(Extensions_HTML.length) P['extensions'] = Extensions_HTML;
|
||
}
|
||
return !Array.isArray(P['extensions']) ? [] : P['extensions'].filter(Xtn => {
|
||
if(Xtn.hasOwnProperty('-spell-of-activation-')) {
|
||
const SoA = Xtn['-spell-of-activation-'];
|
||
if(!SoA || !/^[a-zA-Z0-9_\-]+$/.test(SoA) || !U.hasOwnProperty(SoA)) return false;
|
||
}
|
||
if(!Xtn || !Xtn['src'] || typeof Xtn['src'] != 'string') return false;
|
||
return (Xtn['src'] = new URL(Xtn['src'], P.Script.src).href);
|
||
});
|
||
})();
|
||
delete P.initialize;
|
||
};
|
||
|
||
|
||
|
||
//==============================================================================================================================================
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
//-- URI-Defined Settings (FileName, Queries, Hash, and EPUBCFI)
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
|
||
export const U = {};
|
||
|
||
|
||
U.translateData = (PnV) => {
|
||
let [_P, _V] = PnV;
|
||
switch(_P) {
|
||
case 'paged': case 'horizontal': case 'vertical': _V = _P, _P = 'reader-view-mode'; break;
|
||
case 'view': case 'rvm': _P = 'reader-view-mode'; break;
|
||
case 'dppd': case 'default-ppd': _P = 'default-page-progression-direction'; break;
|
||
case 'pagination': _P = 'pagination-method'; break;
|
||
}
|
||
return [_P, _V];
|
||
};
|
||
|
||
|
||
Bibi.at1st.List.unshift(() => {
|
||
const LS = location.search; if(typeof LS != 'string') return;
|
||
const Q = LS.replace(/^\?/, '').split('&').reduce((Q, PnV) => {
|
||
let [_P, _V] = PnV.split('=');
|
||
if(!_V) _V = undefined;
|
||
switch(_P) {
|
||
case 'log': if(!_V) _V = '1'; break;
|
||
case 'book': if(!_V) return Q; break;
|
||
case 'zine': case 'wait': case 'debug': if(!_V) _V = 'true'; break;
|
||
default: [_P, _V] = U.translateData([_P, _V]);
|
||
}
|
||
Q[_P] = _V;
|
||
return Q;
|
||
}, {});
|
||
Object.assign(U, Bibi.applyFilteredSettingsTo(Q, Q, [Bibi.SettingTypes, Bibi.SettingTypes_UserOnly]));
|
||
if(!U['book']) delete U['zine'];
|
||
if(U['debug']) Bibi.Debug = true, U['log'] = 9;
|
||
});
|
||
|
||
|
||
U.initialize = () => {
|
||
const _U = Bibi.applyFilteredSettingsTo({}, U, [Bibi.SettingTypes, Bibi.SettingTypes_UserOnly]);
|
||
const HashData = (() => {
|
||
let Hash = location.hash;
|
||
if(typeof Hash != 'string') return {};
|
||
const Data = {};
|
||
const CatGroupREStr = '([&#])([a-zA-Z_]+)\\(([^\\(\\)]+)\\)', CatGroups = Hash.match(new RegExp(CatGroupREStr, 'g'));
|
||
if(CatGroups && CatGroups.length) CatGroups.forEach(CatGroup => {
|
||
const CatGroupParts = CatGroup.match(new RegExp(CatGroupREStr));
|
||
let Cat = CatGroupParts[2].toLowerCase(), Dat = CatGroupParts[3];
|
||
if(Dat) {
|
||
Cat = Cat == 'bibi' ? 'Bibi' : Cat == 'jo' ? 'Jo' : Cat == 'epubcfi' ? 'EPUBCFI' : undefined;
|
||
if(Cat) Data[Cat] = Dat;
|
||
}
|
||
Hash = Hash.replace(CatGroup, CatGroupParts[1]);
|
||
});
|
||
Data['#'] = Hash.replace(/^#|&$/, '');
|
||
for(const Cat in Data) {
|
||
if(Cat == 'EPUBCFI') continue;
|
||
const ParsedData = U.initialize.parseDataString(Data[Cat]);
|
||
if(!ParsedData) continue;
|
||
Data[Cat] = Bibi.applyFilteredSettingsTo({}, ParsedData, [Bibi.SettingTypes, Bibi.SettingTypes_UserOnly]);
|
||
delete Data[Cat]['book'];
|
||
}
|
||
return Data;
|
||
})();
|
||
if(HashData['#'] ) { Object.assign(_U, _U['#'] = HashData['#'] ); }
|
||
if(HashData['Bibi'] ) { Object.assign(_U, _U['Bibi'] = HashData['Bibi']); }
|
||
if(HashData['Jo'] ) { Object.assign(_U, _U['Jo'] = HashData['Jo'] ); if(history.replaceState) history.replaceState(null, null, location.href.replace(/[&#]jo\([^\)]*\)$/g, '')); }
|
||
if(HashData['EPUBCFI']) { _U['EPUBCFI'] = HashData['EPUBCFI']; }
|
||
_U['Query'] = {}; for(const Pro in U) {
|
||
if(typeof U[Pro] != 'function') _U['Query'][Pro] = U[Pro];
|
||
U[Pro] = undefined; delete U[Pro];
|
||
}
|
||
Object.assign(U, _U);
|
||
if(typeof U['nav'] == 'number') U['nav'] < 1 ? delete U['nav'] : R.StartOn = { Nav: U['nav'] }; // to be converted in L.coordinateLinkages
|
||
else if(typeof U['p'] == 'string') R.StartOn = { P: U['p'] };
|
||
else if(typeof U['iipp'] == 'number') R.StartOn = { IIPP: U['iipp'] };
|
||
else if(typeof U['edge'] == 'string') R.StartOn = { Edge: U['edge'] };
|
||
else if(typeof U['EPUBCFI'] == 'string') E.add('bibi:readied', () => { if(X['EPUBCFI']) R.StartOn = X['EPUBCFI'].getDestination(U['EPUBCFI']); });
|
||
};
|
||
|
||
U.initialize.parseDataString = (DataString) => {
|
||
if(typeof DataString != 'string' || !DataString) return null;
|
||
const ParsedData = {}; let HasData = false;
|
||
DataString.split('&').forEach(PnV => {
|
||
const DD = U.translateData(PnV.split('='));
|
||
if(DD && DD[1] != undefined) ParsedData[DD[0]] = DD[1], HasData = true;
|
||
});
|
||
return HasData ? ParsedData : null;
|
||
};
|
||
|
||
|
||
|
||
//==============================================================================================================================================
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
//-- Document-Defined Settings (Bookshelf, Book, Book-Data)
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
|
||
export const D = {};
|
||
|
||
|
||
D.initialize = () => {
|
||
const Bookshelf = document.getElementById('bibi-preset').getAttribute('data-bibi-bookshelf');
|
||
if(Bookshelf) {
|
||
D['bookshelf'] = new URL(Bookshelf, location.href.split('?')[0]);
|
||
//delete P['bookshelf'];
|
||
}
|
||
const Book = document.body.getAttribute('data-bibi-book');
|
||
if(Book) {
|
||
D['book'] = document.body.getAttribute('data-bibi-book');
|
||
}
|
||
const BookDataElement = document.getElementById('bibi-book-data');
|
||
if(BookDataElement) {
|
||
const BookData = BookDataElement.innerText.trim();
|
||
if(BookData) {
|
||
const BookDataMIMEType = BookDataElement.getAttribute('data-bibi-book-mimetype');
|
||
if(/^application\/(epub\+zip|zip|x-zip(-compressed)?)$/i.test(BookDataMIMEType)) {
|
||
D['book-data'] = BookData;
|
||
D['book-data-mimetype'] = BookDataMIMEType;
|
||
}
|
||
}
|
||
BookDataElement.innerHTML = '';
|
||
BookDataElement.parentNode.removeChild(BookDataElement);
|
||
}
|
||
if(D['book'] || D['book-data']) {
|
||
//delete U['book'];
|
||
let HRef = location.href.replace(/([\?&])book=[^&]*&?/, '$1');
|
||
if(!HRef.split('?')[1]) HRef = HRef.split('?')[0];
|
||
history.replaceState(null, document.title, HRef);
|
||
}
|
||
delete D.initialize;
|
||
};
|
||
|
||
|
||
|
||
|
||
//==============================================================================================================================================
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
//-- Settings
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
|
||
export const S = {}; // Bibi.Settings
|
||
|
||
|
||
S.initialize = () => {
|
||
for(const Pro in S) if(typeof S[Pro] != 'function') delete S[Pro];
|
||
sML.applyRtL(S, P, 'ExceptFunctions');
|
||
sML.applyRtL(S, U, 'ExceptFunctions');
|
||
sML.applyRtL(S, D, 'ExceptFunctions');
|
||
Bibi.SettingTypes['yes-no'].concat(Bibi.SettingTypes_PresetOnly['yes-no']).concat(Bibi.SettingTypes_UserOnly['yes-no']).forEach(Pro => S[Pro] = (S[Pro] == 'yes' || (S[Pro] == 'mobile' && O.TouchOS) || (S[Pro] == 'desktop' && !O.TouchOS)));
|
||
// --------
|
||
if(!S['trustworthy-origins'].includes(O.Origin)) S['trustworthy-origins'].unshift(O.Origin);
|
||
// --------
|
||
S['book'] = (!S['book-data'] && typeof S['book'] == 'string' && S['book']) ? new URL(S['book'], S['bookshelf'] + '/').href : '';
|
||
if(!S['book-data'] && S['book'] && !S['trustworthy-origins'].includes(new URL(S['book']).origin)) throw `The Origin of the Path of the Book Is Not Allowed.`;
|
||
// --------
|
||
if(typeof S['parent-bibi-index'] != 'number') delete S['parent-bibi-index'];
|
||
// --------
|
||
if(S['book'] || !window.File) S['accept-local-file'] = false, S['accept-blob-converted-data'] = false, S['accept-base64-encoded-data'] = false;
|
||
else S['accept-local-file'] = S['accept-local-file'] && (S['extract-if-necessary'].includes('*') || S['extract-if-necessary'].includes('.epub') || S['extract-if-necessary'].includes('.zip')) ? true : false;
|
||
// --------
|
||
S['autostart'] = S['wait'] ? false : !S['book'] ? true : window.parent != window ? S['autostart-embedded'] : S['autostart'];
|
||
S['start-in-new-window'] = (window.parent != window && !S['autostart']) ? S['start-embedded-in-new-window'] : false;
|
||
// --------
|
||
S['default-page-progression-direction'] = S['default-page-progression-direction'] == 'rtl' ? 'rtl' : 'ltr';
|
||
['history', 'bookmarks'].forEach(_ => {
|
||
if( S['max-' + _] == 0) S['use-' + _] = false;
|
||
if(!S['use-' + _] ) S['max-' + _] = 0;
|
||
});
|
||
if(!S['use-menubar']) S['use-full-height'] = true;
|
||
// --------
|
||
if(sML.UA.Trident || sML.UA.EdgeHTML) S['pagination-method'] = 'auto';
|
||
// --------
|
||
if(!S['reader-view-mode']) S['reader-view-mode'] = 'paged';
|
||
if(O.Biscuits) E.bind('bibi:initialized-book', () => {
|
||
const BookBiscuits = O.Biscuits.remember('Book');
|
||
if(S['keep-settings']) {
|
||
if(!U['reader-view-mode'] && BookBiscuits.RVM) S['reader-view-mode'] = BookBiscuits.RVM;
|
||
if(!U['full-breadth-layout-in-scroll'] && BookBiscuits.FBL) S['full-breadth-layout-in-scroll'] = BookBiscuits.FBL;
|
||
}
|
||
if(S['resume-from-last-position']) {
|
||
if(!R.StartOn && BookBiscuits.Position && BookBiscuits.Position.IIPP) R.StartOn = sML.clone(BookBiscuits.Position);
|
||
}
|
||
});
|
||
// --------
|
||
S.Modes = { // 'Mode': { SH: 'ShortHand', CNP: 'ClassNamePrefix' }
|
||
'book-rendition-layout' : { SH: 'BRL', CNP: 'book' },
|
||
'reader-view-mode' : { SH: 'RVM', CNP: 'view' },
|
||
'page-progression-direction' : { SH: 'PPD', CNP: 'page' },
|
||
'spread-layout-axis' : { SH: 'SLA', CNP: 'spread' },
|
||
'spread-layout-direction' : { SH: 'SLD', CNP: 'spread' },
|
||
'apparent-reading-axis' : { SH: 'ARA', CNP: 'appearance' },
|
||
'apparent-reading-direction' : { SH: 'ARD', CNP: 'appearance' },
|
||
'navigation-layout-direction': { SH: 'NLD', CNP: 'nav' }
|
||
};
|
||
for(const Mode in S.Modes) {
|
||
const _ = S.Modes[Mode];
|
||
Object.defineProperty(S, _.SH, { get: () => S[Mode], set: (Val) => S[Mode] = Val });
|
||
delete _.SH;
|
||
}
|
||
// --------
|
||
E.dispatch('bibi:initialized-settings');
|
||
};
|
||
|
||
|
||
S.update = (Settings) => {
|
||
const Prev = {}; for(const Mode in S.Modes) Prev[Mode] = S[Mode];
|
||
if(typeof Settings == 'object') for(const Property in Settings) if(typeof S[Property] != 'function') S[Property] = Settings[Property];
|
||
S['book-rendition-layout'] = B.Package.Metadata['rendition:layout'];
|
||
S['allow-placeholders'] = (S['allow-placeholders'] && B.AllowPlaceholderItems);
|
||
if(S.FontFamilyStyleIndex) sML.deleteCSSRule(S.FontFamilyStyleIndex);
|
||
if(S['ui-font-family']) S.FontFamilyStyleIndex = sML.appendCSSRule('html', 'font-family: ' + S['ui-font-family'] + ' !important;');
|
||
S['page-progression-direction'] = B.PPD;
|
||
if(S['pagination-method'] == 'x') {
|
||
S['spread-layout-axis'] = S['reader-view-mode'] == 'vertical' ? 'vertical' : 'horizontal';
|
||
} else {
|
||
S['spread-layout-axis'] = (() => {
|
||
if(S['reader-view-mode'] != 'paged') return S['reader-view-mode'];
|
||
if(S['book-rendition-layout'] == 'reflowable') switch(B.WritingMode) {
|
||
case 'tb-rl': case 'tb-lr': return 'vertical'; ////
|
||
} return 'horizontal';
|
||
})();
|
||
}
|
||
S['apparent-reading-axis'] = (S['reader-view-mode'] == 'paged' ) ? 'horizontal' : S['reader-view-mode'];
|
||
S['apparent-reading-direction'] = (S['reader-view-mode'] == 'vertical') ? 'ttb' : S['page-progression-direction'];
|
||
S['spread-layout-direction'] = (S['spread-layout-axis'] == 'vertical') ? 'ttb' : S['page-progression-direction'];
|
||
S['navigation-layout-direction'] = (S['fix-nav-ttb'] || S['page-progression-direction'] != 'rtl') ? 'ttb' : 'rtl';
|
||
//S['spread-gap'] = S['book-rendition-layout'] == 'reflowable' ? S['spread-gap_reflowable'] : S['spread-gap_pre-paginated'];
|
||
for(const Mode in S.Modes) {
|
||
const Pfx = S.Modes[Mode].CNP + '-', PC = Pfx + Prev[Mode], CC = Pfx + S[Mode];
|
||
if(PC != CC) O.HTML.classList.remove(PC);
|
||
O.HTML.classList.add(CC);
|
||
}
|
||
C.update();
|
||
E.dispatch('bibi:updated-settings', S);
|
||
};
|
||
|
||
|
||
|
||
|
||
//==============================================================================================================================================
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
//-- Compass
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
export const C = {};
|
||
|
||
|
||
C.update = () => {
|
||
C.probe('L', S['spread-layout-axis'] ); // Rules in "L"ayout
|
||
C.probe('A', S['apparent-reading-axis']); // Rules in "A"ppearance
|
||
C.DDD = (() => { switch(S.PPD) { // DDD: Direction-Distance Dictionary
|
||
case 'ltr': return S.ARD != 'ttb' ? { 'left': -1 , 'right': 1 , 'top': '-1', 'bottom': '1' } : { 'left': '-1', 'right': '1', 'top': -1 , 'bottom': 1 };
|
||
case 'rtl': return S.ARD != 'ttb' ? { 'left': 1 , 'right': -1 , 'top': '-1', 'bottom': '1' } : { 'left': '1', 'right':'-1', 'top': -1 , 'bottom': 1 };
|
||
} })();
|
||
};
|
||
|
||
C.probe = (L_A, AXIS) => {
|
||
const LR_RL = ['left', 'right']; if(S.PPD != 'ltr') LR_RL.reverse();
|
||
if(AXIS == 'horizontal') {
|
||
C._app(L_A, 'BASE', { b: LR_RL[0], a: LR_RL[1], s: 'top', e: 'bottom' });
|
||
C._app(L_A, 'SIZE', { b: 'height', l: 'width' });
|
||
C._app(L_A, 'OOBL', { b: 'top', l: 'left' });
|
||
C._app(L_A, 'OEBL', { b: 'bottom', l: 'right' });
|
||
C._app(L_A, 'AXIS', { b: 'y', l: 'x' }); C[L_A + '_AXIS_D'] = S.PPD == 'ltr' ? 1 : -1;
|
||
} else {
|
||
C._app(L_A, 'BASE', { b: 'top', a: 'bottom', s: LR_RL[0], e: LR_RL[1] });
|
||
C._app(L_A, 'SIZE', { b: 'width', l: 'height' });
|
||
C._app(L_A, 'OOBL', { b: 'left', l: 'top' });
|
||
C._app(L_A, 'OEBL', { b: 'right', l: 'bottom' });
|
||
C._app(L_A, 'AXIS', { b: 'x', l: 'y' }); C[L_A + '_AXIS_D'] = 1;
|
||
}
|
||
// BASE: Directions ("B"efore-"A"fter-"S"tart-"E"nd. Top-Bottom-Left-Right on TtB, Left-Right-Top-Bottom on LtR, and Right-Left-Top-Bottom on RtL.)
|
||
// SIZE: Breadth, Length (Width-Height on TtB, Height-Width on LtR and RtL.)
|
||
// OOBL: "O"ffset "O"rigin of "B"readth and "L"ength
|
||
// OOBL: "O"ffset "E"nd of "B"readth and "L"ength
|
||
// AXIS: X or Y for Breadth and Length (X-Y on TtB, Y-X on LtR and RtL), and ±1 for Culcuration of Length (1 on TtB and LtR, -1 on RtL.)
|
||
};
|
||
|
||
C._app = (L_A, Gauge, Par) => {
|
||
for(const Pro in Par) C[[L_A, Gauge, Pro ].join('_')] = Par[Pro] ,
|
||
C[[L_A, Gauge, sML.capitalise(Pro)].join('_')] = sML.capitalise(Par[Pro]);
|
||
};
|
||
|
||
C.d2d = (Dir, AOD) => { const Dist = C.DDD[Dir]; return AOD ? Dist * 1 : typeof Dist == 'number' ? Dist : 0; }; // d2d: Direction to Distance // AOD: Allow Orthogonal Direction
|
||
|
||
|
||
|
||
//==============================================================================================================================================
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
//-- Operation Utilities
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
|
||
export const O = {}; // Bibi.Operator
|
||
|
||
|
||
O.log = (Log, A2, A3) => { let Obj = '', Tag = '';
|
||
if(A3) Obj = A2, Tag = A3;
|
||
else if(/^<..>$/.test(A2)) Tag = A2;
|
||
else if(A2) Obj = A2;
|
||
switch(Tag) {
|
||
case '<e/>': return console.error(Log);
|
||
case '</g>': O.log.Depth--;
|
||
}
|
||
if(
|
||
(Log || Obj)
|
||
&&
|
||
(O.log.Depth <= O.log.Limit || Tag == '<b:>' || Tag == '</b>' || Tag == '<*/>')
|
||
) {
|
||
const Time = (O.log.Depth <= 1) ? O.stamp(Log) : 0;
|
||
let Ls = [], Ss = [];
|
||
if(Log) switch(Tag) {
|
||
case '<b:>': Ls.unshift(`📕`); Ls.push('%c' + Log), Ss.push(O.log.BStyle); Ls.push(`%c(v${ Bibi['version'] })` + (Bibi.Dev ? ':%cDEV' : '')), Ss.push(O.log.NStyle); if(Bibi.Dev) Ss.push(O.log.BStyle); break;
|
||
case '</b>': Ls.unshift(`📖`); Ls.push('%c' + Log), Ss.push(O.log.BStyle); if(O.log.Limit) Ls.push(`%c(${ Math.floor(Time / 1000) + '.' + String(Time % 1000).padStart(3, 0) }sec)`), Ss.push(O.log.NStyle); break;
|
||
case '<g:>': Ls.unshift(`┌`); Ls.push(Log); break;
|
||
case '</g>': Ls.unshift(`└`); Ls.push(Log); break;
|
||
//case '<o/>': Ls.unshift( `>`); Ls.push(Log); break;
|
||
default : Ls.unshift( `-`); Ls.push(Log);
|
||
}
|
||
for(let i = O.log.Depth; i > 1; i--) Ls.unshift('│');
|
||
Ls.unshift('%cBibi:'); Ss.unshift(O.log.NStyle);
|
||
switch(Tag) {
|
||
//case '<o/>': O.log.log('groupCollapsed', Ls, Ss); console.log(Obj); console.groupEnd(); break;
|
||
default : O.log.log('log', Ls, Ss, Obj );
|
||
}
|
||
}
|
||
switch(Tag) {
|
||
case '<g:>': O.log.Depth++;
|
||
}
|
||
};
|
||
|
||
O.log.initialize = () => {
|
||
if(parent && parent != window) return O.log = () => true;
|
||
O.log.Limit = U.hasOwnProperty('log') && typeof (U['log'] *= 1) == 'number' ? U['log'] : 0;
|
||
O.log.Depth = 1;
|
||
O.log.NStyle = 'font: normal normal 10px/1 Menlo, Consolas, monospace;';
|
||
O.log.BStyle = 'font: normal bold 10px/1 Menlo, Consolas, monospace;';
|
||
O.log.distill = (sML.UA.Trident || sML.UA.EdgeHTML) ?
|
||
(Logs, Styles) => [Logs.join(' ').replace(/%c/g, '')] : // Ignore Styles
|
||
(Logs, Styles) => [Logs.join(' ') ].concat(Styles);
|
||
O.log.log = (Method, Logs, Styles, Obj) => {
|
||
const Args = O.log.distill(Logs, Styles);
|
||
if(Obj) Args.push(Obj);
|
||
console[Method].apply(console, Args);
|
||
};
|
||
};
|
||
/*
|
||
O.logSets = (...Args) => {
|
||
let Repeats = [], Sets = []; Sets.length = 1;
|
||
Args.reverse();
|
||
for(let i = 0; i < Args.length; i++) {
|
||
if(!Array.isArray(Args[i])) Args[i] = [Args[i]];
|
||
Repeats[i] = Sets.length;
|
||
Sets.length = Sets.length * Args[i].length;
|
||
}
|
||
Args.reverse(), Repeats.reverse();
|
||
for(let i = 0; i < Sets.length; i++) Sets[i] = '';
|
||
Args.forEach((_AA, i) => {
|
||
let s = 0;
|
||
while(s < Sets.length) _AA.forEach(_A => {
|
||
let r = Repeats[i];
|
||
while(r--) Sets[s++] += _A;
|
||
});
|
||
});
|
||
Sets.forEach(Set => console.log('- ' + Set + ': ' + eval(Set)));
|
||
};*/
|
||
|
||
|
||
O.error = (Err) => {
|
||
O.Busy = false;
|
||
O.HTML.classList.remove('busy');
|
||
O.HTML.classList.remove('loading');
|
||
O.HTML.classList.remove('waiting');
|
||
I.note(Err, 99999999999, 'ErrorOccured');
|
||
O.log(Err, '<e/>');
|
||
E.dispatch('bibi:x_x', typeof Err == 'string' ? new Error(Err) : Err);
|
||
};
|
||
|
||
|
||
O.TimeCard = {};
|
||
|
||
O.getTimeLabel = (TimeFromOrigin = Date.now() - Bibi.TimeOrigin) => [
|
||
TimeFromOrigin / 1000 / 60 / 60,
|
||
TimeFromOrigin / 1000 / 60 % 60,
|
||
TimeFromOrigin / 1000 % 60
|
||
].map(Val => String(Math.floor(Val)).padStart(2, 0)).join(':') + '.' + String(TimeFromOrigin % 1000).padStart(3, 0);
|
||
|
||
O.stamp = (What, TimeCard = O.TimeCard) => {
|
||
const TimeFromOrigin = Date.now() - Bibi.TimeOrigin;
|
||
const TimeLabel = O.getTimeLabel(TimeFromOrigin);
|
||
if(!TimeCard[TimeLabel]) TimeCard[TimeLabel] = [];
|
||
TimeCard[TimeLabel].push(What);
|
||
return TimeFromOrigin;
|
||
};
|
||
|
||
|
||
O.isToBeExtractedIfNecessary = (Path) => {
|
||
if(!Path || !S['extract-if-necessary'].length) return false;
|
||
if(S['extract-if-necessary'].includes('*')) return true;
|
||
if(S['extract-if-necessary'].includes( '')) return !/(\.[\w\d]+)+$/.test(Path);
|
||
for(let l = S['extract-if-necessary'].length, i = 0; i < l; i++) if(new RegExp(S['extract-if-necessary'][i].replace(/\./g, '\\.') + '$', 'i').test(Path)) return true;
|
||
return false;
|
||
};
|
||
|
||
|
||
O.src = (Source) => {
|
||
if(!B.Package.Manifest[Source.Path]) B.Package.Manifest[Source.Path] = Source;
|
||
if(!Source['media-type']) Source['media-type'] = O.getContentType(Source.Path);
|
||
return B.Package.Manifest[Source.Path];
|
||
};
|
||
|
||
|
||
O.RangeLoader = null;
|
||
|
||
O.cancelExtraction = (Source) => {
|
||
if(Source.Resources) Source.Resources.forEach(Res => Res.Retlieved ? Promise.resolve() : O.RangeLoader.abort(Res.Path));
|
||
return Source.Retlieved ? Promise.resolve() : O.RangeLoader.abort(Source.Path);
|
||
};
|
||
|
||
O.extract = (Source) => {
|
||
Source = O.src(Source);
|
||
if(Source.Retlieving) return Source.Retlieving;
|
||
if(Source.Content) return Promise.resolve(Source);
|
||
if(Source.URI) return O.download(Source);
|
||
return Source.Retlieving = O.RangeLoader.getBuffer(Source.Path).then(ABuf => {
|
||
if(O.isBin(Source)) Source.DataType = 'Blob', Source.Content = new Blob([ABuf], { type: Source['media-type'] });
|
||
else Source.DataType = 'Text', Source.Content = new TextDecoder('utf-8').decode(new Uint8Array(ABuf));
|
||
Source.Retlieved = true;
|
||
delete Source.Retlieving;
|
||
return Source;
|
||
}).catch(Err => {
|
||
delete Source.Retlieving;
|
||
return Promise.reject(
|
||
/404/.test(Err) ? Bibi.ErrorMessages.NotFound :
|
||
/aborted/.test(Err) ? Bibi.ErrorMessages.Canceled :
|
||
/fetch/.test(Err) ? Bibi.ErrorMessages.CORSBlocked :
|
||
/not found/.test(Err) ? Bibi.ErrorMessages.DataInvalid :
|
||
/invalid/.test(Err) ? Bibi.ErrorMessages.DataInvalid :
|
||
Err);
|
||
});
|
||
};
|
||
|
||
O.download = (Source/*, Opt = {}*/) => {
|
||
Source = O.src(Source);
|
||
if(Source.Retlieving) return Source.Retlieving;
|
||
if(Source.Content) return Promise.resolve(Source);
|
||
const IsBin = O.isBin(Source);
|
||
const XHR = new XMLHttpRequest(); //if(Opt.MimeType) XHR.overrideMimeType(Opt.MimeType);
|
||
const RemotePath = Source.URI ? Source.URI : (/^([a-z]+:\/\/|\/)/.test(Source.Path) ? '' : B.Path + '/') + Source.Path;
|
||
return Source.Retlieving = new Promise((resolve, reject) => {
|
||
XHR.open('GET', RemotePath, true);
|
||
XHR.responseType = IsBin ? 'blob' : 'text';
|
||
XHR.onloadend = () => XHR.status == 200 ? resolve() : reject();
|
||
XHR.onerror = () => reject();
|
||
XHR.send(null);
|
||
}).then(() => {
|
||
Source.DataType = IsBin ? 'Blob' : 'Text', Source.Content = XHR.response;
|
||
Source.Retlieved = true;
|
||
delete Source.Retlieving;
|
||
return Source;
|
||
}).catch(() => {
|
||
delete Source.Retlieving;
|
||
return Promise.reject(
|
||
XHR.status == 404 ? Bibi.ErrorMessages.NotFound :
|
||
XHR.status == 0 ? Bibi.ErrorMessages.CORSBlocked :
|
||
XHR.status + ' ' + XHR.statusText);
|
||
});
|
||
};
|
||
|
||
O.tryRangeRequest = (Path = Bibi.Script.src, Bytes = '0-0') => new Promise((resolve, reject) => {
|
||
const XHR = new XMLHttpRequest();
|
||
XHR.onloadend = () => XHR.status != 206 ? reject() : resolve();
|
||
XHR.open('GET', Path, true);
|
||
XHR.setRequestHeader('Range', 'bytes=' + Bytes);
|
||
XHR.send(null);
|
||
});
|
||
|
||
O.file = (Source, Opt = {}) => new Promise((resolve, reject) => {
|
||
Source = O.src(Source);
|
||
if(Opt.URI && Source.URI) return resolve(Source);
|
||
(() => {
|
||
if(Source.Content) return Promise.resolve(Source);
|
||
if(Source.URI || !B.ExtractionPolicy) return O.download(Source);
|
||
switch(B.ExtractionPolicy) {
|
||
case 'on-the-fly': return O.extract(Source);
|
||
case 'at-once': return Promise.reject(`File Not Included: "${ Source.Path }"`);
|
||
}
|
||
})().then(Source => {
|
||
if(typeof Opt.initialize == 'function') Opt.initialize(Source);
|
||
return (Opt.Preprocess && !Source.Preprocessed) ? O.preprocess(Source) : Source;
|
||
}).then(Source => {
|
||
if(Opt.URI) {
|
||
if(!Source.URI) Source.URI = O.createBlobURL(Source.DataType, Source.Content, Source['media-type']);
|
||
Source.Content = '';
|
||
}
|
||
resolve(Source);
|
||
}).catch(reject);
|
||
});
|
||
|
||
|
||
O.isBin = (Source) => /\.(aac|gif|jpe?g|m4[av]|mp[g34]|ogg|[ot]tf|pdf|png|web[mp]|woff2?)$/i.test(Source.Path);
|
||
|
||
O.createBlobURL = (DT, CB, MT) => URL.createObjectURL(DT == 'Text' ? new Blob([CB], { type: MT }) : CB);
|
||
|
||
|
||
O.ContentTypes = {
|
||
'pdf' : 'application/pdf',
|
||
'xht(ml)?': 'application/xhtml+xml',
|
||
'xml' : 'application/xml',
|
||
'aac' : 'audio/aac',
|
||
'mp3' : 'audio/mpeg',
|
||
'otf' : 'font/opentype',
|
||
'ttf' : 'font/truetype',
|
||
'woff' : 'font/woff',
|
||
'woff2' : 'font/woff2',
|
||
'gif' : 'image/gif',
|
||
'jpe?g' : 'image/jpeg',
|
||
'png' : 'image/png',
|
||
'svg' : 'image/svg+xml',
|
||
'webp' : 'image/webp',
|
||
'css' : 'text/css',
|
||
'js' : 'text/javascript',
|
||
'html?' : 'text/html',
|
||
'mp4' : 'video/mp4',
|
||
'webm' : 'video/webm',
|
||
'ya?ml' : 'application/x-yaml'
|
||
};
|
||
|
||
O.getContentType = (FileName) => {
|
||
for(const Ext in O.ContentTypes) if(new RegExp('\\.' + Ext + '$').test(FileName)) return O.ContentTypes[Ext];
|
||
return null;
|
||
};
|
||
|
||
|
||
O.preprocess = (Source) => {
|
||
Source = O.src(Source);
|
||
const Resources = [];
|
||
const Setting = O.preprocess.getSetting(Source.Path);
|
||
if(!Setting) {
|
||
return Promise.resolve(Source);
|
||
}
|
||
const Promises = [];
|
||
if(Setting.ReplaceRules) Source.Content = Setting.ReplaceRules.reduce((SourceContent, Rule) => SourceContent.replace(Rule[0], Rule[1]), Source.Content);
|
||
if(Setting.ResolveRules) { // RRR
|
||
const FileDir = Source.Path.replace(/\/?[^\/]+$/, '');
|
||
Setting.ResolveRules.forEach(ResolveRule => ResolveRule.Patterns.forEach(Pattern => {
|
||
const ResRE = ResolveRule.getRE(Pattern.Attribute);
|
||
const Reses = Source.Content.match(ResRE);
|
||
if(!Reses) return;
|
||
const ExtRE = new RegExp('\\.(' + Pattern.Extensions + ')$', 'i');
|
||
Reses.forEach(Res => {
|
||
const ResPathInSource = Res.replace(ResRE, ResolveRule.PathRef);
|
||
const ResPaths = O.getPath(FileDir, (!/^(\.*\/+|#)/.test(ResPathInSource) ? './' : '') + ResPathInSource).split('#');
|
||
if(!ExtRE.test(ResPaths[0])) return;
|
||
const Resource = O.src({ Path: ResPaths[0] });
|
||
Resources.push(Resource);
|
||
Promises.push(O.file(Resource, { Preprocess: true, URI: true }).then(ChildSource => {
|
||
ResPaths[0] = ChildSource.URI;
|
||
Source.Content = Source.Content.replace(Res, Res.replace(ResPathInSource, ResPaths.join('#')));
|
||
}));
|
||
});
|
||
}));
|
||
}
|
||
return Promise.all(Promises).then(() => {
|
||
Source.Preprocessed = true;
|
||
Source.Resources = Resources;
|
||
return Source;
|
||
});
|
||
};
|
||
|
||
O.preprocess.getSetting = (FilePath) => { const PpSs = O.preprocess.Settings;
|
||
for(const Ext in PpSs) if(new RegExp('\\.(' + Ext + ')$', 'i').test(FilePath)) return typeof PpSs[Ext].init == 'function' ? PpSs[Ext].init() : PpSs[Ext];
|
||
return null;
|
||
};
|
||
|
||
O.preprocess.Settings = {
|
||
'css': {
|
||
ReplaceRules: [
|
||
[/\/\*[.\s\S]*?\*\/|[^\{\}]+\{\s*\}/gm, ''],
|
||
[/[\r\n]+/g, '\n']
|
||
],
|
||
ResolveRules: [{
|
||
getRE: () => /@import\s+["'](?!(?:https?|data):)(.+?)['"]/g,
|
||
PathRef: '$1',
|
||
Patterns: [
|
||
{ Extensions: 'css' }
|
||
]
|
||
}, {
|
||
getRE: () => /@import\s+url\(["']?(?!(?:https?|data):)(.+?)['"]?\)/g,
|
||
PathRef: '$1',
|
||
Patterns: [
|
||
{ Extensions: 'css' }
|
||
]
|
||
}, {
|
||
getRE: () => /url\(["']?(?!(?:https?|data):)(.+?)['"]?\)/g,
|
||
PathRef: '$1',
|
||
Patterns: [
|
||
{ Extensions: 'gif|png|jpe?g|svg|ttf|otf|woff' }
|
||
]
|
||
}],
|
||
init: function() { const RRs = this.ReplaceRules;
|
||
RRs.push([/(-(epub|webkit)-)?column-count\s*:\s*1\s*([;\}])/gm, 'column-count: auto$3']);
|
||
RRs.push([/(-(epub|webkit)-)?text-underline-position\s*:/gm, 'text-underline-position:']);
|
||
if(sML.UA.Chromium || sML.UA.WebKit) {
|
||
return this;
|
||
}
|
||
RRs.push([/-(epub|webkit)-/gm, '']);
|
||
if(sML.UA.Gecko) {
|
||
RRs.push([/text-combine-horizontal\s*:\s*([^;\}]+)\s*([;\}])/gm, 'text-combine-upright: $1$2']);
|
||
RRs.push([/text-combine\s*:\s*horizontal\s*([;\}])/gm, 'text-combine-upright: all$1']);
|
||
return this;
|
||
}
|
||
if(sML.UA.EdgeHTML) {
|
||
RRs.push([/text-combine-(upright|horizontal)\s*:\s*([^;\}\s]+)\s*([;\}])/gm, 'text-combine-horizontal: $2; text-combine-upright: $2$3']);
|
||
RRs.push([/text-combine\s*:\s*horizontal\s*([;\}])/gm, 'text-combine-horizontal: all; text-combine-upright: all$1']);
|
||
}
|
||
if(sML.UA.Trident) {
|
||
RRs.push([/writing-mode\s*:\s*vertical-rl\s*([;\}])/gm, 'writing-mode: tb-rl$1']);
|
||
RRs.push([/writing-mode\s*:\s*vertical-lr\s*([;\}])/gm, 'writing-mode: tb-lr$1']);
|
||
RRs.push([/writing-mode\s*:\s*horizontal-tb\s*([;\}])/gm, 'writing-mode: lr-tb$1']);
|
||
RRs.push([/text-combine-(upright|horizontal)\s*:\s*([^;\}\s]+)\s*([;\}])/gm, '-ms-text-combine-horizontal: $2$3']);
|
||
RRs.push([/text-combine\s*:\s*horizontal\s*([;\}])/gm, '-ms-text-combine-horizontal: all$1']);
|
||
}
|
||
if(/^(zho?|chi|kor?|ja|jpn)$/.test(B.Language)) {
|
||
RRs.push([/text-align\s*:\s*justify\s*([;\}])/gm, 'text-align: justify; text-justify: inter-ideograph$1']);
|
||
}
|
||
//delete this.init;
|
||
return this;
|
||
}
|
||
},
|
||
'svg': {
|
||
ReplaceRules: [
|
||
[/<!--\s+[.\s\S]*?\s+-->/gm, '']
|
||
],
|
||
ResolveRules: [{
|
||
getRE: (Att) => new RegExp('<\\??[a-zA-Z:\\-]+[^>]*? (' + Att + ')\\s*=\\s*["\'](?!(?:https?|data):)(.+?)[\'"]', 'g'),
|
||
PathRef: '$2',
|
||
Patterns: [
|
||
{ Attribute: 'href', Extensions: 'css' },
|
||
{ Attribute: 'src', Extensions: 'svg' },
|
||
{ Attribute: 'src|xlink:href', Extensions: 'gif|png|jpe?g' }
|
||
]
|
||
}]
|
||
},
|
||
'html?|xht(ml)?|xml': {
|
||
ReplaceRules: [
|
||
[/<!--\s+[.\s\S]*?\s+-->/gm, '']
|
||
],
|
||
ResolveRules: [{
|
||
getRE: (Att) => new RegExp('<\\??[a-zA-Z:\\-]+[^>]*? (' + Att + ')\\s*=\\s*["\'](?!(?:https?|data):)(.+?)[\'"]', 'g'),
|
||
PathRef: '$2',
|
||
Patterns: [
|
||
{ Attribute: 'href', Extensions: 'css' },
|
||
{ Attribute: 'src', Extensions: 'js|svg' },
|
||
{ Attribute: 'src|xlink:href', Extensions: 'gif|png|jpe?g|mp([34]|e?g)|m4[av]' },
|
||
{ Attribute: 'poster', Extensions: 'gif|png|jpe?g' }
|
||
]
|
||
}]
|
||
}
|
||
};
|
||
|
||
|
||
O.openDocument = (Source) => O.file(Source).then(Source => (new DOMParser()).parseFromString(Source.Content, /\.(xml|opf|ncx)$/i.test(Source.Path) ? 'text/xml' : 'text/html'));
|
||
|
||
|
||
O.editCSSRules = function() {
|
||
let Doc, fun;
|
||
if(typeof arguments[0] == 'function') Doc = arguments[1], fun = arguments[0];
|
||
else if(typeof arguments[1] == 'function') Doc = arguments[0], fun = arguments[1];
|
||
if(!Doc) Doc = document;
|
||
if(!Doc.styleSheets || typeof fun != 'function') return;
|
||
sML.forEach(Doc.styleSheets)(StyleSheet => O.editCSSRulesOfStyleSheet(StyleSheet, fun));
|
||
};
|
||
|
||
O.editCSSRulesOfStyleSheet = (StyleSheet, fun) => {
|
||
try{ if(!StyleSheet.cssRules) return; } catch(_) { return; }
|
||
for(let l = StyleSheet.cssRules.length, i = 0; i < l; i++) {
|
||
const CSSRule = StyleSheet.cssRules[i];
|
||
/**/ if(CSSRule.cssRules) O.editCSSRulesOfStyleSheet(CSSRule, fun);
|
||
else if(CSSRule.styleSheet) O.editCSSRulesOfStyleSheet(CSSRule.styleSheet, fun);
|
||
else fun(CSSRule );
|
||
}
|
||
};
|
||
|
||
|
||
O.getWritingMode = (Ele) => {
|
||
const CS = getComputedStyle(Ele);
|
||
if(!O.WritingModeProperty) return (CS['direction'] == 'rtl' ? 'rl-tb' : 'lr-tb');
|
||
else if( /^vertical-/.test(CS[O.WritingModeProperty])) return (CS['direction'] == 'rtl' ? 'bt' : 'tb') + '-' + (/-lr$/.test(CS[O.WritingModeProperty]) ? 'lr' : 'rl');
|
||
else if( /^horizontal-/.test(CS[O.WritingModeProperty])) return (CS['direction'] == 'rtl' ? 'rl' : 'lr') + '-' + (/-bt$/.test(CS[O.WritingModeProperty]) ? 'bt' : 'tb');
|
||
else if(/^(lr|rl|tb|bt)-/.test(CS[O.WritingModeProperty])) return CS[O.WritingModeProperty];
|
||
};
|
||
|
||
|
||
O.getElementInnerText = (Ele) => {
|
||
let InnerText = 'InnerText';
|
||
const Copy = document.createElement('div');
|
||
Copy.innerHTML = Ele.innerHTML.replace(/ (src(set)?|source|(xlink:)?href)=/g, ' data-$1=');
|
||
sML.forEach(Copy.querySelectorAll('svg' ))(Ele => Ele.parentNode.removeChild(Ele));
|
||
sML.forEach(Copy.querySelectorAll('video' ))(Ele => Ele.parentNode.removeChild(Ele));
|
||
sML.forEach(Copy.querySelectorAll('audio' ))(Ele => Ele.parentNode.removeChild(Ele));
|
||
sML.forEach(Copy.querySelectorAll('img' ))(Ele => Ele.parentNode.removeChild(Ele));
|
||
sML.forEach(Copy.querySelectorAll('script'))(Ele => Ele.parentNode.removeChild(Ele));
|
||
sML.forEach(Copy.querySelectorAll('style' ))(Ele => Ele.parentNode.removeChild(Ele));
|
||
/**/ if(typeof Copy.textContent != 'undefined') InnerText = Copy.textContent;
|
||
else if(typeof Copy.innerText != 'undefined') InnerText = Copy.innerText;
|
||
return InnerText.replace(/[\r\n\s\t ]/g, '');
|
||
};
|
||
|
||
|
||
O.getElementCoord = (Ele, OPa) => {
|
||
const Coord = { X: Ele.offsetLeft, Y: Ele.offsetTop };
|
||
OPa = OPa && OPa.tagName ? OPa : null;
|
||
while(Ele.offsetParent != OPa) Ele = Ele.offsetParent, Coord.X += Ele.offsetLeft, Coord.Y += Ele.offsetTop;
|
||
return Coord;
|
||
};
|
||
|
||
|
||
O.getViewportZooming = () => document.body.clientWidth / window.innerWidth;
|
||
|
||
|
||
O.getPath = function() {
|
||
let Origin = '', Path = arguments[0];
|
||
if(arguments.length == 2 && /^[\w\d]+:\/\//.test(arguments[1])) Path = arguments[1];
|
||
else for(let l = arguments.length, i = 1; i < l; i++) Path += '/' + arguments[i];
|
||
Path.replace(/^([a-zA-Z]+:\/\/[^\/]+)?\/*(.*)$/, (M, P1, P2) => { Origin = P1, Path = P2; });
|
||
while(/([^:\/])\/{2,}/.test(Path)) Path = Path.replace(/([^:\/])\/{2,}/g, '$1/');
|
||
while( /\/\.\//.test(Path)) Path = Path.replace( /\/\.\//g, '/');
|
||
while(/[^\/]+\/\.\.\//.test(Path)) Path = Path.replace(/[^\/]+\/\.\.\//g, '');
|
||
/**/ Path = Path.replace( /^(\.\/)+/g, '');
|
||
if(Origin) Path = Origin + '/' + Path;
|
||
return Path;
|
||
};
|
||
|
||
|
||
O.fullPath = (FilePath) => B.Path + B.PathDelimiter + FilePath;
|
||
|
||
|
||
O.getViewportByMetaContent = (Str) => {
|
||
if(typeof Str == 'string' && /width/.test(Str) && /height/.test(Str)) {
|
||
Str = Str.replace(/\s+/g, '');
|
||
const W = Str.replace( /^.*?width=(\d+).*$/, '$1') * 1;
|
||
const H = Str.replace(/^.*?height=(\d+).*$/, '$1') * 1;
|
||
if(!isNaN(W) && !isNaN(H)) return { Width: W, Height: H };
|
||
}
|
||
return null;
|
||
};
|
||
|
||
O.getViewportByViewBox = (Str) => {
|
||
if(typeof Str == 'string') {
|
||
const XYWH = Str.replace(/^\s+/, '').replace(/\s+$/, '').split(/\s+/);
|
||
if(XYWH.length == 4) {
|
||
const W = XYWH[2] * 1;// - XYWH[0] * 1;
|
||
const H = XYWH[3] * 1;// - XYWH[1] * 1;
|
||
if(!isNaN(W) && !isNaN(H)) return { Width: W, Height: H };
|
||
}
|
||
}
|
||
return null;
|
||
};
|
||
|
||
O.getViewportByImage = (Img) => {
|
||
if(Img && /^img$/i.test(Img.tagName)) {
|
||
const ImageStyle = getComputedStyle(Img);
|
||
return { Width: parseInt(ImageStyle.width), Height: parseInt(ImageStyle.height) };
|
||
}
|
||
return null;
|
||
};
|
||
|
||
O.getViewportByOriginalResolution = (Str) => {
|
||
if(typeof Str == 'string') {
|
||
const WH = Str.replace(/\s+/, '').split('x');
|
||
if(WH.length == 2) {
|
||
const W = WH[0] * 1;
|
||
const H = WH[1] * 1;
|
||
if(!isNaN(W) && !isNaN(H)) return { Width: W, Height: H };
|
||
}
|
||
}
|
||
return null;
|
||
};
|
||
|
||
|
||
O.isPointableContent = (Ele) => {
|
||
while(Ele) {
|
||
if(/^(a|audio|video)$/i.test(Ele.tagName)) return true;
|
||
Ele = Ele.parentElement;
|
||
}
|
||
return false;
|
||
};
|
||
|
||
|
||
O.stopPropagation = (Eve) => { Eve.stopPropagation(); return false; };
|
||
|
||
O.preventDefault = (Eve) => { Eve.preventDefault(); return false; };
|
||
|
||
|
||
O.getBibiEvent = (Eve) => {
|
||
if(!Eve) return {};
|
||
const Coord = O.getBibiEventCoord(Eve);
|
||
const FlipperWidth = S['flipper-width'];
|
||
const Ratio = {
|
||
X: Coord.X / window.innerWidth,
|
||
Y: Coord.Y / window.innerHeight
|
||
};
|
||
let BorderT, BorderR, BorderB, BorderL;
|
||
if(FlipperWidth < 1) { // Ratio
|
||
BorderL = BorderT = FlipperWidth;
|
||
BorderR = BorderB = 1 - FlipperWidth;
|
||
} else { // Pixel to Ratio
|
||
BorderL = FlipperWidth / window.innerWidth;
|
||
BorderT = FlipperWidth / window.innerHeight;
|
||
BorderR = 1 - BorderL;
|
||
BorderB = 1 - BorderT;
|
||
}
|
||
const Division = { /* 9: 5 */ };
|
||
if(Ratio.X < BorderL ) Division.X = 'left';//, Division[9] -= 1;
|
||
else if( BorderR < Ratio.X) Division.X = 'right';//, Division[9] += 1;
|
||
else Division.X = 'center';
|
||
if(Ratio.Y < BorderT ) Division.Y = 'top';//, Division[9] -= 3;
|
||
else if( BorderB < Ratio.Y) Division.Y = 'bottom';//, Division[9] += 3;
|
||
else Division.Y = 'middle';
|
||
return {
|
||
Target: Eve.target,
|
||
Coord: Coord,
|
||
Ratio: Ratio,
|
||
Division: Division
|
||
};
|
||
};
|
||
|
||
O.getBibiEventCoord = (Eve) => { let Coord = { X: 0, Y: 0 };
|
||
if(/^touch/.test(Eve.type)) {
|
||
Coord.X = Eve.changedTouches[0].pageX;
|
||
Coord.Y = Eve.changedTouches[0].pageY;
|
||
} else {
|
||
Coord.X = Eve.pageX;
|
||
Coord.Y = Eve.pageY;
|
||
}
|
||
Coord = O.getCoordInViewport(Coord, Eve.target.ownerDocument);/*
|
||
console.log(
|
||
`[${ Eve.target.ownerDocument == document ? 'PARENT' : 'CHILD' }]`,
|
||
Coord,
|
||
{
|
||
X: Eve.screenX - window.screenX - (window.outerWidth - window.innerWidth),
|
||
Y: Eve.screenY - window.screenY - (window.outerHeight - window.innerHeight)
|
||
},
|
||
Eve
|
||
);//*/
|
||
return Coord;
|
||
};
|
||
|
||
O.getCoordInViewport = (Coord, Doc) => {
|
||
if(Doc == document) {
|
||
Coord.X -= O.Body.scrollLeft;
|
||
Coord.Y -= O.Body.scrollTop;
|
||
} else {
|
||
const Main = R.Main;
|
||
const MainTransformation = I.Loupe.CurrentTransformation || { Scale: 1, TranslateX: 0, TranslateY: 0 };
|
||
const MainScale = MainTransformation.Scale;
|
||
const MainTransformOriginX_InMain = Main.offsetWidth / 2;
|
||
const MainTransformOriginY_InMain = Main.offsetHeight / 2;
|
||
const MainTranslationX = MainTransformation.TranslateX;
|
||
const MainTranslationY = MainTransformation.TranslateY;
|
||
const Item = Doc.documentElement.Item;
|
||
const ItemScale = Item.Scale;
|
||
const ItemCoordInMain = O.getElementCoord(Item, Main);
|
||
if(Item['rendition:layout'] != 'pre-paginated' && !Item.Outsourcing) ItemCoordInMain.X += S['item-padding-left'], ItemCoordInMain.Y += S['item-padding-top'];
|
||
Coord.X = Math.floor(Main.offsetLeft + ((MainTransformOriginX_InMain + MainTranslationX) + ((((ItemCoordInMain.X + (Coord.X * ItemScale)) - Main.scrollLeft) - MainTransformOriginX_InMain) * MainScale)));
|
||
Coord.Y = Math.floor(Main.offsetTop + ((MainTransformOriginY_InMain + MainTranslationY) + ((((ItemCoordInMain.Y + (Coord.Y * ItemScale)) - Main.scrollTop ) - MainTransformOriginY_InMain) * MainScale)));
|
||
// (MainCoord + ((MainTransformOrigin_in_Main + MainTranslation ) + ((((ItemCoord_in_Main + Coord_in_Item ) - ScrolledLength ) - MainTransformOrigin_in_Main) * MainScale)))
|
||
// (MainCoord + ((MainTransformOrigin_in_Main + MainTranslation ) + (((Coord_in_Main - ScrolledLength ) - MainTransformOrigin_in_Main) * MainScale)))
|
||
// (MainCoord + ((MainTransformOrigin_in_Main + MainTranslation ) + ((Coord_in_Viewport_of_Main - MainTransformOrigin_in_Main) * MainScale)))
|
||
// (MainCoord + ((MainTransformOrigin_in_Main + MainTranslation ) + (Coord_from_MainTransformOrigin_in_Main * MainScale)))
|
||
// (MainCoord + (MainTransformOrigin_in_Translated-Main + Coord_from_TransformOrigin_in_Scaled-Main ))
|
||
// (MainCoord + Coord_in_Transformed-Main )
|
||
// Coord
|
||
}
|
||
return Coord;
|
||
};
|
||
|
||
|
||
O.Cookies = {
|
||
Label: 'bibi',
|
||
remember: (Group) => {
|
||
const BCs = JSON.parse(sML.Cookies.read(O.Cookies.Label) || '{}');
|
||
console.log('Cookies:', BCs);
|
||
if(typeof Group != 'string' || !Group) return BCs;
|
||
return BCs[Group];
|
||
},
|
||
eat: (Group, KeyVal, Opt) => {
|
||
if(typeof Group != 'string' || !Group) return false;
|
||
if(typeof KeyVal != 'object') return false;
|
||
const BCs = O.Cookies.remember();
|
||
if(typeof BCs[Group] != 'object') BCs[Group] = {};
|
||
for(const Key in KeyVal) {
|
||
const Val = KeyVal[Key];
|
||
if(typeof Val == 'function') continue;
|
||
BCs[Group][Key] = Val;
|
||
}
|
||
if(!Opt) Opt = {};
|
||
Opt.Path = location.pathname.replace(/[^\/]+$/, '');
|
||
if(!Opt.Expires) Opt.Expires = S['cookie-expires'];
|
||
sML.Cookies.write(O.Cookies.Label, JSON.stringify(BCs), Opt);
|
||
}
|
||
};
|
||
|
||
|
||
O.Biscuits = {
|
||
Memories: {}, Labels: {},
|
||
initialize: (Tag) => {
|
||
if(!localStorage) return O.Biscuits = null;
|
||
if(typeof Tag != 'string') {
|
||
O.Biscuits.LabelBase = 'BibiBiscuits:' + P.Script.src.replace(new RegExp('^' + O.Origin.replace(/([\/\.])/g, '\\$1')), '');
|
||
E.bind('bibi:initialized', () => O.Biscuits.initialize('Bibi'));
|
||
E.bind('bibi:initialized-book', () => O.Biscuits.initialize('Book'));
|
||
return null;
|
||
}
|
||
switch(Tag) {
|
||
case 'Bibi': break;
|
||
case 'Book': if(B.ID) break;
|
||
default: return null;
|
||
}
|
||
const Label = O.Biscuits.Labels[Tag] = O.Biscuits.LabelBase + (Tag == 'Book' ? '#' + B.ID : '');
|
||
const BiscuitsOfTheLabel = localStorage.getItem(Label);
|
||
O.Biscuits.Memories[Label] = BiscuitsOfTheLabel ? JSON.parse(BiscuitsOfTheLabel) : {};
|
||
return O.Biscuits.Memories[Label];
|
||
},
|
||
remember: (Tag, Key) => {
|
||
if(!Tag || typeof Tag != 'string' || !O.Biscuits.Labels[Tag]) return O.Biscuits.Memories;
|
||
const Label = O.Biscuits.Labels[Tag];
|
||
return (!Key || typeof Key != 'string') ? O.Biscuits.Memories[Label] : O.Biscuits.Memories[Label][Key];
|
||
},
|
||
memorize: (Tag, KnV) => {
|
||
if(!Tag || typeof Tag != 'string' || !O.Biscuits.Labels[Tag]) return false;
|
||
const Label = O.Biscuits.Labels[Tag];
|
||
if(KnV && typeof KnV == 'object') for(const Key in KnV) { const Val = KnV[Key];
|
||
try {
|
||
if(Val && typeof Val != 'function' && typeof JSON.parse(JSON.stringify({ [Key]: Val }))[Key] != 'undefined') O.Biscuits.Memories[Label][Key] = Val;
|
||
//if(Val) O.Biscuits.Memories[Label][Key] = Val;
|
||
else throw '';
|
||
} catch(Err) {
|
||
delete O.Biscuits.Memories[Label][Key];
|
||
}
|
||
}
|
||
return localStorage.setItem(Label, JSON.stringify(O.Biscuits.Memories[Label]));
|
||
},
|
||
forget: (Tag, Keys) => {
|
||
if(!Tag) {
|
||
localStorage.removeItem(O.Biscuits.Labels.Bibi);
|
||
localStorage.removeItem(O.Biscuits.Labels.Book);
|
||
O.Biscuits.Memories = {};
|
||
} else if(typeof Tag != 'string' || !O.Biscuits.Labels[Tag]) {
|
||
} else {
|
||
const Label = O.Biscuits.Labels[Tag];
|
||
if(!Keys) {
|
||
localStorage.removeItem(Label);
|
||
delete O.Biscuits.Memories[Label];
|
||
} else {
|
||
if(typeof Keys == 'string') Keys = [Keys];
|
||
if(Array.isArray(Keys)) Keys.forEach(Key => (typeof Key != 'string' || !Key) ? false : delete O.Biscuits.Memories[Label][Key]);
|
||
localStorage.setItem(Label, JSON.stringify(O.Biscuits.Memories[Label]));
|
||
}
|
||
}
|
||
return O.Biscuits.Memories;
|
||
}
|
||
};
|
||
|
||
|
||
|
||
|
||
//==============================================================================================================================================
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
//-- Events
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
|
||
export const E = {};
|
||
|
||
|
||
E.initialize = () => {
|
||
if(document.onpointerdown !== undefined) {
|
||
E['pointerdown'] = 'pointerdown';
|
||
E['pointermove'] = 'pointermove';
|
||
E['pointerup'] = 'pointerup';
|
||
E['pointerover'] = 'pointerover';
|
||
E['pointerout'] = 'pointerout';
|
||
} else if(O.TouchOS && document.ontouchstart !== undefined) {
|
||
E['pointerdown'] = 'touchstart';
|
||
E['pointermove'] = 'touchmove';
|
||
E['pointerup'] = 'touchend';
|
||
} else {
|
||
E['pointerdown'] = 'mousedown';
|
||
E['pointermove'] = 'mousemove';
|
||
E['pointerup'] = 'mouseup';
|
||
E['pointerover'] = 'mouseover';
|
||
E['pointerout'] = 'mouseout';
|
||
}
|
||
E['resize'] = O.TouchOS ? 'orientationchange' : 'resize';
|
||
E.Cpt0Psv0 = { capture: false, passive: false };
|
||
E.Cpt1Psv0 = { capture: true, passive: false };
|
||
//sML.applyRtL(E, new sML.CustomEvents('bibi'));
|
||
E.CustomEvents = new sML.CustomEvents('bibi');
|
||
E.add = function(/*[Tar,]*/ Nam, fun, Opt) {
|
||
if(Array.isArray(arguments[0]) ) return arguments[0].forEach(AI => E.add(AI, arguments[1], arguments[2], arguments[3]));
|
||
if(Array.isArray(arguments[1]) ) return arguments[1].forEach(AI => E.add(arguments[0], AI, arguments[2], arguments[3]));
|
||
if(Array.isArray(arguments[2]) && typeof arguments[2][0] == 'function') return arguments[2].forEach(AI => E.add(arguments[0], arguments[1], AI, arguments[3]));
|
||
let Tar = document; if(typeof fun != 'function') Tar = arguments[0], Nam = arguments[1], fun = arguments[2], Opt = arguments[3];
|
||
return /^bibi:/.test(Nam) ? E.CustomEvents.add(Tar, Nam, fun) : Tar.addEventListener(Nam, fun, Opt);
|
||
};
|
||
E.remove = function(/*[Tar,]*/ Nam, fun, Opt) {
|
||
if(Array.isArray(arguments[0]) ) return arguments[0].forEach(AI => E.remove(AI, arguments[1], arguments[2], arguments[3]));
|
||
if(Array.isArray(arguments[1]) ) return arguments[1].forEach(AI => E.remove(arguments[0], AI, arguments[2], arguments[3]));
|
||
if(Array.isArray(arguments[2]) && typeof arguments[2][0] == 'function') return arguments[2].forEach(AI => E.remove(arguments[0], arguments[1], AI, arguments[3]));
|
||
let Tar = document; if(typeof fun != 'function') Tar = arguments[0], Nam = arguments[1], fun = arguments[2], Opt = arguments[3];
|
||
return /^bibi:/.test(Nam) ? E.CustomEvents.remove(Tar, Nam, fun) : Tar.removeEventListener(Nam, fun, Opt);
|
||
};
|
||
E.bind = function() { return E.CustomEvents.bind .apply(E.CustomEvents, arguments); };
|
||
E.unbind = function() { return E.CustomEvents.unbind .apply(E.CustomEvents, arguments); };
|
||
E.dispatch = function() { return E.CustomEvents.dispatch.apply(E.CustomEvents, arguments); };
|
||
delete E.initialize;
|
||
};
|
||
|
||
|
||
|
||
|
||
//==============================================================================================================================================
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
//-- Messages
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
|
||
export const M = {}; // Bibi.Messages
|
||
|
||
|
||
M.judge = (Msg, Origin) => (O.ParentBibi && Msg && typeof Msg == 'string' && Origin && typeof Origin == 'string' && S['trustworthy-origins'].includes(Origin));
|
||
|
||
|
||
M.post = (Msg) => !M.judge(Msg, O.ParentOrigin) ? false : window.parent.postMessage(Msg, window.parent.location.origin);
|
||
|
||
|
||
M.receive = (Eve) => {
|
||
if(!Eve || !M.judge(Eve.data, Eve.origin)) return false; try {
|
||
const Data = JSON.parse(Eve.data);
|
||
if(!Data || typeof Data != 'object') return false;
|
||
for(const EventName in Data) if(/^bibi:commands:/.test(EventName)) E.dispatch(EventName, Data[EventName]);
|
||
return true; } catch(Err) {} return false;
|
||
};
|
||
|
||
|
||
|
||
|
||
//==============================================================================================================================================
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
//-- Extensions
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
|
||
export const X = { // Bibi.Extensions
|
||
Extensions: [], Bibi: {}
|
||
};
|
||
|
||
X.load = (Xtn) => new Promise((resolve, reject) => {
|
||
if(!Xtn['src'] || typeof Xtn['src'] != 'string') return reject(`"path" of the Extension Seems to Be Invalid. ("${ Xtn['src'] }")`);
|
||
const XO = new URL(Xtn['src']).origin;
|
||
if(!S['trustworthy-origins'].includes(XO)) return reject(`The Origin Is Not Allowed. ("${ Xtn['src'] }")`);
|
||
Xtn.Script = document.head.appendChild(sML.create('script', { className: 'bibi-extension-script', src: Xtn['src'], Extension: Xtn, resolve: resolve, reject: function() { reject(); document.head.removeChild(this); } }));
|
||
});
|
||
|
||
X.add = (XMeta) => {
|
||
const XScript = document.currentScript;
|
||
if(typeof XMeta['id'] == 'undefined') return XScript.reject(`"id" of the extension is undefined.`);
|
||
if(typeof XMeta['id'] != 'string') return XScript.reject(`"id" of the extension is invalid.`);
|
||
if(!XMeta['id']) return XScript.reject(`"id" of the extension is blank.`);
|
||
if(X[XMeta['id']]) return XScript.reject(`"id" of the extension is reserved or already used by another. ("${ XMeta['id'] }")`);
|
||
XScript.setAttribute('data-bibi-extension-id', XMeta['id']);
|
||
X[XMeta['id']] = XScript.Extension = sML.applyRtL(XMeta, XScript.Extension);
|
||
X[XMeta['id']].Index = X.Extensions.length;
|
||
X.Extensions.push(X[XMeta['id']]);
|
||
XScript.resolve(X[XMeta['id']]);
|
||
const Xtn = X[XMeta['id']];
|
||
return function(onR) { if(Xtn && typeof onR == 'function') E.bind('bibi:readied', () => onR.call(Xtn, Xtn));
|
||
return function(onP) { if(Xtn && typeof onP == 'function') E.bind('bibi:prepared', () => onP.call(Xtn, Xtn));
|
||
return function(onO) { if(Xtn && typeof onO == 'function') E.bind('bibi:opened', () => onO.call(Xtn, Xtn)); }; }; };
|
||
};
|
||
|
||
Bibi.x = X.add;
|