mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +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",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.20.69",
|
||||
"version": "4.20.70",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
|
|
|
@ -70,7 +70,7 @@ export const Links = {
|
|||
i18n: this.i18n,
|
||||
allow_media: show_images,
|
||||
allow_unsafe: show_unsafe,
|
||||
onload: tip.update
|
||||
onload: () => requestAnimationFrame(() => tip.update())
|
||||
};
|
||||
|
||||
let content;
|
||||
|
@ -214,7 +214,7 @@ Links.tooltip.interactive = 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 64;
|
||||
|
|
|
@ -112,7 +112,7 @@ export default class TooltipProvider extends Module {
|
|||
|
||||
|
||||
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);
|
||||
|
||||
|
@ -125,12 +125,17 @@ export default class TooltipProvider extends Module {
|
|||
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, {
|
||||
html: true,
|
||||
i18n: this.i18n,
|
||||
live: true,
|
||||
check_modifiers: true,
|
||||
container: tip_container || container,
|
||||
|
||||
delayHide: this.checkDelayHide.bind(this, default_type),
|
||||
delayShow: this.checkDelayShow.bind(this, default_type),
|
||||
|
|
|
@ -174,43 +174,111 @@ export default class Twilight extends BaseSite {
|
|||
}
|
||||
|
||||
getCore() {
|
||||
if ( this._core )
|
||||
return this._core;
|
||||
if ( ! this._core )
|
||||
this._core = this.web_munch.getModule('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;
|
||||
return this._core;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const CALCULATE_BITS = '_calculateChangedBits';
|
||||
|
||||
Twilight.KNOWN_MODULES = {
|
||||
simplebar: n => n.globalObserver && n.initDOMLoadedElements,
|
||||
react: n => n.Component && n.createElement,
|
||||
'core-1': n => n.o && n.o.experiments,
|
||||
'core-2': n => n.p && n.p.experiments,
|
||||
'core-3': n => n.q && n.q.experiments,
|
||||
core: n => {
|
||||
if ( n['$6']?.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,
|
||||
'extension-service': n => n.extensionService,
|
||||
'chat-types': n => n.b && has(n.b, 'Message') && has(n.b, 'RoomMods'),
|
||||
'gql-printer': n => n !== window && n.print,
|
||||
'chat-types': n => {
|
||||
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,
|
||||
'algolia-search': n => n.a && n.a.prototype && n.a.prototype.queryTopResults && n.a.prototype.queryForType,
|
||||
highlightstack: n => n.b && has(n.b, '_calculateChangedBits') && n.c && has(n.c, '_calculateChangedBits')
|
||||
'algolia-search': n => {
|
||||
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 = [
|
||||
'embed-chat',
|
||||
'popout'
|
||||
'popout',
|
||||
'dash-popout-chat',
|
||||
'mod-popout-chat'
|
||||
];
|
||||
|
||||
|
||||
|
@ -233,7 +301,9 @@ Twilight.CHAT_ROUTES = [
|
|||
'squad',
|
||||
'command-center',
|
||||
'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',
|
||||
'dash': 'Dashboard',
|
||||
'popout': 'Popout Chat',
|
||||
'dash-chat': 'Dashboard Popout Chat',
|
||||
'dash-popout-chat': 'Dashboard Popout Chat',
|
||||
'user-video': 'Channel Video',
|
||||
'popout-player': 'Popout/Embed Player'
|
||||
};
|
||||
|
@ -293,6 +363,7 @@ Twilight.DASH_ROUTES = {
|
|||
'dash-settings-revenue': '/u/:userName/settings/revenue',
|
||||
'dash-extensions': '/u/:userName/extensions',
|
||||
'dash-streaming-tools': '/u/:userName/broadcast',
|
||||
'dash-popout-chat': '/popout/u/:userName/stream-manager/chat',
|
||||
};
|
||||
|
||||
Twilight.ROUTES = {
|
||||
|
@ -313,7 +384,7 @@ Twilight.ROUTES = {
|
|||
//'dash-automod': '/:userName/dashboard/settings/automod',
|
||||
'event': '/event/:eventName',
|
||||
'popout': '/popout/:userName/chat',
|
||||
'dash-chat': '/popout/:userName/dashboard/live/chat',
|
||||
//'dash-chat': '/popout/:userName/dashboard/live/chat',
|
||||
'video': '/videos/:videoID',
|
||||
'user-video': '/:userName/video/:videoID',
|
||||
'user-videos': '/:userName/videos/:filter?',
|
||||
|
@ -331,7 +402,8 @@ Twilight.ROUTES = {
|
|||
'squad': '/:userName/squad',
|
||||
'command-center': '/:userName/commandcenter',
|
||||
'embed-chat': '/embed/:userName/chat',
|
||||
'mod-view': '/moderator/:userName'
|
||||
'mod-view': '/moderator/:userName',
|
||||
'mod-popout-chat': '/popout/moderator/:userName/chat'
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -193,8 +193,11 @@ export default class Channel extends Module {
|
|||
this.removePanelTips(inst);
|
||||
|
||||
if ( ! inst._ffz_tips ) {
|
||||
inst._ffz_tips = this.resolve('tooltips')._createInstance(el, 'tw-link', 'link');
|
||||
inst._ffz_tip_el = el;
|
||||
const tt = this.resolve('tooltips');
|
||||
if ( tt ) {
|
||||
inst._ffz_tips = tt._createInstance(el, 'tw-link', 'link', tt.getRoot());
|
||||
inst._ffz_tip_el = el;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -735,27 +735,32 @@ export default class ChatHook extends Module {
|
|||
|
||||
|
||||
async grabTypes() {
|
||||
const ct = await this.web_munch.findModule('chat-types'),
|
||||
changes = [];
|
||||
if ( this.types_loaded )
|
||||
return;
|
||||
|
||||
this.automod_types = ct && ct.a || AUTOMOD_TYPES;
|
||||
this.chat_types = ct && ct.b || CHAT_TYPES;
|
||||
this.message_types = ct && ct.c || MESSAGE_TYPES;
|
||||
this.mod_types = ct && ct.e || MOD_TYPES;
|
||||
const ct = await this.web_munch.findModule('chat-types');
|
||||
|
||||
this.automod_types = ct?.automod || AUTOMOD_TYPES;
|
||||
this.chat_types = ct?.chat || CHAT_TYPES;
|
||||
this.message_types = ct?.message || MESSAGE_TYPES;
|
||||
this.mod_types = ct?.mod || MOD_TYPES;
|
||||
|
||||
if ( ! ct )
|
||||
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');
|
||||
|
||||
if ( ct.b && ! shallow_object_equals(ct.b, CHAT_TYPES) )
|
||||
if ( ! shallow_object_equals(this.chat_types, 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');
|
||||
|
||||
if ( ct.e && ! shallow_object_equals(ct.e, MOD_TYPES) )
|
||||
if ( ! shallow_object_equals(this.mod_types, MOD_TYPES) )
|
||||
changes.push('MOD_TYPES');
|
||||
|
||||
if ( changes.length )
|
||||
|
@ -1296,15 +1301,15 @@ export default class ChatHook extends Module {
|
|||
|
||||
const t = this,
|
||||
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;
|
||||
|
||||
this.CommunityStackHandler = function() {
|
||||
const stack = React.useContext(Stack.b),
|
||||
dispatch = React.useContext(Stack.c);
|
||||
const stack = React.useContext(StackMod.stack),
|
||||
dispatch = React.useContext(StackMod.dispatch);
|
||||
|
||||
t.community_stack = stack;
|
||||
t.community_dispatch = dispatch;
|
||||
|
|
|
@ -5,4 +5,8 @@
|
|||
|
||||
.top-nav__menu > div:empty {
|
||||
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.fine');
|
||||
this.inject('site.router');
|
||||
|
||||
this.tried = new Set;
|
||||
}
|
||||
|
||||
|
||||
|
@ -64,29 +66,49 @@ export default class Switchboard extends Module {
|
|||
const router = await this.awaitRouter();
|
||||
|
||||
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.loadRoute(da_switch, location, true);
|
||||
this.loadOne();
|
||||
}
|
||||
|
||||
loadRoute(da_switch, location, with_params) {
|
||||
for(const route of da_switch.props.children) {
|
||||
loadOne() {
|
||||
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 )
|
||||
continue;
|
||||
|
||||
if ( with_params !== null && with_params !== route.props.path.includes(':') )
|
||||
continue;
|
||||
|
||||
if ( this.tried.has(route.props.path) )
|
||||
continue;
|
||||
|
||||
try {
|
||||
const reg = pathToRegexp(route.props.path);
|
||||
if ( ! reg.exec || reg.exec(location) )
|
||||
if ( ! reg.exec || reg.exec(this.location) )
|
||||
continue;
|
||||
|
||||
} catch(err) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.tried.add(route.props.path);
|
||||
this.log.info('Found Non-Matching Route', route.props.path);
|
||||
|
||||
const component_class = route.props.component;
|
||||
|
@ -107,6 +129,7 @@ export default class Switchboard extends Module {
|
|||
try {
|
||||
component.props.loader().then(() => {
|
||||
this.log.info('Successfully forced a chunk to load using route', route.props.path)
|
||||
this.waitAndSee();
|
||||
});
|
||||
} catch(err) {
|
||||
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 {
|
||||
component.props.children.props.loader().then(() => {
|
||||
this.log.info('Successfully forced a chunk to load using route', route.props.path)
|
||||
this.waitAndSee();
|
||||
});
|
||||
} catch(err) {
|
||||
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 {get} from 'utilities/object';
|
||||
import merge from 'utilities/graphql';
|
||||
import { FFZEvent } from 'utilities/events';
|
||||
|
||||
|
||||
/*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 {
|
||||
constructor(err) {
|
||||
super(`${err.message}; Location: ${err.locations}`);
|
||||
|
@ -49,11 +64,20 @@ export default class Apollo extends Module {
|
|||
this.modifiers = {};
|
||||
this.post_modifiers = {};
|
||||
|
||||
this.inject('..web_munch');
|
||||
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.
|
||||
let client = this.client;
|
||||
|
||||
|
@ -69,9 +93,6 @@ export default class Apollo extends Module {
|
|||
if ( ! client )
|
||||
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.
|
||||
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.');
|
||||
|
@ -216,19 +237,25 @@ export default class Apollo extends Module {
|
|||
|
||||
onDisable() {
|
||||
// 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) {
|
||||
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,
|
||||
query_map = qm && qm.queries,
|
||||
raw_id = id_map && id_map[operation],
|
||||
id = Array.isArray(raw_id) ? raw_id[0] : raw_id,
|
||||
query = query_map && query_map.get(id),
|
||||
modifiers = this.modifiers[operation];
|
||||
query = query_map && query_map.get(id);
|
||||
|
||||
if ( modifiers ) {
|
||||
for(const mod of modifiers) {
|
||||
|
@ -239,30 +266,43 @@ export default class Apollo extends Module {
|
|||
}
|
||||
}
|
||||
|
||||
this.emit(`:request.${operation}`, request.query, request.variables);
|
||||
let modified = !! modifiers;
|
||||
|
||||
// Wipe the old query data. This is obviously not optimal, but Apollo will
|
||||
// raise an exception otherwise because the query string doesn't match.
|
||||
if ( has_listeners ) {
|
||||
const e = new ApolloEvent({
|
||||
operation,
|
||||
request
|
||||
});
|
||||
|
||||
const q = this.client.queryManager.queryStore.store[id],
|
||||
qs = this.gql_print && this.gql_print(request.query);
|
||||
this.emit(event, e);
|
||||
if ( e._changed )
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if ( q )
|
||||
if ( qs ) {
|
||||
q.queryString = qs;
|
||||
request.query.loc.source.body = qs;
|
||||
request.query.loc.end = qs.length;
|
||||
if ( modified ) {
|
||||
// Wipe the old query data. This is obviously not optimal, but Apollo will
|
||||
// raise an exception otherwise because the query string doesn't match.
|
||||
|
||||
if ( query ) {
|
||||
query.document = request.query;
|
||||
if ( query.observableQuery && query.observableQuery.options )
|
||||
query.observableQuery.options.query = request.query;
|
||||
const q = this.client.queryManager.queryStore.store[id],
|
||||
qs = this.gqlPrint && this.gqlPrint(request.query);
|
||||
|
||||
if ( q )
|
||||
if ( qs ) {
|
||||
q.queryString = qs;
|
||||
request.query.loc.source.body = qs;
|
||||
request.query.loc.end = qs.length;
|
||||
|
||||
if ( query ) {
|
||||
query.document = request.query;
|
||||
if ( query.observableQuery && query.observableQuery.options )
|
||||
query.observableQuery.options.query = request.query;
|
||||
}
|
||||
|
||||
} else {
|
||||
this.log.info('Unable to find GQL Print. Clearing store for query:', operation);
|
||||
this.client.queryManager.queryStore.store[id] = null;
|
||||
}
|
||||
|
||||
} else {
|
||||
this.log.info('Unable to find GQL Print. Clearing store for query:', operation);
|
||||
this.client.queryManager.queryStore.store[id] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apolloPostFlight(response) {
|
||||
|
|
|
@ -7,7 +7,17 @@
|
|||
|
||||
import Module from 'utilities/module';
|
||||
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;
|
||||
|
||||
|
@ -20,9 +30,11 @@ export default class WebMunch extends Module {
|
|||
this._original_loader = null;
|
||||
this._known_rules = {};
|
||||
this._require = null;
|
||||
this._module_names = {};
|
||||
this._chunk_names = {};
|
||||
this._mod_cache = {};
|
||||
|
||||
this._known_ids = new Set;
|
||||
|
||||
this.v4 = null;
|
||||
|
||||
this.hookLoader();
|
||||
|
@ -38,37 +50,47 @@ export default class WebMunch extends Module {
|
|||
if ( this._original_loader )
|
||||
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 )
|
||||
return this.log.error("Unable to find webpack's loader after two minutes.");
|
||||
|
||||
return setTimeout(this.hookLoader.bind(this, attempts + 1), 250);
|
||||
}
|
||||
|
||||
if ( typeof window.webpackJsonp === 'function' ) {
|
||||
const thing = window[name];
|
||||
|
||||
if ( typeof thing === 'function' ) {
|
||||
// v3
|
||||
this.v4 = false;
|
||||
this._original_loader = window.webpackJsonp;
|
||||
this._original_loader = thing;
|
||||
|
||||
try {
|
||||
window.webpackJsonp = this.webpackJsonpv3.bind(this);
|
||||
window[name] = this.webpackJsonpv3.bind(this);
|
||||
} catch(err) {
|
||||
this.log.warn('Unable to wrap webpackJsonp due to write protection.');
|
||||
return;
|
||||
}
|
||||
|
||||
} else if ( Array.isArray(window.webpackJsonp) ) {
|
||||
} else if ( Array.isArray(thing) ) {
|
||||
// v4
|
||||
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.
|
||||
for(const chunk of window.webpackJsonp)
|
||||
for(const chunk of thing)
|
||||
if ( chunk && chunk[1] )
|
||||
this.processModulesV4(chunk[1]);
|
||||
|
||||
try {
|
||||
window.webpackJsonp.push = this.webpackJsonpv4.bind(this);
|
||||
thing.push = this.webpackJsonpv4.bind(this);
|
||||
} catch(err) {
|
||||
this.log.warn('Unable to wrap webpackJsonp (v4) due to write protection.');
|
||||
return;
|
||||
|
@ -84,9 +106,9 @@ export default class WebMunch extends Module {
|
|||
|
||||
|
||||
webpackJsonpv3(chunk_ids, modules) {
|
||||
const names = chunk_ids.map(x => this._module_names[x] || x).join(', ');
|
||||
this.log.debug(`Twitch Chunk Loaded: ${chunk_ids} (${names})`);
|
||||
this.log.debug(`Modules: ${Object.keys(modules)}`);
|
||||
const names = chunk_ids.map(x => this._chunk_names[x] || x).join(', ');
|
||||
this.log.verbose(`Twitch Chunk Loaded: ${chunk_ids} (${names})`);
|
||||
this.log.verbose(`Modules: ${Object.keys(modules)}`);
|
||||
|
||||
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)
|
||||
if ( has(modules, mod_id) ) {
|
||||
this._known_ids.add(mod_id);
|
||||
const original_module = modules[mod_id];
|
||||
modules[mod_id] = function(module, exports, require, ...args) {
|
||||
if ( ! t._require && typeof require === 'function' ) {
|
||||
|
@ -127,15 +150,15 @@ export default class WebMunch extends Module {
|
|||
webpackJsonpv4(data) {
|
||||
const chunk_ids = data[0],
|
||||
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.debug(`Modules: ${Object.keys(modules)}`);
|
||||
this.log.verbose(`Twitch Chunk Loaded: ${chunk_ids} (${names})`);
|
||||
this.log.verbose(`Modules: ${Object.keys(modules)}`);
|
||||
|
||||
if ( 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);
|
||||
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) {
|
||||
if ( typeof key === 'function' ) {
|
||||
predicate = key;
|
||||
|
@ -176,7 +247,7 @@ export default class WebMunch extends Module {
|
|||
return this._mod_cache[key];
|
||||
|
||||
const require = this._require;
|
||||
if ( ! require || ! require.c )
|
||||
if ( ! require )
|
||||
return null;
|
||||
|
||||
if ( ! predicate )
|
||||
|
@ -185,17 +256,147 @@ export default class WebMunch extends Module {
|
|||
if ( ! predicate )
|
||||
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) ) {
|
||||
checked++;
|
||||
const module = require.c[k],
|
||||
mod = module && module.exports;
|
||||
|
||||
if ( mod && predicate(mod) ) {
|
||||
if ( key )
|
||||
this._mod_cache[key] = mod;
|
||||
return 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 )
|
||||
this._mod_cache[key] = out;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -256,10 +457,38 @@ export default class WebMunch extends Module {
|
|||
try {
|
||||
modules = JSON.parse(data[1].replace(/(\d+):/g, '"$1":'))
|
||||
} 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 ) {
|
||||
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().`)
|
||||
} else
|
||||
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) ) {
|
||||
if ( prop && prop !== 'false' )
|
||||
el.setAttribute(key, prop);
|
||||
console.log('bool-attr', key, prop);
|
||||
|
||||
} else if ( lk.startsWith('aria-') || ATTRS.includes(lk) )
|
||||
el.setAttribute(key, prop);
|
||||
|
|
|
@ -124,6 +124,10 @@ export class EventEmitter {
|
|||
return list ? Array.from(list) : [];
|
||||
}
|
||||
|
||||
hasListeners(event) {
|
||||
return !! this.__listeners[event]
|
||||
}
|
||||
|
||||
emitUnsafe(event, ...args) {
|
||||
let list = this.__listeners[event];
|
||||
if ( ! list )
|
||||
|
@ -446,6 +450,7 @@ export class HierarchicalEventEmitter extends EventEmitter {
|
|||
waitFor(event) { return super.waitFor(this.abs_path(event)) }
|
||||
off(event, fn, ctx) { return super.off(this.abs_path(event), fn, ctx) }
|
||||
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) }
|
||||
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 {
|
||||
constructor(parent, name, level, raven) {
|
||||
this.root = parent ? parent.root : this;
|
||||
|
@ -21,7 +37,7 @@ export class Logger {
|
|||
|
||||
this.init = false;
|
||||
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.children = {};
|
||||
|
@ -34,6 +50,10 @@ export class Logger {
|
|||
return this.children[name];
|
||||
}
|
||||
|
||||
verbose(...args) {
|
||||
return this.invoke(Logger.VERBOSE, args);
|
||||
}
|
||||
|
||||
debug(...args) {
|
||||
return this.invoke(Logger.DEBUG, args);
|
||||
}
|
||||
|
@ -79,26 +99,28 @@ export class Logger {
|
|||
|
||||
const message = Array.prototype.slice.call(args);
|
||||
|
||||
if ( this.root.init )
|
||||
this.root.captured_init.push({
|
||||
time: Date.now(),
|
||||
category: this.name,
|
||||
if ( level !== Logger.VERBOSE ) {
|
||||
if ( this.root.init )
|
||||
this.root.captured_init.push({
|
||||
time: Date.now(),
|
||||
category: this.name,
|
||||
message: message.join(' '),
|
||||
level: RAVEN_LEVELS[level] || level
|
||||
});
|
||||
|
||||
this.crumb({
|
||||
message: message.join(' '),
|
||||
category: this.name,
|
||||
level: RAVEN_LEVELS[level] || level
|
||||
});
|
||||
|
||||
this.crumb({
|
||||
message: message.join(' '),
|
||||
category: this.name,
|
||||
level: RAVEN_LEVELS[level] || level
|
||||
});
|
||||
}
|
||||
|
||||
if ( this.name )
|
||||
message.unshift(`%c${this.root.label} [%c${this.name}%c]:%c`, 'color:#755000; font-weight:bold', '', 'color:#755000; font-weight:bold', '');
|
||||
else
|
||||
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);
|
||||
|
||||
else if ( level === Logger.INFO )
|
||||
|
@ -115,9 +137,7 @@ export class Logger {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Logger.DEFAULT_LEVEL = 2;
|
||||
|
||||
Logger.VERBOSE = 0;
|
||||
Logger.DEBUG = 1;
|
||||
Logger.INFO = 2;
|
||||
Logger.WARN = 4;
|
||||
|
@ -125,4 +145,6 @@ Logger.WARNING = 4;
|
|||
Logger.ERROR = 8;
|
||||
Logger.OFF = 99;
|
||||
|
||||
Logger.DEFAULT_LEVEL = Logger.INFO;
|
||||
|
||||
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.parent = parent;
|
||||
this.container = this.options.container || this.parent;
|
||||
this.cls = cls;
|
||||
|
||||
if ( this.check_modifiers )
|
||||
|
@ -134,6 +135,7 @@ export class Tooltip {
|
|||
|
||||
this.elements = null;
|
||||
this._onMouseOut = this._onMouseOver = null;
|
||||
this.container = null;
|
||||
this.parent = null;
|
||||
}
|
||||
|
||||
|
@ -367,8 +369,9 @@ export class Tooltip {
|
|||
|
||||
tip._update = () => {
|
||||
if ( tip.popper ) {
|
||||
tip.popper.destroy();
|
||||
tip.popper = new Popper(popper_target, el, pop_opts);
|
||||
tip.popper.update();
|
||||
/*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.
|
||||
tip.popper = new Popper(popper_target, el, pop_opts);
|
||||
this.parent.appendChild(el);
|
||||
this.container.appendChild(el);
|
||||
tip.visible = true;
|
||||
|
||||
if ( opts.onShow )
|
||||
|
|
|
@ -138,11 +138,9 @@ export default class TwitchData extends Module {
|
|||
return this._search;
|
||||
|
||||
const apollo = this.apollo.client,
|
||||
core = this.site.getCore(),
|
||||
|
||||
search_module = this.web_munch.getModule('algolia-search'),
|
||||
SearchClient = search_module && search_module.a;
|
||||
core = this.site.getCore();
|
||||
|
||||
const SearchClient = this.web_munch.getModule('algolia-search');
|
||||
if ( ! SearchClient || ! apollo || ! core )
|
||||
return null;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue