1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-09-01 10:50:56 +00:00

Improve support for interactive tooltips. Allow chat tokenizers to supply custom delays and interactive flags for their tooltips. Wrap text in <span> elements. Fix bug with stream uptime metadata. Fix bug with fine-router. Add method to EventEmitter that wraps emit in a try/catch. Add lilz to the socket server lineup.

This commit is contained in:
SirStendec 2017-11-14 04:11:43 -05:00
parent c0320dd3ab
commit a081247fdc
14 changed files with 168 additions and 40 deletions

View file

@ -1,3 +1,27 @@
<div class="list-header">4.0.0-beta1.3<span>@b86de82715b14711a01c</span> <time datetime="2017-11-13">(2017-11-13)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Freeze Chat Scrolling</li>
<li>Changed: Wrap chat line text in <code>&lt;span&gt;</code> elements for greater compatibility.</li>
</ul>
<div class="list-header">4.0.0-beta1.2<span>@5ddb43a7481741a4fdec</span> <time datetime="2017-11-13">(2017-11-13)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Stop running the FFZ script on certain Twitch subdomains.</li>
</ul>
<div class="list-header">4.0.0-beta1.2<span>@843d3308f09fa8d42276</span> <time datetime="2017-11-13">(2017-11-13)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Not catching errors when emitting events.</li>
<li>Fixed: lastResult is null when getting stream uptime.</li>
</ul>
<div class="list-header">4.0.0-beta1.2<span>@a2ef3cb4f248129b2a3b</span> <time datetime="2017-11-13">(2017-11-13)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: New socket server: <code>lilz.frankerfacez.com</code></li>
<li>Fixed: Avoid hiding page content when the FFZ menu is maximized to avoid causing scrolling issues.</li>
<li>Fixed: Stop modifying the games directory query until we have a better way to modify queries.</li>
</ul>
<div class="list-header">4.0.0-beta1 <time datetime="2017-11-12">(2017-11-12)</time></div> <div class="list-header">4.0.0-beta1 <time datetime="2017-11-12">(2017-11-12)</time></div>
<ul class="chat-menu-content menu-side-padding"> <ul class="chat-menu-content menu-side-padding">
<li>This is the initial release of the complete rewrite, FrankerFaceZ v4.</li> <li>This is the initial release of the complete rewrite, FrankerFaceZ v4.</li>

View file

@ -95,7 +95,7 @@ class FrankerFaceZ extends Module {
FrankerFaceZ.Logger = Logger; FrankerFaceZ.Logger = Logger;
const VER = FrankerFaceZ.version_info = { const VER = FrankerFaceZ.version_info = {
major: 4, minor: 0, revision: 0, extra: '-beta1.1', major: 4, minor: 0, revision: 0, extra: '-beta1.3',
build: __webpack_hash__, build: __webpack_hash__,
toString: () => toString: () =>
`${VER.major}.${VER.minor}.${VER.revision}${VER.extra || ''}${DEBUG ? '-dev' : ''}` `${VER.major}.${VER.minor}.${VER.revision}${VER.extra || ''}${DEBUG ? '-dev' : ''}`

View file

@ -108,6 +108,15 @@ export default class Chat extends Module {
} }
}); });
this.settings.add('tooltip.link-interaction', {
default: true,
ui: {
path: 'Chat > Tooltips >> Links',
title: 'Allow interaction with supported link tooltips.',
component: 'setting-check-box'
}
});
this.settings.add('tooltip.link-images', { this.settings.add('tooltip.link-images', {
default: true, default: true,
requires: ['tooltip.images'], requires: ['tooltip.images'],
@ -380,8 +389,13 @@ export default class Chat extends Module {
if ( tokenizer.priority == null ) if ( tokenizer.priority == null )
tokenizer.priority = 0; tokenizer.priority = 0;
if ( tokenizer.tooltip ) if ( tokenizer.tooltip ) {
this.tooltips.types[type] = tokenizer.tooltip.bind(this); const tt = tokenizer.tooltip;
const tk = this.tooltips.types[type] = tt.bind(this);
for(const i of ['interactive', 'delayShow', 'delayHide'])
tk[i] = typeof tt[i] === 'function' ? tt[i].bind(this) : tt[i];
}
this.__tokenizers.push(tokenizer); this.__tokenizers.push(tokenizer);
this.__tokenizers.sort((a, b) => { this.__tokenizers.sort((a, b) => {
@ -450,7 +464,9 @@ export default class Chat extends Module {
let res; let res;
if ( type === 'text' ) if ( type === 'text' )
res = token.text; res = e('span', {
'data-a-target': 'chat-message-text'
}, token.text);
else if ( tk ) else if ( tk )
res = tk.render.call(this, token, e); res = tk.render.call(this, token, e);

View file

@ -36,7 +36,10 @@ export default class Room extends EventEmitter {
this.users = []; this.users = [];
this.user_ids = []; this.user_ids = [];
this.load_data(); if ( this.login ) {
this.manager.socket.subscribe(`room.${login}`);
this.load_data();
}
} }
destroy() { destroy() {
@ -44,6 +47,8 @@ export default class Room extends EventEmitter {
this._destroy_timer = null; this._destroy_timer = null;
this.destroyed = true; this.destroyed = true;
this.manager.socket.unsubscribe(`room.${this.login}`);
this.style.destroy(); this.style.destroy();
if ( this.manager.room_ids[this.id] === this ) if ( this.manager.room_ids[this.id] === this )

View file

@ -140,6 +140,21 @@ export const Links = {
} }
} }
Links.tooltip.interactive = function(target, tip) {
if ( ! this.context.get('tooltip.rich-links') || ! this.context.get('tooltip.link-interaction') || target.dataset.isMail === 'true' )
return false;
const info = this.get_link_info(target.dataset.url, true);
return info && info.interactive;
};
Links.tooltip.delayHide = function(target, tip) {
if ( ! this.context.get('tooltip.rich-links') || ! this.context.get('tooltip.link-interaction') || target.dataset.isMail === 'true' )
return 0;
return 64;
};
// ============================================================================ // ============================================================================
// Rich Content // Rich Content

View file

@ -61,11 +61,16 @@ export default class Metadata extends Module {
setup() { setup() {
const socket = this.resolve('socket'), const socket = this.resolve('socket'),
query = this.resolve('site.apollo').getQuery('ChannelPage_ChannelInfoBar_User'), query = this.resolve('site.apollo').getQuery('ChannelPage_ChannelInfoBar_User'),
result = query.lastResult, result = query && query.lastResult,
created_at = get('data.user.stream.createdAt', result); created_at = result && get('data.user.stream.createdAt', result);
if ( ! query )
return {};
if ( created_at === undefined && ! query._ffz_refetched ) { if ( created_at === undefined && ! query._ffz_refetched ) {
query._ffz_refetched = true; query._ffz_refetched = true;
if ( result )
result.stale = true;
query.refetch(); query.refetch();
return {}; return {};
} }

View file

@ -5,6 +5,7 @@
// ============================================================================ // ============================================================================
import {createElement as e} from 'utilities/dom'; import {createElement as e} from 'utilities/dom';
import {has, maybe_call} from 'utilities/object';
import Tooltip from 'utilities/tooltip'; import Tooltip from 'utilities/tooltip';
import Module from 'utilities/module'; import Module from 'utilities/module';
@ -65,14 +66,47 @@ export default class TooltipProvider extends Module {
onEnable() { onEnable() {
this.tips = new Tooltip('[data-reactroot]', 'ffz-tooltip', { this.tips = new Tooltip('[data-reactroot]', 'ffz-tooltip', {
html: true, html: true,
delayHide: this.checkDelayHide.bind(this),
delayShow: this.checkDelayShow.bind(this),
content: this.process.bind(this), content: this.process.bind(this),
interactive: this.checkInteractive.bind(this),
popper: { popper: {
placement: 'top' placement: 'top'
} }
}); });
} }
process(target, tip) { //eslint-disable-line class-methods-use-this checkDelayShow(target, tip) {
const type = target.dataset.tooltipType,
handler = this.types[type];
if ( has(handler, 'delayShow') )
return maybe_call(handler.delayShow, null, target, tip);
return 0;
}
checkDelayHide(target, tip) {
const type = target.dataset.tooltipType,
handler = this.types[type];
if ( has(handler, 'delayHide') )
return maybe_call(handler.delayHide, null, target, tip);
return 0;
}
checkInteractive(target, tip) {
const type = target.dataset.tooltipType,
handler = this.types[type];
if ( has(handler, 'interactive') )
return maybe_call(handler.interactive, null, target, tip);
return false;
}
process(target, tip) {
const type = target.dataset.tooltipType, const type = target.dataset.tooltipType,
handler = this.types[type]; handler = this.types[type];

View file

@ -51,7 +51,7 @@ export default class Twilight extends BaseSite {
this.updateContext(); this.updateContext();
this.router.on(':route', (route, match) => { this.router.on(':route', (route, match) => {
this.log.info('Navigation', route && route.name, match[0]); this.log.info('Navigation', route && route.name, match && match[0]);
}); });
document.head.appendChild(e('link', { document.head.appendChild(e('link', {

View file

@ -52,14 +52,14 @@ export default class FineRouter extends Module {
this.log.debug('Matching Route', route, match); this.log.debug('Matching Route', route, match);
this.current = route; this.current = route;
this.match = match; this.match = match;
this.emit(':route', route, match); this.emitSafe(':route', route, match);
this.emit(`:route:${route.name}`, ...match); this.emitSafe(`:route:${route.name}`, ...match);
return; return;
} }
} }
this.current = this.match = null; this.current = this.match = null;
this.emit(':route', null, null); this.emitSafe(':route', null, null);
} }
route(name, path) { route(name, path) {

View file

@ -9,7 +9,8 @@ export const WS_CLUSTERS = {
Production: [ Production: [
['wss://catbag.frankerfacez.com/', 0.25], ['wss://catbag.frankerfacez.com/', 0.25],
['wss://andknuckles.frankerfacez.com/', 1], ['wss://andknuckles.frankerfacez.com/', 1],
['wss://tuturu.frankerfacez.com/', 1] ['wss://tuturu.frankerfacez.com/', 1],
['wss://lilz.frankerfacez.com/', 1]
], ],
Development: [ Development: [

View file

@ -160,6 +160,15 @@ export class EventEmitter {
} }
} }
emitSafe(event, ...args) {
try {
return [this.emit(event, ...args), undefined];
} catch(err) {
return [null, err];
}
}
emitAsync(event, ...args) { emitAsync(event, ...args) {
const list = this.__listeners[event]; const list = this.__listeners[event];
if ( ! list ) if ( ! list )

View file

@ -123,55 +123,60 @@ export class Tooltip {
_enter(target) { _enter(target) {
let tip = target[this._accessor], let tip = target[this._accessor];
delay = this.options.delayShow;
if ( ! tip ) if ( ! tip )
tip = target[this._accessor] = {target}; tip = target[this._accessor] = {target};
tip.state = true; tip.state = true;
if ( tip._show_timer ) {
clearTimeout(tip._show_timer);
tip._show_timer = null;
}
if ( tip.visible ) if ( tip.visible )
return; return;
const delay = maybe_call(this.options.delayShow, null, target, tip);
if ( delay === 0 ) if ( delay === 0 )
this.show(tip); this.show(tip);
else { else
if ( tip._show_timer )
clearTimeout(tip._show_timer);
tip._show_timer = setTimeout(() => { tip._show_timer = setTimeout(() => {
tip._show_timer = null; tip._show_timer = null;
if ( tip.state ) if ( tip.state )
this.show(tip); this.show(tip);
}, delay); }, delay);
}
} }
_exit(target) { _exit(target) {
const tip = target[this._accessor]; const tip = target[this._accessor];
if ( ! tip || ! tip.visible ) if ( ! tip )
return; return;
const delay = this.options.delayHide; tip.state = false;
tip.state = false; if ( tip._show_timer ) {
clearTimeout(tip._show_timer);
tip._show_timer = null;
}
if ( ! tip.visible )
return;
const delay = maybe_call(this.options.delayHide, null, target, tip);
if ( delay === 0 ) if ( delay === 0 )
this.hide(tip); this.hide(tip);
else { else
if ( tip._show_timer )
clearTimeout(tip._show_timer);
tip._show_timer = setTimeout(() => { tip._show_timer = setTimeout(() => {
tip._show_timer = null; tip._show_timer = null;
if ( ! tip.state ) if ( ! tip.state )
this.hide(tip); this.hide(tip);
}, delay); }, delay);
}
} }
@ -207,11 +212,20 @@ export class Tooltip {
arrow.setAttribute('x-arrow', true); arrow.setAttribute('x-arrow', true);
if ( maybe_call(opts.interactive, null, target, tip) ) { const interactive = maybe_call(opts.interactive, null, target, tip);
el.classList.add('interactive'); el.classList.toggle('interactive', interactive || false);
el.addEventListener('mouseover', () => this._enter(target));
el.addEventListener('mouseout', () => this._exit(target)); el.addEventListener('mouseover', () => {
} if ( ! document.contains(target) )
this.hide(tip);
else if ( maybe_call(opts.interactive, null, target, tip) )
this._enter(target);
else
this._exit(target);
});
el.addEventListener('mouseout', () => this._exit(target));
// Assign our content. If there's a Promise, we'll need // Assign our content. If there's a Promise, we'll need
// to do this weirdly. // to do this weirdly.
@ -219,7 +233,7 @@ export class Tooltip {
setter = use_html ? 'innerHTML' : 'textContent'; setter = use_html ? 'innerHTML' : 'textContent';
const pop_opts = Object.assign({ const pop_opts = Object.assign({
arrowElement: arrow, arrowElement: arrow
}, opts.popper); }, opts.popper);
tip._update = () => { tip._update = () => {

View file

@ -11,11 +11,9 @@ body {
font-size: 1.2rem; font-size: 1.2rem;
line-height: 1; line-height: 1;
text-align: left; text-align: left;
pointer-events: none;
white-space: pre-wrap; white-space: pre-wrap;
&.html { white-space: normal } &.html { white-space: normal }
&.interactive { pointer-events: all }
.loader { .loader {

View file

@ -127,15 +127,22 @@
.list-header { .list-header {
margin: 10px 20px 5px; margin: 10px 20px 5px;
padding: 15px 0 5px; padding: 15px 0 5px;
border-top: 1px solid #dad8de; border-bottom: 1px solid #dad8de;
font-size: 16px; font-size: 16px;
text-transform: uppercase; font-variant: small-caps;
.theme--dark & { .theme--dark & {
border-top-color: #2c2541; border-bottom-color: #2c2541;
}
span {
opacity: 0.5;
font-size: 10px;
font-variant: normal;
} }
time { time {
float: right;
opacity: 0.5; opacity: 0.5;
} }
} }