1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-03 00:18:31 +00:00
* Changed: Replace a few unnecessary menu components that only display markdown with a generic component.
* Changed: Initial work for letting users override display names and user colors in chat.
* Changed: The FFZ menu button no longer depends on add-ons being loaded to load itself.
* Fixed: Rendering of highlighted messages in Chat on Videos.
* Fixed: Featured channels menu not working when a bad channel is in the list.
This commit is contained in:
SirStendec 2020-01-11 17:13:56 -05:00
parent c91822cdc9
commit 3cbe4ee2fc
19 changed files with 284 additions and 228 deletions

View file

@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
"version": "4.17.12",
"version": "4.17.13",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"license": "Apache-2.0",
"scripts": {

View file

@ -13,6 +13,7 @@ import {timeout, has, glob_to_regex, escape_regex, split_chars} from 'utilities/
import Badges from './badges';
import Emotes from './emotes';
import Emoji from './emoji';
import Overrides from './overrides';
import Room from './room';
import User from './user';
@ -40,6 +41,7 @@ export default class Chat extends Module {
this.inject(Emotes);
this.inject(Emoji);
this.inject(Actions);
this.inject(Overrides);
this._link_info = {};

View file

@ -0,0 +1,133 @@
'use strict';
// ============================================================================
// Name and Color Overrides
// ============================================================================
import Module from 'utilities/module';
export default class Overrides extends Module {
constructor(...args) {
super(...args);
this.inject('settings');
this.color_cache = null;
this.name_cache = null;
}
onEnable() {
this.settings.provider.on('changed', this.onProviderChange, this);
}
onProviderChange(key) {
if ( key === 'overrides.colors' )
this.loadColors();
else if ( key === 'overrides.names' )
this.loadNames();
}
get colors() {
if ( ! this.color_cache )
this.loadColors();
return this.color_cache;
}
get names() {
if ( ! this.name_cache )
this.loadNames();
return this.name_cache;
}
loadColors() {
let old_keys,
loaded = true;
if ( ! this.color_cache ) {
loaded = false;
this.color_cache = {};
old_keys = new Set;
} else
old_keys = new Set(Object.keys(this.color_cache));
for(const [key, val] of Object.entries(this.settings.provider.get('overrides.colors', {}))) {
old_keys.delete(key);
if ( this.color_cache[key] !== val ) {
this.color_cache[key] = val;
if ( loaded )
this.emit(':changed', key, 'color', val);
}
}
for(const key of old_keys) {
this.color_cache[key] = undefined;
if ( loaded )
this.emit(':changed', key, 'color', undefined);
}
}
loadNames() {
let old_keys,
loaded = true;
if ( ! this.name_cache ) {
loaded = false;
this.name_cache = {};
old_keys = new Set;
} else
old_keys = new Set(Object.keys(this.name_cache));
for(const [key, val] of Object.entries(this.settings.provider.get('overrides.names', {}))) {
old_keys.delete(key);
if ( this.name_cache[key] !== val ) {
this.name_cache[key] = val;
if ( loaded )
this.emit(':changed', key, 'name', val);
}
}
for(const key of old_keys) {
this.name_cache[key] = undefined;
if ( loaded )
this.emit(':changed', key, 'name', undefined);
}
}
getColor(id) {
if ( this.colors[id] != null )
return this.colors[id];
return null;
}
getName(id) {
if ( this.names[id] != null )
return this.names[id];
return null;
}
setColor(id, color) {
if ( this.colors[id] !== color ) {
this.colors[id] = color;
this.settings.provider.set('overrides.colors', this.colors);
this.emit(':changed', id, 'color', color);
}
}
setName(id, name) {
if ( this.names[id] !== name ) {
this.names[id] = name;
this.settings.provider.set('overrides.names', this.names);
this.emit(':changed', id, 'name', name);
}
}
deleteColor(id) {
this.setColor(id, undefined);
}
deleteName(id) {
this.setName(id, undefined);
}
}

View file

@ -1,20 +0,0 @@
<template lang="html">
<div class="ffz--home tw-border-t tw-pd-y-1">
<markdown :source="t('home.faq', md)" />
</div>
</template>
<script>
import FAQ_MD from '../faq.md';
export default {
props: ['item', 'context'],
data() {
return {
md: FAQ_MD
}
}
}
</script>

View file

@ -1,20 +0,0 @@
<template lang="html">
<div class="ffz--home tw-border-t tw-pd-y-1">
<markdown :source="t('home.feedback', md)" />
</div>
</template>
<script>
import FEEDBACK_MD from '../feedback.md';
export default {
props: ['item', 'context'],
data() {
return {
md: FEEDBACK_MD
}
}
}
</script>

View file

@ -1,20 +0,0 @@
<template lang="html">
<div class="ffz--home tw-border-t tw-pd-y-1">
<markdown :source="md" />
</div>
</template>
<script>
import LEGAL_MD from '../legal.md';
export default {
props: ['item', 'context'],
data() {
return {
md: LEGAL_MD
}
}
}
</script>

View file

@ -0,0 +1,36 @@
<template lang="html">
<div class="ffz--home tw-border-t tw-pd-y-1">
<div v-if="loading" class="tw-align-center tw-pd-1">
<h1 class="tw-mg-5 ffz-i-zreknarf loading" />
</div>
<markdown v-else :source="t(`home.${key}`, md)" />
</div>
</template>
<script>
export default {
props: ['item', 'context'],
data() {
const key = this.item.key;
return {
key,
loading: true,
md: null
}
},
mounted() {
this.load();
},
methods: {
async load() {
this.md = (await import(/* webpackChunkName: 'menu-md' */ `../${this.key}.md`)).default;
this.loading = false;
}
}
}
</script>

View file

@ -64,12 +64,21 @@ export default class MainMenu extends Module {
this.settings.addUI('faq', {
path: 'Home > FAQ @{"profile_warning": false}',
component: 'faq-page'
component: 'md-page',
key: 'faq'
});
/*this.settings.addUI('privacy', {
path: 'Home > Privacy @{"profile_warning": false}',
component: 'md-page',
key: 'privacy',
force_seen: true
});*/
this.settings.addUI('feedback', {
path: 'Home > Feedback @{"profile_warning": false}',
component: 'feedback-page'
component: 'md-page',
key: 'feedback'
});
this.settings.addUI('feedback.log', {
@ -96,7 +105,8 @@ export default class MainMenu extends Module {
this.settings.addUI('legal', {
path: 'Home > Legal @{"sort": 1000}',
component: 'legal-page',
component: 'md-page',
key: 'legal',
force_seen: true
});

View file

@ -246,7 +246,7 @@ export default class ChatHook extends Module {
this.GiftBanner = this.fine.define(
'gift-banner',
n => n.getBannerText && n.handleCountdownEnd && n.getRemainingTime,
n => n.getBannerText && n.onGiftMoreClick,
Twilight.CHAT_ROUTES
);

View file

@ -33,6 +33,7 @@ export default class ChatLine extends Module {
this.inject(RichContent);
this.inject('chat.actions');
this.inject('chat.overrides');
this.ChatLine = this.fine.define(
'chat-line',
@ -46,18 +47,6 @@ export default class ChatLine extends Module {
Twilight.CHAT_ROUTES
);
/*this.ChatRoomLine = this.fine.define(
'chat-room-line',
n => n.renderMessageBody && n.props && ! n.onExtensionNameClick && has(n.props, 'hasModPermissions'),
Twilight.CHAT_ROUTES
);*/
/*this.ChatRoomContainer = this.fine.define(
'chat-room-container',
n => n.renderPlaceholders && n.sendRoomMessage && n.props && n.props.channel,
Twilight.CHAT_ROUTES
);*/
this.WhisperLine = this.fine.define(
'whisper-line',
n => n.props && n.props.message && n.props.reportOutgoingWhisperRendered
@ -65,6 +54,8 @@ export default class ChatLine extends Module {
}
async onEnable() {
this.on('chat.overrides:changed', id => this.updateLinesByUser(id), this);
this.chat.context.on('changed:chat.emoji.style', this.updateLines, this);
this.chat.context.on('changed:chat.bits.stack', this.updateLines, this);
this.chat.context.on('changed:chat.badges.style', this.updateLines, this);
@ -99,121 +90,6 @@ export default class ChatLine extends Module {
FFZRichContent = this.rich_content && this.rich_content.RichContent;
/*this.ChatRoomLine.ready(cls => {
const old_render = cls.prototype.render;
cls.prototype.render = function() { try {
this._ffz_no_scan = true;
const msg = t.chat.standardizeMessage(this.props.message),
is_action = msg.is_action,
user = msg.user,
color = t.parent.colors.process(user.color),
show_deleted = t.chat.context.get('chat.filtering.show-deleted');
let show, show_class;
if ( show_deleted ) {
show = true;
show_class = msg.deleted;
} else {
show = this.state && this.state.shouldShowDeletedBody || ! msg.deleted;
show_class = false;
}
const u = t.site.getUser(),
r = {id: null, login: null};
if ( u ) {
u.moderator = this.props.hasModPermissions;
}
// Find the parent element.
const parent = this._ffz_parent = this._ffz_parent || t.fine.searchParent(this,
n => (n.props && n.props.banStatusData && n.props.channelID) ||
(n.renderPlaceholders && n.sendRoomMessage && n.props && n.props.channel), 50);
if ( parent != null ) {
r.id = parent.props.channelID;
r.login = parent.props.channelLogin;
}
const tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u, r),
rich_content = FFZRichContent && t.chat.pluckRichContent(tokens, msg),
bg_css = msg.mentioned && msg.mention_color ? t.parent.inverse_colors.process(msg.mention_color) : null;
if ( ! this.ffz_user_click_handler )
this.ffz_user_click_handler = this.props.onUsernameClick;
const cls = `chat-line__message${show_class ? ' ffz--deleted-message' : ''}`,
out = (tokens.length || ! msg.ffz_type) ? [
this.props.showTimestamps && e('span', {
className: 'chat-line__timestamp'
}, t.chat.formatTime(msg.timestamp)),
this.renderModerationIcons(),
//t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e),
e('span', {
className: 'chat-line__message--badges'
}, t.chat.badges.render(msg, e)),
e('span', {
className: 'chat-line__username notranslate',
role: 'button',
style: { color },
onClick: this.ffz_user_click_handler
}, [
e('span', {
className: 'chat-author__display-name'
}, user.displayName),
user.isIntl && e('span', {
className: 'chat-author__intl-login'
}, ` (${user.login})`)
]),
e('span', null, is_action ? ' ' : ': '),
show ?
e('span', {
className: 'message',
style: is_action ? { color } : null
}, t.chat.renderTokens(tokens, e))
:
e('span', {
className: 'chat-line__message--deleted'
}, e('a', {
href: '',
onClick: this.showDeleted
}, t.i18n.t('chat.message-deleted', '<message deleted>'))),
show && rich_content && e(FFZRichContent, rich_content)
] : null;
if ( ! out )
return null;
return e('div', {
className: `${cls}${msg.mentioned ? ' ffz-mentioned' : ''}${bg_css ? ' ffz-custom-color' : ''}`,
style: {backgroundColor: bg_css},
'data-room-id': r.id,
'data-room': r.login,
'data-user-id': user.id,
'data-user': user.login && user.login.toLowerCase()
}, out);
} catch(err) {
t.log.capture(err, {
extra: {
props: this.props
}
});
return old_render.call(this);
} };
// Do this after a short delay to hopefully reduce the chance of React
// freaking out on us.
setTimeout(() => this.ChatRoomLine.forceUpdate());
});*/
this.WhisperLine.ready(cls => {
const old_render = cls.prototype.render;
@ -227,19 +103,22 @@ export default class ChatLine extends Module {
is_action = msg.is_action,
user = msg.user,
color = t.parent.colors.process(user.color),
raw_color = t.overrides.getColor(user.id) || user.color,
color = t.parent.colors.process(raw_color),
tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, null, null),
contents = t.chat.renderTokens(tokens, e);
contents = t.chat.renderTokens(tokens, e),
override_name = t.overrides.getName(user.id);
return e('div', {className: 'thread-message__message'},
e('div', {className: 'tw-pd-x-1 tw-pd-y-05'}, [
e('span', {
className: 'thread-message__message--user-name notranslate',
className: `thread-message__message--user-name notranslate${override_name ? ' ffz--name-override' : ''}`,
style: {
color
}
}, user.displayName),
}, override_name || user.displayName),
e('span', null, is_action ? ' ' : ': '),
e('span', {
className: 'message',
@ -290,7 +169,9 @@ export default class ChatLine extends Module {
is_action = msg.messageType === types.Action,
user = msg.user,
color = t.parent.colors.process(user.color);
raw_color = t.overrides.getColor(user.id) || user.color,
color = t.parent.colors.process(raw_color);
let mod_mode = this.props.deletedMessageDisplay;
let show, show_class, mod_action = null;
@ -422,6 +303,19 @@ other {# messages were deleted by a moderator.}
this.ffz_user_click_handler = this.openViewerCard || this.usernameClickHandler; //event => event.ctrlKey ? this.usernameClickHandler(event) : t.viewer_cards.openCard(r, user, event);
}
const user_block = [
e('span', {
className: 'chat-author__display-name'
}, user.displayName),
user.isIntl && e('span', {
className: 'chat-author__intl-login'
}, ` (${user.login})`)
];
const override_name = t.overrides.getName(user.id);
let cls = `chat-line__message${show_class ? ' ffz--deleted-message' : ''}`,
out = (tokens.length || ! msg.ffz_type) ? [
this.props.showTimestamps && e('span', {
@ -432,19 +326,19 @@ other {# messages were deleted by a moderator.}
className: 'chat-line__message--badges'
}, t.chat.badges.render(msg, e)),
e('span', {
className: 'chat-line__username notranslate',
className: `chat-line__username notranslate${override_name ? ' ffz--name-override tw-relative tw-tooltip-wrapper' : ''}`,
role: 'button',
style: { color },
onClick: this.ffz_user_click_handler,
onContextMenu: t.actions.handleUserContext
}, [
}, override_name ? [
e('span', {
className: 'chat-author__display-name'
}, user.displayName),
user.isIntl && e('span', {
className: 'chat-author__intl-login'
}, ` (${user.login})`)
]),
className: 'chat-author__display_name'
}, override_name),
e('div', {
className: 'tw-tooltip tw-tooltip--down tw-tooltip--align-center'
}, user_block)
] : user_block),
e('span', null, is_action ? ' ' : ': '),
show ?
e('span', {
@ -859,6 +753,23 @@ other {# messages were deleted by a moderator.}
}
updateLinesByUser(id, login) {
for(const inst of this.ChatLine.instances) {
const msg = inst.props.message,
user = msg?.user;
if ( user && (id && id == user.id) || (login && login == user.login) )
inst.forceUpdate();
}
for(const inst of this.WhisperLine.instances) {
const msg = inst.props.message?._ffz_message,
user = msg?.user;
if ( user && (id && id == user.id) || (login && login == user.login) )
inst.forceUpdate();
}
}
maybeUpdateLines() {
if ( this.chat.context.get('chat.rich.all-links') )
this.updateLines();
@ -881,14 +792,6 @@ other {# messages were deleted by a moderator.}
}
}
/*for(const inst of this.ChatRoomLine.instances) {
const msg = inst.props.message;
if ( msg ) {
msg.ffz_tokens = null;
msg.mentioned = msg.mention_color = null;
}
}*/
for(const inst of this.WhisperLine.instances) {
const msg = inst.props.message;
if ( msg && msg._ffz_message )
@ -897,7 +800,6 @@ other {# messages were deleted by a moderator.}
this.ChatLine.forceUpdate();
this.ExtensionLine.forceUpdate();
//this.ChatRoomLine.forceUpdate();
this.WhisperLine.forceUpdate();
this.emit('chat:updated-lines');

View file

@ -33,6 +33,7 @@
}
}
.video-chat__message-list-wrapper li:nth-child(2n+0) .ffz--points-highlight,
.ffz--points-highlight:nth-child(2n+0) {
background-color: var(--ffz-channel-color-30);
}

View file

@ -109,7 +109,7 @@ export default class FeaturedFollow extends Module {
follows = {};
for (const user of ap_data.data.users) {
if ( ! user.id )
if ( ! user || ! user.id )
continue;
follows[user.id] = {

View file

@ -18,7 +18,7 @@ export default class MenuButton extends SiteModule {
this.inject('i18n');
this.inject('settings');
this.inject('site.fine');
this.inject('addons');
//this.inject('addons');
this.should_enable = true;
this._pill_content = null;
@ -158,13 +158,15 @@ export default class MenuButton extends SiteModule {
if ( this.has_strings )
return this.i18n.formatNumber(this.i18n.new_strings + this.i18n.changed_strings);
if ( DEBUG && this.addons.has_dev )
const addons = this.resolve('addons');
if ( DEBUG && addons.has_dev )
return this.i18n.t('site.menu_button.dev', 'dev');
if ( DEBUG && ! this.addons.has_dev )
if ( DEBUG && ! addons.has_dev )
return this.i18n.t('site.menu_button.main-dev', 'm-dev');
if ( ! DEBUG && this.addons.has_dev )
if ( ! DEBUG && addons.has_dev )
return this.i18n.t('site.menu_button.addon-dev', 'a-dev');
return null;
@ -263,7 +265,8 @@ export default class MenuButton extends SiteModule {
if ( el )
el.remove();
const pill = this.formatPill(),
const addons = this.resolve('addons'),
pill = this.formatPill(),
extra_pill = this.formatExtraPill();
el = (<div
@ -315,7 +318,7 @@ export default class MenuButton extends SiteModule {
{DEBUG && (<div class="tw-mg-t-1">
{this.i18n.t('site.menu_button.main-dev-desc', 'You are running a developer build of FrankerFaceZ.')}
</div>)}
{this.addons.has_dev && (<div class="tw-mg-t-1">
{addons.has_dev && (<div class="tw-mg-t-1">
{this.i18n.t('site.menu_button.addon-dev-desc', 'You have loaded add-on data from a local development server.')}
</div>)}
</div>)}

View file

@ -283,13 +283,13 @@ export default class VideoChatHook extends Module {
return (<div
data-test-selector="message-layout"
class={`tw-align-items-start tw-flex tw-flex-nowrap tw-full-width tw-pd-l-05 tw-pd-y-05 vod-message${msg.mentioned ? ' ffz-mentioned' : ''}${bg_css ? ' ffz-custom-color' : ''}`}
class={`tw-align-items-start tw-flex tw-flex-nowrap tw-full-width tw-pd-l-05 tw-pd-y-05 vod-message${msg.highlight ? ' ffz-notice-line ffz--points-line ffz--points-highlight ffz-custom-color' : ''}${msg.mentioned ? ' ffz-mentioned' : ''}${bg_css ? ' ffz-custom-color' : ''}`}
style={{backgroundColor: bg_css}}
>
{this.props.hideTimestamp || (<div data-test-selector="message-timestamp" class="tw-align-right tw-flex tw-flex-shrink-0 vod-message__header">
<div class="tw-mg-r-05">
<div class="tw-inline-flex tw-relative tw-tooltip-wrapper">
<button class="tw-block tw-full-width tw-interactable tw-interactable--inverted" onClick={this.onTimestampClickHandler}>
<button class="tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive" onClick={this.onTimestampClickHandler}>
<div class="tw-pd-x-05">
<p class="tw-font-size-7">{print_duration(context.comment.contentOffset)}</p>
</div>
@ -377,7 +377,8 @@ export default class VideoChatHook extends Module {
messageParts: comment.message.tokens,
is_action: comment.message.isAction,
more_replies: comment.moreReplies,
timestamp: comment.createdAt
timestamp: comment.createdAt,
highlight: comment.message.userNoticeParams?.['msg-id'] === 'highlighted-message'
};
this.chat.detokenizeMessage(out);

View file

@ -16,6 +16,9 @@ export const State = {
}
const ANONYMOUS_ID = '683b45e4-f853-4c45-bf96-7d799cc93e34';
export default class SocketClient extends Module {
constructor(...args) {
super(...args);
@ -62,7 +65,7 @@ export default class SocketClient extends Module {
this.settings.on(':changed:socket.use-cluster', () => {
this._host = null;
if ( this.disconnected)
if ( this.disconnected )
this.connect();
else
this.reconnect();
@ -252,14 +255,18 @@ export default class SocketClient extends Module {
this._ping_time = performance.now();
this._send(
'hello',
[`ffz_${window.FrankerFaceZ.version_info}`, this.settings.provider.get('client-id')],
[`ffz_${window.FrankerFaceZ.version_info}`, ANONYMOUS_ID],
(success, data) => {
if ( ! success )
return this.log.warn('Error Saying Hello', data);
this._on_pong(false, success, data[1]);
this.settings.provider.set('client-id', data[0]);
this.log.info('Client ID:', data[0]);
/*if ( data[0] === ANONYMOUS_ID )
this.log.info('Client ID: <Anonymous>');
else {
this.settings.provider.set('client-id', data[0]);
this.log.info('Client ID:', data[0]);
}*/
});

View file

@ -8,6 +8,7 @@ query FFZ_FetchPoll($id: ID!) {
login
displayName
}
durationSeconds
endedAt
startedAt
totalVoters

View file

@ -5,7 +5,9 @@ query FFZ_FetchUser($id: ID, $login: String) {
displayName
profileImageURL(width: 50)
roles {
isAffiliate
isPartner
isStaff
}
}
}

View file

@ -0,0 +1,9 @@
query FFZ_UserSelf($id: ID, $login: String) {
user(id: $id, login: $login) {
id
self {
isEditor
isModerator
}
}
}

View file

@ -194,6 +194,15 @@ export default class TwitchData extends Module {
return get('data.user', data);
}
async getUserSelf(id, login) {
const data = await this.queryApollo(
await import(/* webpackChunkName: 'queries' */ './data/user-self.gql'),
{ id, login }
);
return get('data.user.self', data);
}
async getLastBroadcast(id, login) {
const data = await this.queryApollo(
await import(/* webpackChunkName: 'queries' */ './data/last-broadcast.gql'),