mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-10 16:10:55 +00:00
4.20.70
* Fixed: Popout Chat from the dashboard and mod view not working correctly. Please note there is still a race condition on the dashboard popout chat. It may require several refreshes or not work at all depending on your Internet connection. * Fixed: Only load the chat types from Twitch once. Ignore any future module loads. * Fixed: Hide the empty bar at the bottom of Twitch pages due to incorrect styles being applied to the new snackbar container element. * Fixed: Apollo should only clear the query cache if it makes changes to a query. Likewise, Apollo should only fetch the `gql-printer` module upon demand. * Fixed: Remove debug logging from `utilities/dom::createElement` * Changed: Slightly delay tool-tip repositioning when rich content is loaded, hopefully reducing flicker events. * Changed: Refactor WebMunch, adding compatibility for a future webpack update and reducing the number of modules checked when scanning for modules. * Changed: Allow Switchboard to keep trying to load routes if the one it tries fails to actually populate `require()`. * API Added: `EventEmitter::hasListeners(event)` method for determining if there are any listeners for a specific event. * API Added: `localStorage.ffzLogLevel` can be set to override the global log level. * API Added: `log.verbose(...)` as an even weaker logging level than `debug(...)` * API Changed: Allow Tooltip instances to add tool-tips to the DOM under a different element than the parent element used for events.
This commit is contained in:
parent
77d6cf56d2
commit
ab4f72c345
16 changed files with 666 additions and 127 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.20.69",
|
"version": "4.20.70",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -70,7 +70,7 @@ export const Links = {
|
||||||
i18n: this.i18n,
|
i18n: this.i18n,
|
||||||
allow_media: show_images,
|
allow_media: show_images,
|
||||||
allow_unsafe: show_unsafe,
|
allow_unsafe: show_unsafe,
|
||||||
onload: tip.update
|
onload: () => requestAnimationFrame(() => tip.update())
|
||||||
};
|
};
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
|
@ -214,7 +214,7 @@ Links.tooltip.interactive = function(target) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Links.tooltip.delayHide = function(target) {
|
Links.tooltip.delayHide = function(target) {
|
||||||
if ( ! this.context.get('tooltip.rich-links') || ! this.context.get('tooltip.link-interaction') || target.dataset.isMail === 'true' )
|
if ( ! this.context.get('tooltip.rich-links') || target.dataset.isMail === 'true' )
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return 64;
|
return 64;
|
||||||
|
|
|
@ -112,7 +112,7 @@ export default class TooltipProvider extends Module {
|
||||||
|
|
||||||
|
|
||||||
onEnable() {
|
onEnable() {
|
||||||
const container = document.querySelector('.sunlight-root') || document.querySelector('#root>div') || document.querySelector('#root') || document.querySelector('.clips-root') || document.body;
|
const container = this.getRoot();
|
||||||
|
|
||||||
window.addEventListener('fullscreenchange', this.onFSChange);
|
window.addEventListener('fullscreenchange', this.onFSChange);
|
||||||
|
|
||||||
|
@ -125,12 +125,17 @@ export default class TooltipProvider extends Module {
|
||||||
this.on(':cleanup', this.cleanup);
|
this.on(':cleanup', this.cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
_createInstance(container, klass = 'ffz-tooltip', default_type = 'text') {
|
getRoot() { // eslint-disable-line class-methods-use-this
|
||||||
|
return document.querySelector('.sunlight-root') || document.querySelector('#root>div') || document.querySelector('#root') || document.querySelector('.clips-root') || document.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
_createInstance(container, klass = 'ffz-tooltip', default_type = 'text', tip_container) {
|
||||||
return new Tooltip(container, klass, {
|
return new Tooltip(container, klass, {
|
||||||
html: true,
|
html: true,
|
||||||
i18n: this.i18n,
|
i18n: this.i18n,
|
||||||
live: true,
|
live: true,
|
||||||
check_modifiers: true,
|
check_modifiers: true,
|
||||||
|
container: tip_container || container,
|
||||||
|
|
||||||
delayHide: this.checkDelayHide.bind(this, default_type),
|
delayHide: this.checkDelayHide.bind(this, default_type),
|
||||||
delayShow: this.checkDelayShow.bind(this, default_type),
|
delayShow: this.checkDelayShow.bind(this, default_type),
|
||||||
|
|
|
@ -174,43 +174,111 @@ export default class Twilight extends BaseSite {
|
||||||
}
|
}
|
||||||
|
|
||||||
getCore() {
|
getCore() {
|
||||||
if ( this._core )
|
if ( ! this._core )
|
||||||
|
this._core = this.web_munch.getModule('core');
|
||||||
|
|
||||||
return this._core;
|
return this._core;
|
||||||
|
|
||||||
let core = this.web_munch.getModule('core-1');
|
|
||||||
if ( core )
|
|
||||||
return this._core = core.o;
|
|
||||||
|
|
||||||
core = this.web_munch.getModule('core-2');
|
|
||||||
if ( core )
|
|
||||||
return this._core = core.p;
|
|
||||||
|
|
||||||
core = this.web_munch.getModule('core-3');
|
|
||||||
if ( core )
|
|
||||||
return this._core = core.q;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const CALCULATE_BITS = '_calculateChangedBits';
|
||||||
|
|
||||||
Twilight.KNOWN_MODULES = {
|
Twilight.KNOWN_MODULES = {
|
||||||
simplebar: n => n.globalObserver && n.initDOMLoadedElements,
|
simplebar: n => n.globalObserver && n.initDOMLoadedElements,
|
||||||
react: n => n.Component && n.createElement,
|
react: n => n.Component && n.createElement,
|
||||||
'core-1': n => n.o && n.o.experiments,
|
core: n => {
|
||||||
'core-2': n => n.p && n.p.experiments,
|
if ( n['$6']?.experiments )
|
||||||
'core-3': n => n.q && n.q.experiments,
|
return n['$6'];
|
||||||
|
if ( n.p?.experiments )
|
||||||
|
return n.p;
|
||||||
|
if ( n.o?.experiments )
|
||||||
|
return n.o;
|
||||||
|
if ( n.q?.experiments )
|
||||||
|
return n.q;
|
||||||
|
},
|
||||||
cookie: n => n && n.set && n.get && n.getJSON && n.withConverter,
|
cookie: n => n && n.set && n.get && n.getJSON && n.withConverter,
|
||||||
'extension-service': n => n.extensionService,
|
'extension-service': n => n.extensionService,
|
||||||
'chat-types': n => n.b && has(n.b, 'Message') && has(n.b, 'RoomMods'),
|
'chat-types': n => {
|
||||||
'gql-printer': n => n !== window && n.print,
|
if ( has(n.b, 'Message') && has(n.b, 'RoomMods') )
|
||||||
|
return {
|
||||||
|
automod: n.a,
|
||||||
|
chat: n.b,
|
||||||
|
message: n.c,
|
||||||
|
mod: n.e
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( has(n.SJ, 'Message') && has(n.SJ, 'RoomMods') )
|
||||||
|
return {
|
||||||
|
automod: n.mT,
|
||||||
|
chat: n.SJ,
|
||||||
|
message: n.Ay,
|
||||||
|
mod: n.Aw
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'gql-printer': n => {
|
||||||
|
if ( n === window )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( n.print && n.print.toString().includes('.visit') )
|
||||||
|
return n.print;
|
||||||
|
|
||||||
|
if ( n.S && n.S.toString().includes('.visit') )
|
||||||
|
return n.S;
|
||||||
|
},
|
||||||
mousetrap: n => n.bindGlobal && n.unbind && n.handleKey,
|
mousetrap: n => n.bindGlobal && n.unbind && n.handleKey,
|
||||||
'algolia-search': n => n.a && n.a.prototype && n.a.prototype.queryTopResults && n.a.prototype.queryForType,
|
'algolia-search': n => {
|
||||||
highlightstack: n => n.b && has(n.b, '_calculateChangedBits') && n.c && has(n.c, '_calculateChangedBits')
|
if ( n.a?.prototype?.queryTopResults && n.a.prototype.queryForType )
|
||||||
|
return n.a;
|
||||||
|
if ( n.w9?.prototype?.queryTopResults && n.w9.prototype.queryForType )
|
||||||
|
return n.w9;
|
||||||
|
},
|
||||||
|
highlightstack: n => {
|
||||||
|
if ( has(n.b, CALCULATE_BITS) && has(n.c, CALCULATE_BITS) )
|
||||||
|
return {
|
||||||
|
stack: n.b,
|
||||||
|
dispatch: n.c
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( has(n.fQ, CALCULATE_BITS) && has(n.vJ, CALCULATE_BITS) )
|
||||||
|
return {
|
||||||
|
stack: n.fQ,
|
||||||
|
dispatch: n.vJ
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VEND_CHUNK = n => n && n.includes('vendor');
|
||||||
|
|
||||||
|
Twilight.KNOWN_MODULES.core.use_result = true;
|
||||||
|
//Twilight.KNOWN_MODULES.core.chunks = 'core';
|
||||||
|
|
||||||
|
Twilight.KNOWN_MODULES.simplebar.chunks = VEND_CHUNK;
|
||||||
|
Twilight.KNOWN_MODULES.react.chunks = VEND_CHUNK;
|
||||||
|
Twilight.KNOWN_MODULES.cookie.chunks = VEND_CHUNK;
|
||||||
|
|
||||||
|
Twilight.KNOWN_MODULES['gql-printer'].use_result = true;
|
||||||
|
Twilight.KNOWN_MODULES['gql-printer'].chunks = VEND_CHUNK;
|
||||||
|
|
||||||
|
Twilight.KNOWN_MODULES.mousetrap.chunks = VEND_CHUNK;
|
||||||
|
|
||||||
|
const CHAT_CHUNK = n => n && n.includes('chat');
|
||||||
|
|
||||||
|
Twilight.KNOWN_MODULES['chat-types'].use_result = true;
|
||||||
|
Twilight.KNOWN_MODULES['chat-types'].chunks = CHAT_CHUNK;
|
||||||
|
Twilight.KNOWN_MODULES['highlightstack'].use_result = true;
|
||||||
|
Twilight.KNOWN_MODULES['highlightstack'].chunks = CHAT_CHUNK;
|
||||||
|
|
||||||
|
Twilight.KNOWN_MODULES['algolia-search'].use_result = true;
|
||||||
|
Twilight.KNOWN_MODULES['algolia-search'].chunks = 'core';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Twilight.POPOUT_ROUTES = [
|
Twilight.POPOUT_ROUTES = [
|
||||||
'embed-chat',
|
'embed-chat',
|
||||||
'popout'
|
'popout',
|
||||||
|
'dash-popout-chat',
|
||||||
|
'mod-popout-chat'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
@ -233,7 +301,9 @@ Twilight.CHAT_ROUTES = [
|
||||||
'squad',
|
'squad',
|
||||||
'command-center',
|
'command-center',
|
||||||
'dash-stream-manager',
|
'dash-stream-manager',
|
||||||
'mod-view'
|
'dash-popout-chat',
|
||||||
|
'mod-view',
|
||||||
|
'mod-popout-chat'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
@ -243,7 +313,7 @@ Twilight.ROUTE_NAMES = {
|
||||||
'dir-all': 'Browse Live Channels',
|
'dir-all': 'Browse Live Channels',
|
||||||
'dash': 'Dashboard',
|
'dash': 'Dashboard',
|
||||||
'popout': 'Popout Chat',
|
'popout': 'Popout Chat',
|
||||||
'dash-chat': 'Dashboard Popout Chat',
|
'dash-popout-chat': 'Dashboard Popout Chat',
|
||||||
'user-video': 'Channel Video',
|
'user-video': 'Channel Video',
|
||||||
'popout-player': 'Popout/Embed Player'
|
'popout-player': 'Popout/Embed Player'
|
||||||
};
|
};
|
||||||
|
@ -293,6 +363,7 @@ Twilight.DASH_ROUTES = {
|
||||||
'dash-settings-revenue': '/u/:userName/settings/revenue',
|
'dash-settings-revenue': '/u/:userName/settings/revenue',
|
||||||
'dash-extensions': '/u/:userName/extensions',
|
'dash-extensions': '/u/:userName/extensions',
|
||||||
'dash-streaming-tools': '/u/:userName/broadcast',
|
'dash-streaming-tools': '/u/:userName/broadcast',
|
||||||
|
'dash-popout-chat': '/popout/u/:userName/stream-manager/chat',
|
||||||
};
|
};
|
||||||
|
|
||||||
Twilight.ROUTES = {
|
Twilight.ROUTES = {
|
||||||
|
@ -313,7 +384,7 @@ Twilight.ROUTES = {
|
||||||
//'dash-automod': '/:userName/dashboard/settings/automod',
|
//'dash-automod': '/:userName/dashboard/settings/automod',
|
||||||
'event': '/event/:eventName',
|
'event': '/event/:eventName',
|
||||||
'popout': '/popout/:userName/chat',
|
'popout': '/popout/:userName/chat',
|
||||||
'dash-chat': '/popout/:userName/dashboard/live/chat',
|
//'dash-chat': '/popout/:userName/dashboard/live/chat',
|
||||||
'video': '/videos/:videoID',
|
'video': '/videos/:videoID',
|
||||||
'user-video': '/:userName/video/:videoID',
|
'user-video': '/:userName/video/:videoID',
|
||||||
'user-videos': '/:userName/videos/:filter?',
|
'user-videos': '/:userName/videos/:filter?',
|
||||||
|
@ -331,7 +402,8 @@ Twilight.ROUTES = {
|
||||||
'squad': '/:userName/squad',
|
'squad': '/:userName/squad',
|
||||||
'command-center': '/:userName/commandcenter',
|
'command-center': '/:userName/commandcenter',
|
||||||
'embed-chat': '/embed/:userName/chat',
|
'embed-chat': '/embed/:userName/chat',
|
||||||
'mod-view': '/moderator/:userName'
|
'mod-view': '/moderator/:userName',
|
||||||
|
'mod-popout-chat': '/popout/moderator/:userName/chat'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -193,10 +193,13 @@ export default class Channel extends Module {
|
||||||
this.removePanelTips(inst);
|
this.removePanelTips(inst);
|
||||||
|
|
||||||
if ( ! inst._ffz_tips ) {
|
if ( ! inst._ffz_tips ) {
|
||||||
inst._ffz_tips = this.resolve('tooltips')._createInstance(el, 'tw-link', 'link');
|
const tt = this.resolve('tooltips');
|
||||||
|
if ( tt ) {
|
||||||
|
inst._ffz_tips = tt._createInstance(el, 'tw-link', 'link', tt.getRoot());
|
||||||
inst._ffz_tip_el = el;
|
inst._ffz_tip_el = el;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
removePanelTips(inst) { // eslint-disable-line class-methods-use-this
|
removePanelTips(inst) { // eslint-disable-line class-methods-use-this
|
||||||
if ( inst?._ffz_tips ) {
|
if ( inst?._ffz_tips ) {
|
||||||
|
|
|
@ -735,27 +735,32 @@ export default class ChatHook extends Module {
|
||||||
|
|
||||||
|
|
||||||
async grabTypes() {
|
async grabTypes() {
|
||||||
const ct = await this.web_munch.findModule('chat-types'),
|
if ( this.types_loaded )
|
||||||
changes = [];
|
return;
|
||||||
|
|
||||||
this.automod_types = ct && ct.a || AUTOMOD_TYPES;
|
const ct = await this.web_munch.findModule('chat-types');
|
||||||
this.chat_types = ct && ct.b || CHAT_TYPES;
|
|
||||||
this.message_types = ct && ct.c || MESSAGE_TYPES;
|
this.automod_types = ct?.automod || AUTOMOD_TYPES;
|
||||||
this.mod_types = ct && ct.e || MOD_TYPES;
|
this.chat_types = ct?.chat || CHAT_TYPES;
|
||||||
|
this.message_types = ct?.message || MESSAGE_TYPES;
|
||||||
|
this.mod_types = ct?.mod || MOD_TYPES;
|
||||||
|
|
||||||
if ( ! ct )
|
if ( ! ct )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ( ct.a && ! shallow_object_equals(ct.a, AUTOMOD_TYPES) )
|
this.types_loaded = true;
|
||||||
|
const changes = [];
|
||||||
|
|
||||||
|
if ( ! shallow_object_equals(this.automod_types, AUTOMOD_TYPES) )
|
||||||
changes.push('AUTOMOD_TYPES');
|
changes.push('AUTOMOD_TYPES');
|
||||||
|
|
||||||
if ( ct.b && ! shallow_object_equals(ct.b, CHAT_TYPES) )
|
if ( ! shallow_object_equals(this.chat_types, CHAT_TYPES) )
|
||||||
changes.push('CHAT_TYPES');
|
changes.push('CHAT_TYPES');
|
||||||
|
|
||||||
if ( ct.c && ! shallow_object_equals(ct.c, MESSAGE_TYPES) )
|
if ( ! shallow_object_equals(this.message_types, MESSAGE_TYPES) )
|
||||||
changes.push('MESSAGE_TYPES');
|
changes.push('MESSAGE_TYPES');
|
||||||
|
|
||||||
if ( ct.e && ! shallow_object_equals(ct.e, MOD_TYPES) )
|
if ( ! shallow_object_equals(this.mod_types, MOD_TYPES) )
|
||||||
changes.push('MOD_TYPES');
|
changes.push('MOD_TYPES');
|
||||||
|
|
||||||
if ( changes.length )
|
if ( changes.length )
|
||||||
|
@ -1296,15 +1301,15 @@ export default class ChatHook extends Module {
|
||||||
|
|
||||||
const t = this,
|
const t = this,
|
||||||
React = this.web_munch.getModule('react'),
|
React = this.web_munch.getModule('react'),
|
||||||
Stack = this.web_munch.getModule('highlightstack'),
|
createElement = React && React.createElement,
|
||||||
createElement = React && React.createElement;
|
StackMod = this.web_munch.getModule('highlightstack');
|
||||||
|
|
||||||
if ( ! createElement || ! Stack || ! Stack.b )
|
if ( ! createElement || ! StackMod )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
this.CommunityStackHandler = function() {
|
this.CommunityStackHandler = function() {
|
||||||
const stack = React.useContext(Stack.b),
|
const stack = React.useContext(StackMod.stack),
|
||||||
dispatch = React.useContext(Stack.c);
|
dispatch = React.useContext(StackMod.dispatch);
|
||||||
|
|
||||||
t.community_stack = stack;
|
t.community_stack = stack;
|
||||||
t.community_dispatch = dispatch;
|
t.community_dispatch = dispatch;
|
||||||
|
|
|
@ -6,3 +6,7 @@
|
||||||
.top-nav__menu > div:empty {
|
.top-nav__menu > div:empty {
|
||||||
display: none
|
display: none
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.twilight-main > .tw-relative.tw-z-above.tw-bottom-0 {
|
||||||
|
position: absolute !important;
|
||||||
|
}
|
|
@ -16,6 +16,8 @@ export default class Switchboard extends Module {
|
||||||
this.inject('site.web_munch');
|
this.inject('site.web_munch');
|
||||||
this.inject('site.fine');
|
this.inject('site.fine');
|
||||||
this.inject('site.router');
|
this.inject('site.router');
|
||||||
|
|
||||||
|
this.tried = new Set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,29 +66,49 @@ export default class Switchboard extends Module {
|
||||||
const router = await this.awaitRouter();
|
const router = await this.awaitRouter();
|
||||||
|
|
||||||
this.log.info(`Found Route and Switch with ${da_switch.props.children.length} routes.`);
|
this.log.info(`Found Route and Switch with ${da_switch.props.children.length} routes.`);
|
||||||
const location = router.props.location.pathname;
|
this.da_switch = da_switch;
|
||||||
|
this.location = router.props.location.pathname;
|
||||||
|
//const location = router.props.location.pathname;
|
||||||
|
|
||||||
if ( ! this.loadRoute(da_switch, location, false) )
|
this.loadOne();
|
||||||
this.loadRoute(da_switch, location, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadRoute(da_switch, location, with_params) {
|
loadOne() {
|
||||||
for(const route of da_switch.props.children) {
|
if ( ! this.loadRoute(false) )
|
||||||
|
this.loadRoute(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
waitAndSee() {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if ( this.web_munch._require )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.log.info('We still need require(). Trying again.');
|
||||||
|
this.loadOne();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRoute(with_params) {
|
||||||
|
for(const route of this.da_switch.props.children) {
|
||||||
if ( ! route.props || ! route.props.component )
|
if ( ! route.props || ! route.props.component )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if ( with_params !== null && with_params !== route.props.path.includes(':') )
|
if ( with_params !== null && with_params !== route.props.path.includes(':') )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if ( this.tried.has(route.props.path) )
|
||||||
|
continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const reg = pathToRegexp(route.props.path);
|
const reg = pathToRegexp(route.props.path);
|
||||||
if ( ! reg.exec || reg.exec(location) )
|
if ( ! reg.exec || reg.exec(this.location) )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.tried.add(route.props.path);
|
||||||
this.log.info('Found Non-Matching Route', route.props.path);
|
this.log.info('Found Non-Matching Route', route.props.path);
|
||||||
|
|
||||||
const component_class = route.props.component;
|
const component_class = route.props.component;
|
||||||
|
@ -107,6 +129,7 @@ export default class Switchboard extends Module {
|
||||||
try {
|
try {
|
||||||
component.props.loader().then(() => {
|
component.props.loader().then(() => {
|
||||||
this.log.info('Successfully forced a chunk to load using route', route.props.path)
|
this.log.info('Successfully forced a chunk to load using route', route.props.path)
|
||||||
|
this.waitAndSee();
|
||||||
});
|
});
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
this.log.warn('Unexpected result trying to use component pre-loader to force loading of another chunk.');
|
this.log.warn('Unexpected result trying to use component pre-loader to force loading of another chunk.');
|
||||||
|
@ -126,6 +149,7 @@ export default class Switchboard extends Module {
|
||||||
try {
|
try {
|
||||||
component.props.children.props.loader().then(() => {
|
component.props.children.props.loader().then(() => {
|
||||||
this.log.info('Successfully forced a chunk to load using route', route.props.path)
|
this.log.info('Successfully forced a chunk to load using route', route.props.path)
|
||||||
|
this.waitAndSee();
|
||||||
});
|
});
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
this.log.warn('Unexpected result trying to use component loader to force loading of another chunk.');
|
this.log.warn('Unexpected result trying to use component loader to force loading of another chunk.');
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
import {get} from 'utilities/object';
|
import {get} from 'utilities/object';
|
||||||
import merge from 'utilities/graphql';
|
import merge from 'utilities/graphql';
|
||||||
|
import { FFZEvent } from 'utilities/events';
|
||||||
|
|
||||||
|
|
||||||
/*const BAD_ERRORS = [
|
/*const BAD_ERRORS = [
|
||||||
|
@ -35,6 +36,20 @@ function skip_error(err) {
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
export class ApolloEvent extends FFZEvent {
|
||||||
|
constructor(data) {
|
||||||
|
super(data);
|
||||||
|
|
||||||
|
this._changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
markChanged() {
|
||||||
|
this._changed = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export class GQLError extends Error {
|
export class GQLError extends Error {
|
||||||
constructor(err) {
|
constructor(err) {
|
||||||
super(`${err.message}; Location: ${err.locations}`);
|
super(`${err.message}; Location: ${err.locations}`);
|
||||||
|
@ -49,11 +64,20 @@ export default class Apollo extends Module {
|
||||||
this.modifiers = {};
|
this.modifiers = {};
|
||||||
this.post_modifiers = {};
|
this.post_modifiers = {};
|
||||||
|
|
||||||
this.inject('..web_munch');
|
|
||||||
this.inject('..fine');
|
this.inject('..fine');
|
||||||
}
|
}
|
||||||
|
|
||||||
async onEnable() {
|
get gqlPrint() {
|
||||||
|
if ( this._gql_print )
|
||||||
|
return this._gql_print;
|
||||||
|
|
||||||
|
const web_munch = this.resolve('site.web_munch'),
|
||||||
|
printer = this._gql_print = web_munch?.getModule?.('gql-printer');
|
||||||
|
|
||||||
|
return printer;
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnable() {
|
||||||
// TODO: Come up with a better way to await something existing.
|
// TODO: Come up with a better way to await something existing.
|
||||||
let client = this.client;
|
let client = this.client;
|
||||||
|
|
||||||
|
@ -69,9 +93,6 @@ export default class Apollo extends Module {
|
||||||
if ( ! client )
|
if ( ! client )
|
||||||
return new Promise(() => this.onEnable(), 50);
|
return new Promise(() => this.onEnable(), 50);
|
||||||
|
|
||||||
this.printer = await this.web_munch.findModule('gql-printer');
|
|
||||||
this.gql_print = this.printer && this.printer.print;
|
|
||||||
|
|
||||||
// Register middleware so that we can intercept requests.
|
// Register middleware so that we can intercept requests.
|
||||||
if ( ! this.client.link || ! this.client.queryManager || ! this.client.queryManager.link ) {
|
if ( ! this.client.link || ! this.client.queryManager || ! this.client.queryManager.link ) {
|
||||||
this.log.error('Apollo does not have a Link. We are unable to manipulate queries.');
|
this.log.error('Apollo does not have a Link. We are unable to manipulate queries.');
|
||||||
|
@ -216,19 +237,25 @@ export default class Apollo extends Module {
|
||||||
|
|
||||||
onDisable() {
|
onDisable() {
|
||||||
// Remove our references to things.
|
// Remove our references to things.
|
||||||
this.client = this.printer = this.gql_print = this.old_link = this.old_qm_dedup = this.old_qm_link = null;
|
this.client = this.printer = this._gql_print = this.old_link = this.old_qm_dedup = this.old_qm_link = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
apolloPreFlight(request) {
|
apolloPreFlight(request) {
|
||||||
const operation = request.operationName,
|
const operation = request.operationName,
|
||||||
qm = this.client.queryManager,
|
modifiers = this.modifiers[operation],
|
||||||
|
event = `:request.${operation}`,
|
||||||
|
has_listeners = this.hasListeners(event);
|
||||||
|
|
||||||
|
if ( ! modifiers && ! has_listeners )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const qm = this.client.queryManager,
|
||||||
id_map = qm && qm.queryIdsByName,
|
id_map = qm && qm.queryIdsByName,
|
||||||
query_map = qm && qm.queries,
|
query_map = qm && qm.queries,
|
||||||
raw_id = id_map && id_map[operation],
|
raw_id = id_map && id_map[operation],
|
||||||
id = Array.isArray(raw_id) ? raw_id[0] : raw_id,
|
id = Array.isArray(raw_id) ? raw_id[0] : raw_id,
|
||||||
query = query_map && query_map.get(id),
|
query = query_map && query_map.get(id);
|
||||||
modifiers = this.modifiers[operation];
|
|
||||||
|
|
||||||
if ( modifiers ) {
|
if ( modifiers ) {
|
||||||
for(const mod of modifiers) {
|
for(const mod of modifiers) {
|
||||||
|
@ -239,13 +266,25 @@ export default class Apollo extends Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit(`:request.${operation}`, request.query, request.variables);
|
let modified = !! modifiers;
|
||||||
|
|
||||||
|
if ( has_listeners ) {
|
||||||
|
const e = new ApolloEvent({
|
||||||
|
operation,
|
||||||
|
request
|
||||||
|
});
|
||||||
|
|
||||||
|
this.emit(event, e);
|
||||||
|
if ( e._changed )
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( modified ) {
|
||||||
// Wipe the old query data. This is obviously not optimal, but Apollo will
|
// Wipe the old query data. This is obviously not optimal, but Apollo will
|
||||||
// raise an exception otherwise because the query string doesn't match.
|
// raise an exception otherwise because the query string doesn't match.
|
||||||
|
|
||||||
const q = this.client.queryManager.queryStore.store[id],
|
const q = this.client.queryManager.queryStore.store[id],
|
||||||
qs = this.gql_print && this.gql_print(request.query);
|
qs = this.gqlPrint && this.gqlPrint(request.query);
|
||||||
|
|
||||||
if ( q )
|
if ( q )
|
||||||
if ( qs ) {
|
if ( qs ) {
|
||||||
|
@ -264,6 +303,7 @@ export default class Apollo extends Module {
|
||||||
this.client.queryManager.queryStore.store[id] = null;
|
this.client.queryManager.queryStore.store[id] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
apolloPostFlight(response) {
|
apolloPostFlight(response) {
|
||||||
if ( ! response.extensions )
|
if ( ! response.extensions )
|
||||||
|
|
|
@ -7,7 +7,17 @@
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
import {has} from 'utilities/object';
|
import {has} from 'utilities/object';
|
||||||
|
import { DEBUG } from '../constants';
|
||||||
|
|
||||||
|
const NAMES = [
|
||||||
|
'webpackJsonp',
|
||||||
|
'webpackChunktwitch_twilight'
|
||||||
|
];
|
||||||
|
|
||||||
|
const HARD_MODULES = [
|
||||||
|
[0, 'vendor'],
|
||||||
|
[1, 'core']
|
||||||
|
];
|
||||||
|
|
||||||
let last_muncher = 0;
|
let last_muncher = 0;
|
||||||
|
|
||||||
|
@ -20,9 +30,11 @@ export default class WebMunch extends Module {
|
||||||
this._original_loader = null;
|
this._original_loader = null;
|
||||||
this._known_rules = {};
|
this._known_rules = {};
|
||||||
this._require = null;
|
this._require = null;
|
||||||
this._module_names = {};
|
this._chunk_names = {};
|
||||||
this._mod_cache = {};
|
this._mod_cache = {};
|
||||||
|
|
||||||
|
this._known_ids = new Set;
|
||||||
|
|
||||||
this.v4 = null;
|
this.v4 = null;
|
||||||
|
|
||||||
this.hookLoader();
|
this.hookLoader();
|
||||||
|
@ -38,37 +50,47 @@ export default class WebMunch extends Module {
|
||||||
if ( this._original_loader )
|
if ( this._original_loader )
|
||||||
return this.log.warn('Attempted to call hookLoader twice.');
|
return this.log.warn('Attempted to call hookLoader twice.');
|
||||||
|
|
||||||
if ( ! window.webpackJsonp ) {
|
let name;
|
||||||
|
for(const n of NAMES)
|
||||||
|
if ( window[n] ) {
|
||||||
|
name = n;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! name ) {
|
||||||
if ( attempts > 500 )
|
if ( attempts > 500 )
|
||||||
return this.log.error("Unable to find webpack's loader after two minutes.");
|
return this.log.error("Unable to find webpack's loader after two minutes.");
|
||||||
|
|
||||||
return setTimeout(this.hookLoader.bind(this, attempts + 1), 250);
|
return setTimeout(this.hookLoader.bind(this, attempts + 1), 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( typeof window.webpackJsonp === 'function' ) {
|
const thing = window[name];
|
||||||
|
|
||||||
|
if ( typeof thing === 'function' ) {
|
||||||
// v3
|
// v3
|
||||||
this.v4 = false;
|
this.v4 = false;
|
||||||
this._original_loader = window.webpackJsonp;
|
this._original_loader = thing;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
window.webpackJsonp = this.webpackJsonpv3.bind(this);
|
window[name] = this.webpackJsonpv3.bind(this);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
this.log.warn('Unable to wrap webpackJsonp due to write protection.');
|
this.log.warn('Unable to wrap webpackJsonp due to write protection.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if ( Array.isArray(window.webpackJsonp) ) {
|
} else if ( Array.isArray(thing) ) {
|
||||||
// v4
|
// v4
|
||||||
this.v4 = true;
|
this.v4 = true;
|
||||||
this._original_loader = window.webpackJsonp.push;
|
this._original_store = thing;
|
||||||
|
this._original_loader = thing.push;
|
||||||
|
|
||||||
// Wrap all existing modules in case any of them haven't been required yet.
|
// Wrap all existing modules in case any of them haven't been required yet.
|
||||||
for(const chunk of window.webpackJsonp)
|
for(const chunk of thing)
|
||||||
if ( chunk && chunk[1] )
|
if ( chunk && chunk[1] )
|
||||||
this.processModulesV4(chunk[1]);
|
this.processModulesV4(chunk[1]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
window.webpackJsonp.push = this.webpackJsonpv4.bind(this);
|
thing.push = this.webpackJsonpv4.bind(this);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
this.log.warn('Unable to wrap webpackJsonp (v4) due to write protection.');
|
this.log.warn('Unable to wrap webpackJsonp (v4) due to write protection.');
|
||||||
return;
|
return;
|
||||||
|
@ -84,9 +106,9 @@ export default class WebMunch extends Module {
|
||||||
|
|
||||||
|
|
||||||
webpackJsonpv3(chunk_ids, modules) {
|
webpackJsonpv3(chunk_ids, modules) {
|
||||||
const names = chunk_ids.map(x => this._module_names[x] || x).join(', ');
|
const names = chunk_ids.map(x => this._chunk_names[x] || x).join(', ');
|
||||||
this.log.debug(`Twitch Chunk Loaded: ${chunk_ids} (${names})`);
|
this.log.verbose(`Twitch Chunk Loaded: ${chunk_ids} (${names})`);
|
||||||
this.log.debug(`Modules: ${Object.keys(modules)}`);
|
this.log.verbose(`Modules: ${Object.keys(modules)}`);
|
||||||
|
|
||||||
const res = this._original_loader.apply(window, arguments); // eslint-disable-line prefer-rest-params
|
const res = this._original_loader.apply(window, arguments); // eslint-disable-line prefer-rest-params
|
||||||
|
|
||||||
|
@ -101,6 +123,7 @@ export default class WebMunch extends Module {
|
||||||
|
|
||||||
for(const mod_id in modules)
|
for(const mod_id in modules)
|
||||||
if ( has(modules, mod_id) ) {
|
if ( has(modules, mod_id) ) {
|
||||||
|
this._known_ids.add(mod_id);
|
||||||
const original_module = modules[mod_id];
|
const original_module = modules[mod_id];
|
||||||
modules[mod_id] = function(module, exports, require, ...args) {
|
modules[mod_id] = function(module, exports, require, ...args) {
|
||||||
if ( ! t._require && typeof require === 'function' ) {
|
if ( ! t._require && typeof require === 'function' ) {
|
||||||
|
@ -127,15 +150,15 @@ export default class WebMunch extends Module {
|
||||||
webpackJsonpv4(data) {
|
webpackJsonpv4(data) {
|
||||||
const chunk_ids = data[0],
|
const chunk_ids = data[0],
|
||||||
modules = data[1],
|
modules = data[1],
|
||||||
names = Array.isArray(chunk_ids) && chunk_ids.map(x => this._module_names[x] || x).join(', ');
|
names = Array.isArray(chunk_ids) && chunk_ids.map(x => this._chunk_names[x] || x).join(', ');
|
||||||
|
|
||||||
this.log.debug(`Twitch Chunk Loaded: ${chunk_ids} (${names})`);
|
this.log.verbose(`Twitch Chunk Loaded: ${chunk_ids} (${names})`);
|
||||||
this.log.debug(`Modules: ${Object.keys(modules)}`);
|
this.log.verbose(`Modules: ${Object.keys(modules)}`);
|
||||||
|
|
||||||
if ( modules )
|
if ( modules )
|
||||||
this.processModulesV4(modules);
|
this.processModulesV4(modules);
|
||||||
|
|
||||||
const res = this._original_loader.apply(window.webpackJsonp, arguments); // eslint-disable-line prefer-rest-params
|
const res = this._original_loader.apply(this._original_store, arguments); // eslint-disable-line prefer-rest-params
|
||||||
this.emit(':loaded', chunk_ids, names, modules);
|
this.emit(':loaded', chunk_ids, names, modules);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -166,6 +189,54 @@ export default class WebMunch extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
findDeep(chunks, predicate, multi = true) {
|
||||||
|
if ( chunks && ! Array.isArray(chunks) )
|
||||||
|
chunks = [chunks];
|
||||||
|
|
||||||
|
if ( ! this._require || ! this.v4 || ! this._original_store )
|
||||||
|
return new Error('We do not have webpack');
|
||||||
|
|
||||||
|
const out = [],
|
||||||
|
names = this._chunk_names;
|
||||||
|
for(const [cs, modules] of this._original_store) {
|
||||||
|
if ( chunks ) {
|
||||||
|
let matched = false;
|
||||||
|
for(const c of cs) {
|
||||||
|
if ( chunks.includes(c) || chunks.includes(`${c}`) || (names[c] && chunks.includes(names[c])) ) {
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! matched )
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const id of Object.keys(modules)) {
|
||||||
|
try {
|
||||||
|
const mod = this._require(id);
|
||||||
|
for(const key in mod)
|
||||||
|
if ( mod[key] && predicate(mod[key]) ) {
|
||||||
|
this.log.info(`Found in key "${key}" of module "${id}" (${this.chunkNameForModule(id)})`);
|
||||||
|
if ( ! multi )
|
||||||
|
return mod;
|
||||||
|
out.push(mod);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
this.log.warn('Exception while deep scanning webpack.', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( out.length )
|
||||||
|
return out;
|
||||||
|
|
||||||
|
this.log.info('Unable to find deep scan target.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
getModule(key, predicate) {
|
getModule(key, predicate) {
|
||||||
if ( typeof key === 'function' ) {
|
if ( typeof key === 'function' ) {
|
||||||
predicate = key;
|
predicate = key;
|
||||||
|
@ -176,7 +247,7 @@ export default class WebMunch extends Module {
|
||||||
return this._mod_cache[key];
|
return this._mod_cache[key];
|
||||||
|
|
||||||
const require = this._require;
|
const require = this._require;
|
||||||
if ( ! require || ! require.c )
|
if ( ! require )
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if ( ! predicate )
|
if ( ! predicate )
|
||||||
|
@ -185,19 +256,149 @@ export default class WebMunch extends Module {
|
||||||
if ( ! predicate )
|
if ( ! predicate )
|
||||||
throw new Error(`no known predicate for locating ${key}`);
|
throw new Error(`no known predicate for locating ${key}`);
|
||||||
|
|
||||||
for(const k in require.c)
|
if ( require.c )
|
||||||
|
return this._oldGetModule(key, predicate, require);
|
||||||
|
|
||||||
|
if ( require.m )
|
||||||
|
return this._newGetModule(key, predicate, require);
|
||||||
|
}
|
||||||
|
|
||||||
|
_chunksForModule(id) {
|
||||||
|
if ( ! this.v4 )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if ( ! this._original_store )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for(const [chunks, modules] of this._original_store) {
|
||||||
|
if ( modules[id] )
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chunkNameForModule(id) {
|
||||||
|
const chunks = this._chunksForModule(id);
|
||||||
|
if ( ! chunks )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for(const chunk of chunks) {
|
||||||
|
const name = this._chunk_names[chunk];
|
||||||
|
if ( name )
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_oldGetModule(key, predicate, require) {
|
||||||
|
if ( ! require || ! require.c )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let ids;
|
||||||
|
if ( this._original_store && predicate.chunks ) {
|
||||||
|
const chunk_pred = typeof predicate.chunks === 'function';
|
||||||
|
if ( ! chunk_pred && ! Array.isArray(predicate.chunks) )
|
||||||
|
predicate.chunks = [predicate.chunks];
|
||||||
|
|
||||||
|
const chunks = predicate.chunks,
|
||||||
|
names = this._chunk_names;
|
||||||
|
|
||||||
|
ids = [];
|
||||||
|
for(const [cs, modules] of this._original_store) {
|
||||||
|
let matched = false;
|
||||||
|
for(const c of cs) {
|
||||||
|
if ( chunk_pred ? chunks(names[c], c) : (chunks.includes(c) || chunks.includes(String(c)) || (names[c] && chunks.includes(names[c]))) ) {
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( matched )
|
||||||
|
ids = [...ids, ...Object.keys(modules)];
|
||||||
|
}
|
||||||
|
|
||||||
|
ids = new Set(ids);
|
||||||
|
} else
|
||||||
|
ids = Object.keys(require.c);
|
||||||
|
|
||||||
|
let checked = 0;
|
||||||
|
for(const k of ids)
|
||||||
if ( has(require.c, k) ) {
|
if ( has(require.c, k) ) {
|
||||||
|
checked++;
|
||||||
const module = require.c[k],
|
const module = require.c[k],
|
||||||
mod = module && module.exports;
|
mod = module && module.exports;
|
||||||
|
|
||||||
if ( mod && predicate(mod) ) {
|
if ( mod ) {
|
||||||
|
const ret = predicate(mod);
|
||||||
|
if ( ret ) {
|
||||||
|
this.log.debug(`Located module "${key}" in module ${k}${DEBUG ? ` (${this.chunkNameForModule(k)})` : ''} after ${checked} tries`);
|
||||||
|
const out = predicate.use_result ? ret : mod;
|
||||||
if ( key )
|
if ( key )
|
||||||
this._mod_cache[key] = mod;
|
this._mod_cache[key] = out;
|
||||||
return mod;
|
return out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.log.debug(`Unable to locate module "${key}"`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_newGetModule(key, predicate, require) {
|
||||||
|
if ( ! require )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let ids = this._known_ids;
|
||||||
|
if ( this._original_store && predicate.chunks ) {
|
||||||
|
const chunk_pred = typeof predicate.chunks === 'function';
|
||||||
|
if ( ! chunk_pred && ! Array.isArray(predicate.chunks) )
|
||||||
|
predicate.chunks = [predicate.chunks];
|
||||||
|
|
||||||
|
const chunks = predicate.chunks,
|
||||||
|
names = this._chunk_names;
|
||||||
|
|
||||||
|
ids = [];
|
||||||
|
for(const [cs, modules] of this._original_store) {
|
||||||
|
let matched = false;
|
||||||
|
for(const c of cs) {
|
||||||
|
if ( chunk_pred ? chunks(names[c], c) : (chunks.includes(c) || chunks.includes(String(c)) || (names[c] && chunks.includes(names[c]))) ) {
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( matched )
|
||||||
|
ids = [...ids, ...Object.keys(modules)];
|
||||||
|
}
|
||||||
|
|
||||||
|
ids = new Set(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
let checked = 0;
|
||||||
|
for(const id of ids) {
|
||||||
|
try {
|
||||||
|
checked++;
|
||||||
|
const mod = require(id);
|
||||||
|
if ( mod ) {
|
||||||
|
const ret = predicate(mod);
|
||||||
|
if ( ret ) {
|
||||||
|
this.log.debug(`Located module "${key}" in module ${id}${DEBUG ? ` (${this.chunkNameForModule(id)})` : ''} after ${checked} tries`);
|
||||||
|
const out = predicate.use_result ? ret : mod;
|
||||||
|
if ( key )
|
||||||
|
this._mod_cache[key] = out;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
this.log.warn('Unexpected error trying to find module', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log.debug(`Unable to locate module "${key}"`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// Grabbing Require
|
// Grabbing Require
|
||||||
|
@ -256,10 +457,38 @@ export default class WebMunch extends Module {
|
||||||
try {
|
try {
|
||||||
modules = JSON.parse(data[1].replace(/(\d+):/g, '"$1":'))
|
modules = JSON.parse(data[1].replace(/(\d+):/g, '"$1":'))
|
||||||
} catch(err) { } // eslint-disable-line no-empty
|
} catch(err) { } // eslint-disable-line no-empty
|
||||||
|
|
||||||
|
} else if ( require.u ) {
|
||||||
|
const builder = require.u.toString(),
|
||||||
|
match = /assets\/"\+({\d+:.*?})/.exec(builder),
|
||||||
|
data = match ? match[1].replace(/([\de]+):/g, (_, m) => {
|
||||||
|
if ( /^\d+e\d+$/.test(m) ) {
|
||||||
|
const bits = m.split('e');
|
||||||
|
m = parseInt(bits[0], 10) * (10 ** parseInt(bits[1], 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
return `"${m}":`;
|
||||||
|
}) : null;
|
||||||
|
|
||||||
|
if ( data )
|
||||||
|
try {
|
||||||
|
modules = JSON.parse(data);
|
||||||
|
} catch(err) { console.log(data); console.log(err) /* no-op */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( modules ) {
|
if ( modules ) {
|
||||||
this._module_names = modules;
|
// Ensure that vendor and core have names.
|
||||||
|
if ( this._original_store ) {
|
||||||
|
for(const [pos, name] of HARD_MODULES) {
|
||||||
|
const mods = this._original_store[pos]?.[0];
|
||||||
|
if ( Array.isArray(mods) )
|
||||||
|
for(const id of mods)
|
||||||
|
if ( typeof id !== 'object' && ! modules[id] )
|
||||||
|
modules[id] = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._chunk_names = modules;
|
||||||
this.log.info(`Loaded names for ${Object.keys(modules).length} chunks from require().`)
|
this.log.info(`Loaded names for ${Object.keys(modules).length} chunks from require().`)
|
||||||
} else
|
} else
|
||||||
this.log.warn(`Unable to find chunk names in require().`);
|
this.log.warn(`Unable to find chunk names in require().`);
|
||||||
|
|
|
@ -127,7 +127,6 @@ export function createElement(tag, props, ...children) {
|
||||||
else if ( BOOLEAN_ATTRS.includes(lk) ) {
|
else if ( BOOLEAN_ATTRS.includes(lk) ) {
|
||||||
if ( prop && prop !== 'false' )
|
if ( prop && prop !== 'false' )
|
||||||
el.setAttribute(key, prop);
|
el.setAttribute(key, prop);
|
||||||
console.log('bool-attr', key, prop);
|
|
||||||
|
|
||||||
} else if ( lk.startsWith('aria-') || ATTRS.includes(lk) )
|
} else if ( lk.startsWith('aria-') || ATTRS.includes(lk) )
|
||||||
el.setAttribute(key, prop);
|
el.setAttribute(key, prop);
|
||||||
|
|
|
@ -124,6 +124,10 @@ export class EventEmitter {
|
||||||
return list ? Array.from(list) : [];
|
return list ? Array.from(list) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasListeners(event) {
|
||||||
|
return !! this.__listeners[event]
|
||||||
|
}
|
||||||
|
|
||||||
emitUnsafe(event, ...args) {
|
emitUnsafe(event, ...args) {
|
||||||
let list = this.__listeners[event];
|
let list = this.__listeners[event];
|
||||||
if ( ! list )
|
if ( ! list )
|
||||||
|
@ -446,6 +450,7 @@ export class HierarchicalEventEmitter extends EventEmitter {
|
||||||
waitFor(event) { return super.waitFor(this.abs_path(event)) }
|
waitFor(event) { return super.waitFor(this.abs_path(event)) }
|
||||||
off(event, fn, ctx) { return super.off(this.abs_path(event), fn, ctx) }
|
off(event, fn, ctx) { return super.off(this.abs_path(event), fn, ctx) }
|
||||||
listeners(event) { return super.listeners(this.abs_path(event)) }
|
listeners(event) { return super.listeners(this.abs_path(event)) }
|
||||||
|
hasListeners(event) { return super.hasListeners(this.abs_path(event)) }
|
||||||
|
|
||||||
emit(event, ...args) { return super.emit(this.abs_path(event), ...args) }
|
emit(event, ...args) { return super.emit(this.abs_path(event), ...args) }
|
||||||
emitUnsafe(event, ...args) { return super.emitUnsafe(this.abs_path(event), ...args) }
|
emitUnsafe(event, ...args) { return super.emitUnsafe(this.abs_path(event), ...args) }
|
||||||
|
|
|
@ -8,6 +8,22 @@ const RAVEN_LEVELS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function readLSLevel() {
|
||||||
|
const level = localStorage.ffzLogLevel;
|
||||||
|
if ( ! level )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const upper = level.toUpperCase();
|
||||||
|
if ( Logger[upper] )
|
||||||
|
return Logger[upper];
|
||||||
|
|
||||||
|
if ( /^\d+$/.test(level) )
|
||||||
|
return parseInt(level, 10);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export class Logger {
|
export class Logger {
|
||||||
constructor(parent, name, level, raven) {
|
constructor(parent, name, level, raven) {
|
||||||
this.root = parent ? parent.root : this;
|
this.root = parent ? parent.root : this;
|
||||||
|
@ -21,7 +37,7 @@ export class Logger {
|
||||||
|
|
||||||
this.init = false;
|
this.init = false;
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
this.level = level || (parent && parent.level) || Logger.DEFAULT_LEVEL;
|
this.level = level ?? (parent && parent.level) ?? readLSLevel() ?? Logger.DEFAULT_LEVEL;
|
||||||
this.raven = raven || (parent && parent.raven);
|
this.raven = raven || (parent && parent.raven);
|
||||||
|
|
||||||
this.children = {};
|
this.children = {};
|
||||||
|
@ -34,6 +50,10 @@ export class Logger {
|
||||||
return this.children[name];
|
return this.children[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verbose(...args) {
|
||||||
|
return this.invoke(Logger.VERBOSE, args);
|
||||||
|
}
|
||||||
|
|
||||||
debug(...args) {
|
debug(...args) {
|
||||||
return this.invoke(Logger.DEBUG, args);
|
return this.invoke(Logger.DEBUG, args);
|
||||||
}
|
}
|
||||||
|
@ -79,6 +99,7 @@ export class Logger {
|
||||||
|
|
||||||
const message = Array.prototype.slice.call(args);
|
const message = Array.prototype.slice.call(args);
|
||||||
|
|
||||||
|
if ( level !== Logger.VERBOSE ) {
|
||||||
if ( this.root.init )
|
if ( this.root.init )
|
||||||
this.root.captured_init.push({
|
this.root.captured_init.push({
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
|
@ -92,13 +113,14 @@ export class Logger {
|
||||||
category: this.name,
|
category: this.name,
|
||||||
level: RAVEN_LEVELS[level] || level
|
level: RAVEN_LEVELS[level] || level
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if ( this.name )
|
if ( this.name )
|
||||||
message.unshift(`%c${this.root.label} [%c${this.name}%c]:%c`, 'color:#755000; font-weight:bold', '', 'color:#755000; font-weight:bold', '');
|
message.unshift(`%c${this.root.label} [%c${this.name}%c]:%c`, 'color:#755000; font-weight:bold', '', 'color:#755000; font-weight:bold', '');
|
||||||
else
|
else
|
||||||
message.unshift(`%c${this.root.label}:%c`, 'color:#755000; font-weight:bold', '');
|
message.unshift(`%c${this.root.label}:%c`, 'color:#755000; font-weight:bold', '');
|
||||||
|
|
||||||
if ( level === Logger.DEBUG )
|
if ( level === Logger.DEBUG || level === Logger.VERBOSE )
|
||||||
console.debug(...message);
|
console.debug(...message);
|
||||||
|
|
||||||
else if ( level === Logger.INFO )
|
else if ( level === Logger.INFO )
|
||||||
|
@ -115,9 +137,7 @@ export class Logger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.VERBOSE = 0;
|
||||||
Logger.DEFAULT_LEVEL = 2;
|
|
||||||
|
|
||||||
Logger.DEBUG = 1;
|
Logger.DEBUG = 1;
|
||||||
Logger.INFO = 2;
|
Logger.INFO = 2;
|
||||||
Logger.WARN = 4;
|
Logger.WARN = 4;
|
||||||
|
@ -125,4 +145,6 @@ Logger.WARNING = 4;
|
||||||
Logger.ERROR = 8;
|
Logger.ERROR = 8;
|
||||||
Logger.OFF = 99;
|
Logger.OFF = 99;
|
||||||
|
|
||||||
|
Logger.DEFAULT_LEVEL = Logger.INFO;
|
||||||
|
|
||||||
export default Logger;
|
export default Logger;
|
130
src/utilities/path-parser.js
Normal file
130
src/utilities/path-parser.js
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
export function parse(path) {
|
||||||
|
return parseAST({
|
||||||
|
path,
|
||||||
|
i: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAST(ctx) {
|
||||||
|
const path = ctx.path,
|
||||||
|
length = path.length,
|
||||||
|
out = [];
|
||||||
|
|
||||||
|
let token, raw;
|
||||||
|
let old_tab = false,
|
||||||
|
old_page = false;
|
||||||
|
|
||||||
|
while ( ctx.i < length ) {
|
||||||
|
const start = ctx.i,
|
||||||
|
char = path[start],
|
||||||
|
next = path[start + 1];
|
||||||
|
|
||||||
|
if ( ! token ) {
|
||||||
|
raw = [];
|
||||||
|
token = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON
|
||||||
|
if ( char === '@' && next === '{') {
|
||||||
|
ctx.i++;
|
||||||
|
const tag = parseJSON(ctx);
|
||||||
|
if ( tag )
|
||||||
|
Object.assign(token, tag);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Segment End?
|
||||||
|
const tab = char === `~` && next === '>',
|
||||||
|
page = char === '>' && next === '>',
|
||||||
|
segment = ! page && char === '>';
|
||||||
|
|
||||||
|
if ( ! segment && ! page && ! tab ) {
|
||||||
|
raw.push(char);
|
||||||
|
ctx.i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're at the end of a segment, so push
|
||||||
|
// the token out.
|
||||||
|
if ( tab || page )
|
||||||
|
ctx.i++;
|
||||||
|
|
||||||
|
token.title = raw.join('').trim();
|
||||||
|
token.key = token.title.toSnakeCase();
|
||||||
|
|
||||||
|
token.page = old_page;
|
||||||
|
token.tab = old_tab;
|
||||||
|
|
||||||
|
old_page = page;
|
||||||
|
old_tab = tab;
|
||||||
|
|
||||||
|
out.push(token);
|
||||||
|
token = raw = null;
|
||||||
|
ctx.i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( token ) {
|
||||||
|
token.title = raw.join('').trim();
|
||||||
|
token.key = token.title.toSnakeCase();
|
||||||
|
token.page = old_page;
|
||||||
|
token.tab = old_tab;
|
||||||
|
out.push(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJSON(ctx) {
|
||||||
|
const path = ctx.path,
|
||||||
|
length = path.length,
|
||||||
|
|
||||||
|
start = ctx.i;
|
||||||
|
|
||||||
|
ctx.i++;
|
||||||
|
|
||||||
|
const stack = ['{'];
|
||||||
|
let string = false;
|
||||||
|
|
||||||
|
while ( ctx.i < length && stack.length ) {
|
||||||
|
const start = ctx.i,
|
||||||
|
char = path[start];
|
||||||
|
|
||||||
|
if ( string ) {
|
||||||
|
if ( char === '\\' ) {
|
||||||
|
ctx.i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (char === '"' || char === "'") && char === string ) {
|
||||||
|
stack.pop();
|
||||||
|
string = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if ( char === '"' || char === "'" ) {
|
||||||
|
string = char;
|
||||||
|
stack.push(char);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( char === '{' || char === '[' )
|
||||||
|
stack.push(char);
|
||||||
|
|
||||||
|
if ( char === ']' ) {
|
||||||
|
if ( stack.pop() !== '[' )
|
||||||
|
throw new SyntaxError('Invalid JSON');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( char === '}' ) {
|
||||||
|
if ( stack.pop() !== '{' )
|
||||||
|
throw new SyntaxError('Invalid JSON');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(path.slice(start, ctx.i));
|
||||||
|
}
|
|
@ -45,6 +45,7 @@ export class Tooltip {
|
||||||
this.check_modifiers = this.options.check_modifiers;
|
this.check_modifiers = this.options.check_modifiers;
|
||||||
|
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
|
this.container = this.options.container || this.parent;
|
||||||
this.cls = cls;
|
this.cls = cls;
|
||||||
|
|
||||||
if ( this.check_modifiers )
|
if ( this.check_modifiers )
|
||||||
|
@ -134,6 +135,7 @@ export class Tooltip {
|
||||||
|
|
||||||
this.elements = null;
|
this.elements = null;
|
||||||
this._onMouseOut = this._onMouseOver = null;
|
this._onMouseOut = this._onMouseOver = null;
|
||||||
|
this.container = null;
|
||||||
this.parent = null;
|
this.parent = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,8 +369,9 @@ export class Tooltip {
|
||||||
|
|
||||||
tip._update = () => {
|
tip._update = () => {
|
||||||
if ( tip.popper ) {
|
if ( tip.popper ) {
|
||||||
tip.popper.destroy();
|
tip.popper.update();
|
||||||
tip.popper = new Popper(popper_target, el, pop_opts);
|
/*tip.popper.destroy();
|
||||||
|
tip.popper = new Popper(popper_target, el, pop_opts);*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,7 +415,7 @@ export class Tooltip {
|
||||||
|
|
||||||
// Add everything to the DOM and create the Popper instance.
|
// Add everything to the DOM and create the Popper instance.
|
||||||
tip.popper = new Popper(popper_target, el, pop_opts);
|
tip.popper = new Popper(popper_target, el, pop_opts);
|
||||||
this.parent.appendChild(el);
|
this.container.appendChild(el);
|
||||||
tip.visible = true;
|
tip.visible = true;
|
||||||
|
|
||||||
if ( opts.onShow )
|
if ( opts.onShow )
|
||||||
|
|
|
@ -138,11 +138,9 @@ export default class TwitchData extends Module {
|
||||||
return this._search;
|
return this._search;
|
||||||
|
|
||||||
const apollo = this.apollo.client,
|
const apollo = this.apollo.client,
|
||||||
core = this.site.getCore(),
|
core = this.site.getCore();
|
||||||
|
|
||||||
search_module = this.web_munch.getModule('algolia-search'),
|
|
||||||
SearchClient = search_module && search_module.a;
|
|
||||||
|
|
||||||
|
const SearchClient = this.web_munch.getModule('algolia-search');
|
||||||
if ( ! SearchClient || ! apollo || ! core )
|
if ( ! SearchClient || ! apollo || ! core )
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue