1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-26 04:28:31 +00:00
This release uses an entirely new emoji data source behind the scenes, and there may be bugs with emoji. Different names for certain emoji are also likely. Please report any issues.

* Added: Support for Blob and OpenMoji emoji sets.
* Changed: Update emoji to support emoji 13.
* Changed: Allow searching for emoji by any of their short codes in the emote menu.
* Removed: Support for the EmojiOne emoji set.
* Fixed: Hover styles for certain elements.
* Fixed: Sort emoji categories.
* Fixed: Typo in the description of a setting.
* Fixed: Do not include the same emoji multiple times in tab completion.
* Fixed: Issue viewing chat messages with replies enabled when not logged in. (Closes #889)
* Fixed: Twitch Style replies for highlighted messages, resubscription messages, etc.
* Fixed: Badge tool-tips not appearing correctly in Firefox.
This commit is contained in:
SirStendec 2020-08-15 15:45:50 -04:00
parent e7228d2795
commit fd2977f899
17 changed files with 282 additions and 136 deletions

8
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "frankerfacez",
"version": "4.20.17",
"version": "4.20.31",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -2851,9 +2851,9 @@
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.0.0.tgz",
"integrity": "sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w=="
},
"emojis-list": {
"version": "2.1.0",

View file

@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
"version": "4.20.31",
"version": "4.20.32",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"license": "Apache-2.0",
"scripts": {
@ -71,7 +71,7 @@
"crypto-js": "^3.1.9-1",
"dayjs": "^1.8.29",
"displacejs": "^1.4.1",
"emoji-regex": "^8.0.0",
"emoji-regex": "^9.0.0",
"file-saver": "^2.0.1",
"graphql": "^15.2.0",
"graphql-tag": "^2.10.3",

View file

@ -257,7 +257,7 @@ export default class Actions extends Module {
reason_elements.push(<li class="tw-full-width tw-relative">
<a
href="#"
class="tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive tw-pd-05"
class="tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive tw-pd-05"
onClick={click_fn(text)}
>
{text}
@ -274,7 +274,7 @@ export default class Actions extends Module {
reason_elements.push(<li class="tw-full-width tw-relative">
<a
href="#"
class="tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive tw-pd-05"
class="tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive tw-pd-05"
onClick={click_fn(rule)}
>
{rule}

View file

@ -12,14 +12,48 @@ import { getBuster } from 'utilities/time';
import splitter from 'emoji-regex/es2015/index';
export const SIZES = {
/*export const SIZES = {
apple: [64, 160],
emojione: [64],
facebook: [64, 96],
google: [64, 136],
messenger: [64, 128],
twitter: [64, 72]
}
}*/
export const HIDDEN_CATEGORIES = [
'component'
];
export const CATEGORIES = {
'smileys-emotion': 'Smileys & Emotions',
'people-body': 'People',
'component': 'Components',
'animals-nature': 'Animals & Nature',
'food-drink': 'Food & Drink',
'travel-places': 'Travel & Places',
'activities': 'Activities',
'objects': 'Objects',
'symbols': 'Symbols',
'flags': 'Flags'
};
export const CATEGORY_SORT = Object.keys(CATEGORIES);
export const SKIN_TONES = {
1: '1f3fb',
2: '1f3fc',
3: '1f3fd',
4: '1f3fe',
5: '1f3ff'
};
export const IMAGE_PATHS = {
google: 'noto',
twitter: 'twemoji',
open: 'openmoji',
blob: 'blob'
};
export function codepoint_to_emoji(cp) {
@ -49,13 +83,11 @@ export default class Emoji extends Module {
title: 'Emoji Style',
component: 'setting-select-box',
data: [
{value: 0, title: 'Native'},
{value: 'twitter', title: 'Twitter'},
{value: 'google', title: 'Google'},
//{value: 'apple', title: 'Apple'},
{value: 'emojione', title: 'EmojiOne'},
//{value: 'facebook', title: 'Facebook'},
//{value: 'messenger', title: 'Messenger'}
{value: 'twitter', title: 'Twitter (Twemoji)'},
{value: 'google', title: 'Google (Noto)'},
{value: 'blob', title: 'Blob'},
{value: 'open', title: 'OpenMoji'},
{value: 0, title: 'Native'}
]
}
});
@ -75,7 +107,7 @@ export default class Emoji extends Module {
async loadEmojiData(tries = 0) {
let data;
try {
data = await fetch(`${SERVER}/script/emoji/v2-.json?_${getBuster(60)}`).then(r =>
data = await fetch(`${SERVER}/script/emoji/v3.2.json?_${getBuster(60)}`).then(r =>
r.ok ? r.json() : null
);
@ -119,11 +151,43 @@ export default class Emoji extends Module {
if ( raw[7] ) {
const vars = emoji.variants = {};
for(const r of raw[7]) {
if ( Array.isArray(r[3]) || ! r[3] ) {
// The tone picker doesn't support multiple tones
// for a single emoji. Just make this variation a
// new emoji.
const em = Object.assign(hydrate_emoji(r), {
category: cats[raw[0]],
sort: raw[1],
names: r[5],
hidden: true
});
if ( ! Array.isArray(em.names) )
em.names = [em.names];
em.name = em.names[0].replace(/_/g, ' ');
out[em.code] = em;
chars.set(em.raw, [em.code, null]);
for(const name of em.names)
names[name] = em.code;
continue;
}
// We just have a normal tone. We need to look
// up the modifier and use it.
const tone = SKIN_TONES[r[3]];
if ( ! tone ) {
console.warn('Unknown tone:', r[3], r, emoji);
continue;
}
const vari = Object.assign(hydrate_emoji(r), {
key: r[3].toLowerCase()
key: tone
});
vars[vari.key] = vari;
vars[tone] = vari;
chars.set(vari.raw, [emoji.code, vari.key]);
}
}
@ -142,41 +206,53 @@ export default class Emoji extends Module {
if ( ! style )
style = this.parent.context.get('chat.emoji.style');
if ( ! has(SIZES, style) )
if ( ! has(IMAGE_PATHS, style) )
style = 'twitter';
return `${SERVER}/static/emoji/img-${style}-${SIZES[style][0]}/${image}`;
return `${SERVER}/static/emoji/images/${IMAGE_PATHS[style]}/${image}`;
/*if ( ! has(SIZES, style) )
style = 'twitter';
return `${SERVER}/static/emoji/img-${style}-${SIZES[style][0]}/${image}`;*/
}
getFullImageSet(image, style) {
if ( ! style )
style = this.parent.context.get('chat.emoji.style');
if ( ! has(SIZES, style) )
if ( ! has(IMAGE_PATHS, style) )
style = 'twitter';
return `${SERVER}/static/emoji/images/${IMAGE_PATHS[style]}/${image} 72w`;
/*if ( ! has(SIZES, style) )
style = 'twitter';
return SIZES[style].map(w =>
`${SERVER}/static/emoji/img-${style}-${w}/${image} ${w}w`
).join(', ');
).join(', ');*/
}
}
function hydrate_emoji(data) {
let code = data[0];
if ( data[4] === 0 )
code = `${code}-fe0f`;
return {
code: data[0],
code,
image: `${data[0]}.png`,
raw: data[0].split('-').map(codepoint_to_emoji).join(''),
raw: code.split('-').map(codepoint_to_emoji).join(''),
sheet_x: data[1][0],
sheet_y: data[1][1],
has: {
apple: !!(0b100000 & data[2]),
google: !!(0b010000 & data[2]),
twitter: !!(0b001000 & data[2]),
emojione: !!(0b000100 & data[2]),
facebook: !!(0b000010 & data[2]),
messenger: !!(0b000001 & data[2])
google: !!(0b1000 & data[2]),
blob: !!(0b0100 & data[2]) || !!(0b1000 & data[2]), // Blob falls back to Noto
twitter: !!(0b0010 & data[2]),
open: !!(0b0001 & data[2])
}
};
}

View file

@ -8,6 +8,7 @@ import {sanitize, createElement} from 'utilities/dom';
import {has, split_chars} from 'utilities/object';
import {TWITCH_EMOTE_BASE, EmoteTypes, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants';
import {CATEGORIES} from './emoji';
const EMOTE_CLASS = 'chat-image chat-line__message--emote',
@ -1227,7 +1228,7 @@ export const AddonEmotes = {
plain_name = true;
name = `:${emoji.names[0]}:${vcode ? `:${vcode.names[0]}:` : ''}`;
const category = emoji.category ? this.i18n.t(`emoji.category.${emoji.category.toSnakeCase()}`, emoji.category) : null;
const category = emoji.category ? this.i18n.t(`emoji.category.${emoji.category.toSnakeCase()}`, CATEGORIES[emoji.category] || emoji.category) : null;
source = this.i18n.t('tooltip.emoji', 'Emoji - {category}', {category});
} else

View file

@ -245,7 +245,7 @@
</div>
<div v-else class="tw-pd-y-1">
<button
class="tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive tw-full-width"
class="tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive tw-full-width"
@click="add_pasting = true"
>
<div class="tw-flex tw-align-items-center tw-pd-y-05 tw-pd-x-1">
@ -264,7 +264,7 @@
v-else
:key="idx"
:disabled="preset.disabled"
class="tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive tw-full-width"
class="tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive tw-full-width"
@click="add(preset.value)"
>
<div class="tw-flex tw-align-items-center tw-pd-y-05 tw-pd-x-1">

View file

@ -6,6 +6,7 @@
import {has, get, once, maybe_call, set_equals} from 'utilities/object';
import {TWITCH_GLOBAL_SETS, EmoteTypes, TWITCH_POINTS_SETS, TWITCH_PRIME_SETS, WEBKIT_CSS as WEBKIT, IS_OSX, KNOWN_CODES, TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS, KEYS} from 'utilities/constants';
import {HIDDEN_CATEGORIES, CATEGORIES, CATEGORY_SORT, IMAGE_PATHS} from 'src/modules/chat/emoji';
import {ClickOutside} from 'utilities/dom';
import Twilight from 'site';
@ -25,7 +26,12 @@ const TONE_EMOJI = [
'ok_hand',
'+1',
'clap',
'fist'
'fist',
'pinched_fingers',
'wave',
'pinch',
'victory',
'love_you_gesture'
];
function maybe_date(val) {
@ -292,9 +298,9 @@ export default class EmoteMenu extends Module {
this.updateEmojiVariables();
this.css_tweaks.setVariable('emoji-menu--sheet', `//cdn.frankerfacez.com/static/emoji/sheet_twitter_32.png`);
this.css_tweaks.setVariable('emoji-menu--count', 52);
this.css_tweaks.setVariable('emoji-menu--size', 20);
this.css_tweaks.setVariable('emoji-menu--sheet', `//cdn.frankerfacez.com/static/emoji/images/sheet-twemoji-36.png`);
this.css_tweaks.setVariable('emoji-menu--count', 58);
this.css_tweaks.setVariable('emoji-menu--size', 36);
const t = this,
React = await this.web_munch.findModule('react'),
@ -337,11 +343,12 @@ export default class EmoteMenu extends Module {
}
updateEmojiVariables() {
const style = this.chat.context.get('chat.emoji.style') || 'twitter',
base = `//cdn.frankerfacez.com/static/emoji/sheet_${style}_`;
const emoji_size = this.emoji_size = 20,
sheet_count = this.emoji_sheet_count = 52,
const style = this.chat.context.get('chat.emoji.style') || 'twitter',
base = `//cdn.frankerfacez.com/static/emoji/images/sheet-${IMAGE_PATHS[style] || 'twemoji'}-`;
const emoji_size = this.emoji_size = 36,
sheet_count = this.emoji_sheet_count = 58,
sheet_size = this.emoji_sheet_size = sheet_count * (emoji_size + 2),
sheet_pct = this.emoji_sheet_pct = 100 * sheet_size / emoji_size;
@ -349,11 +356,11 @@ export default class EmoteMenu extends Module {
this.css_tweaks.set('emoji-menu', `.ffz--emoji-tone-picker__emoji,.emote-picker__emoji .emote-picker__emote-figure {
background-size: ${sheet_pct}% ${sheet_pct}%;
background-image: url("${base}20.png");
background-image: url("${base}36.png");
background-image: ${WEBKIT}image-set(
url("${base}20.png") 1x,
url("${base}32.png") 1.6x,
url("${base}64.png") 3.2x
url("${base}18.png") 0.5x,
url("${base}36.png") 1x,
url("${base}72.png") 2x
);
}`);
}
@ -422,7 +429,7 @@ export default class EmoteMenu extends Module {
return (<button
key={data.code}
data-tone={tone}
class="tw-interactive tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive tw-pd-y-05 tw-pd-x-2"
class="tw-interactive tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive tw-pd-y-05 tw-pd-x-2"
onClick={this.clickTone}
>
{this.renderEmoji(data)}
@ -1282,17 +1289,31 @@ export default class EmoteMenu extends Module {
const emote_name = emote.search || emote.name,
emote_lower = emote_name.toLowerCase(),
term_lower = filter.toLowerCase();
term_lower = filter.toLowerCase(),
has_colon = filter.startsWith(':'),
term_trail = term_lower.slice(1);
if ( ! filter.startsWith(':') )
if ( Array.isArray(emote.extra) ) {
let i = emote.extra.length;
while(i--) {
if ( ! has_colon && emote.extra[i].includes(term_lower) )
return true;
else if ( has_colon && emote.extra[i].startsWith(term_trail) )
return true;
}
}
if ( ! has_colon )
return emote_lower.includes(term_lower);
if ( emote_lower.startsWith(term_lower.slice(1)) )
if ( emote_lower.startsWith(term_trail) )
return true;
const idx = emote_name.indexOf(filter.charAt(1).toUpperCase());
if ( idx !== -1 )
return emote_lower.slice(idx+1).startsWith(term_lower.slice(2));
return false;
}
@ -1301,15 +1322,18 @@ export default class EmoteMenu extends Module {
sets = state.emoji_sets = [],
emoji_favorites = t.emotes.getFavorites('emoji'),
style = t.chat.context.get('chat.emoji.style') || 'twitter',
favorites = state.favorites = (state.favorites || []).filter(x => ! x.emoji),
tone = state.tone = state.tone || null,
tone_choices = state.tone_emoji = [],
categories = {};
let style = t.chat.context.get('chat.emoji.style') || 'twitter';
if ( ! IMAGE_PATHS[style] )
style = 'twitter';
for(const emoji of Object.values(t.emoji.emoji)) {
if ( ! emoji || ! emoji.has[style] || emoji.category === 'Skin Tones' )
if ( ! emoji || ! emoji.has[style] || HIDDEN_CATEGORIES.includes(emoji.category) )
continue;
if ( emoji.variants ) {
@ -1331,10 +1355,11 @@ export default class EmoteMenu extends Module {
sets.push({
key: `emoji-${emoji.category}`,
sort_key: CATEGORY_SORT.indexOf(emoji.category),
emoji: true,
image: t.emoji.getFullImage(source.image),
i18n: `emoji.category.${emoji.category.toSnakeCase()}`,
title: emoji.category,
title: CATEGORIES[emoji.category] || emoji.category,
src: 'emoji',
source: 'Emoji',
source_i18n: 'emote-menu.emoji',
@ -1344,12 +1369,15 @@ export default class EmoteMenu extends Module {
const em = {
provider: 'emoji',
id: emoji.sort,
emoji: true,
code: emoji.code,
name: source.raw,
variant: has_tone && tone,
hidden: emoji.hidden,
search: emoji.names[0],
extra: emoji.names.length > 1 ? emoji.names.map(x => x.toLowerCase()) : null,
height: 18,
width: 18,
@ -1385,6 +1413,7 @@ export default class EmoteMenu extends Module {
// We use this sorter because we don't want things grouped by sets.
favorites.sort(this.getSorter());
sets.sort(sort_sets);
return state;
}
@ -2138,7 +2167,7 @@ export default class EmoteMenu extends Module {
<div class="emote-picker__tab-nav-container tw-flex tw-border-t tw-c-background-alt">
{! visibility && <div class={`emote-picker-tab-item${tab === 'fav' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
<button
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive${tab === 'fav' ? ' tw-interactable--selected' : ''}`}
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive${tab === 'fav' ? ' tw-interactable--selected' : ''}`}
id="emote-picker__fav"
data-tab="fav"
data-tooltip-type="html"
@ -2152,7 +2181,7 @@ export default class EmoteMenu extends Module {
</div>}
{this.state.has_channel_tab && <div class={`emote-picker-tab-item${tab === 'channel' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
<button
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive${tab === 'channel' ? ' tw-interactable--selected' : ''}`}
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive${tab === 'channel' ? ' tw-interactable--selected' : ''}`}
id="emote-picker__channel"
data-tab="channel"
data-tooltip-type="html"
@ -2166,7 +2195,7 @@ export default class EmoteMenu extends Module {
</div>}
<div class={`emote-picker-tab-item${tab === 'all' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
<button
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive${tab === 'all' ? ' tw-interactable--selected' : ''}`}
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive${tab === 'all' ? ' tw-interactable--selected' : ''}`}
id="emote-picker__all"
data-tab="all"
data-tooltip-type="html"
@ -2180,7 +2209,7 @@ export default class EmoteMenu extends Module {
</div>
{! visibility && this.state.has_emoji_tab && <div class={`emote-picker-tab-item${tab === 'emoji' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
<button
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive${tab === 'emoji' ? ' tw-interactable--selected' : ''}`}
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive${tab === 'emoji' ? ' tw-interactable--selected' : ''}`}
id="emote-picker__emoji"
data-tab="emoji"
data-tooltip-type="html"
@ -2195,7 +2224,7 @@ export default class EmoteMenu extends Module {
<div class="tw-flex-grow-1" />
<div class="emote-picker-tab-item tw-relative">
<button
class="ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive"
class="ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive"
data-tooltip-type="html"
data-title={t.i18n.t('emote-menu.settings', 'Open Settings')}
onClick={this.clickSettings}

View file

@ -255,7 +255,7 @@ export default class ChatHook extends Module {
ui: {
path: 'Chat > Appearance >> Replies',
title: 'Style',
description: `Twitch's default style makes adds a floating button to the right and displays a notice above messages that are replies. FrankerFaceZ uses an In-Line Chat Action (that can be removed in Chat > Actions > In-Line) and uses an in-line mention to denote replies.`,
description: `Twitch's default style adds a floating button to the right and displays a notice above messages that are replies. FrankerFaceZ uses an In-Line Chat Action (that can be removed in Chat > Actions > In-Line) and uses an in-line mention to denote replies.`,
component: 'setting-select-box',
data: [
{value: 0, title: 'Disabled'},
@ -1366,7 +1366,7 @@ export default class ChatHook extends Module {
if ( event.defaultPrevented || m.ffz_removed )
return;
} else if ( msg.type === types.ModerationAction && inst.markUserEventDeleted && inst.unsetModeratedUser ) {
} else if ( msg.type === types.ModerationAction && false && inst.markUserEventDeleted && inst.unsetModeratedUser ) {
if ( !((! msg.level || ! msg.level.length) && msg.targetUserLogin && msg.targetUserLogin === inst.props.currentUserLogin) ) {
//t.log.info('Moderation Action', msg);
if ( ! inst.props.isCurrentUserModerator )
@ -1389,13 +1389,15 @@ export default class ChatHook extends Module {
if ( len !== inst.buffer.length && ! inst.props.isBackground )
inst.notifySubscribers();
inst.ffzModerateBuffer([inst.delayedMessageBuffer], msg);
inst.moderateBuffers([
inst.delayedMessageBuffer.map(e => e.event)
], user, msg);
} else
inst.ffzModerateBuffer([inst.buffer, inst.delayedMessageBuffer], msg);
inst.moderatedUsers.add(user);
setTimeout(inst.unsetModeratedUser(user), 1e3);
inst.moderateBuffers([
inst.buffer,
inst.delayedMessageBuffer.map(e => e.event)
], user, msg);
inst.delayedMessageBuffer.push({
event: msg,
@ -1407,7 +1409,7 @@ export default class ChatHook extends Module {
}
}
} else if ( msg.type === types.Moderation && inst.markUserEventDeleted && inst.unsetModeratedUser ) {
} else if ( msg.type === types.Moderation && false && inst.unsetModeratedUser ) {
//t.log.info('Moderation', msg);
if ( inst.props.isCurrentUserModerator )
return;
@ -1441,13 +1443,15 @@ export default class ChatHook extends Module {
if ( len !== inst.buffer.length && ! inst.props.isBackground )
inst.notifySubscribers();
inst.ffzModerateBuffer([inst.delayedMessageBuffer], msg);
inst.moderateBuffers([
inst.delayedMessageBuffer.map(e => e.event)
], user, msg);
} else
inst.ffzModerateBuffer([inst.buffer, inst.delayedMessageBuffer], msg);
inst.moderatedUsers.add(user);
setTimeout(inst.unsetModeratedUser(user), 1e3);
inst.moderateBuffers([
inst.buffer,
inst.delayedMessageBuffer.map(e => e.event)
], user, msg);
inst.delayedMessageBuffer.push({
event: msg,
@ -1474,8 +1478,9 @@ export default class ChatHook extends Module {
return old_handle.call(inst, msg);
}
inst.ffzModerateBuffer = function(buffers, event) {
/*inst.ffzModerateBuffer = function(buffers, event) {
const mod_types = t.mod_types || {},
ctypes = t.chat_types || {},
mod_type = event.moderationActionType,
user_login = event.targetUserLogin || event.userLogin,
mod_login = event.createdByLogin,
@ -1488,22 +1493,35 @@ export default class ChatHook extends Module {
if ( m.event )
m = m.event;
/*if ( m.message && m.type in [ctypes.ChannelPointsReward, ctypes.Resubscription, ctypes.Ritual] )
m = m.message;/
if ( m.reply ) {
if ( target_id ? target_id === m.reply.parentMsgId : (user_login && user_login === m.reply.parentUserLogin) )
m.reply = {
...m.reply,
parentDeleted: true
}
}
if ( ! user_login || ! m.user || user_login !== m.user.userLogin || ! m.messageParts )
return;
if ( ! m || m.deleted )
return;
if ( target_id && m.id !== target_id )
return;
const msg = inst.markUserEventDeleted(m, user_login);
if ( ! msg )
return;
m.deleted = true;
m.banned = mod_type === mod_types.Ban;
last_msg = msg;
last_msg = m;
deleted_count++;
msg.modLogin = mod_login;
msg.modActionType = mod_type;
msg.duration = event.duration;
if ( is_delete )
return true;
m.modLogin = mod_login;
m.modActionType = mod_type;
m.duration = event.duration;
};
for(const buffer of buffers)
@ -1514,7 +1532,7 @@ export default class ChatHook extends Module {
if ( last_msg )
last_msg.deletedCount = deleted_count;
}
}*/
inst.setPaused = function(paused) {
if ( inst.paused === paused )

View file

@ -706,13 +706,16 @@ export default class Input extends Module {
if ( has_colon )
search = search.slice(0,-1);
const included = new Set;
for(const name in this.emoji.names)
if ( has_colon ? name === search : name.startsWith(search) ) {
const emoji = this.emoji.emoji[this.emoji.names[name]],
toned = emoji.variants && emoji.variants[tone],
source = toned || emoji;
if ( emoji && (style === 0 || source.has[style]) ) {
if ( emoji && (style === 0 || source.has[style]) && ! included.has(source.raw) ) {
included.add(source.raw);
const favorite = favorites.includes(emoji.code);
results.push({
current: input,

View file

@ -422,7 +422,7 @@ other {# messages were deleted by a moderator.}
const has_replies = this.chatRepliesTreatment ? this.chatRepliesTreatment !== 'control' : false,
can_replies = has_replies && msg.message && ! msg.deleted && ! this.props.disableReplyClick,
can_reply = can_replies && u.login !== msg.user?.login && ! msg.reply,
can_reply = can_replies && u && u.login !== msg.user?.login && ! msg.reply,
twitch_clickable = reply_mode === 1 && can_replies && (!!msg.reply || can_reply);
if ( u ) {
@ -511,9 +511,9 @@ other {# messages were deleted by a moderator.}
this.props.showTimestamps && e('span', {
className: 'chat-line__timestamp'
}, t.chat.formatTime(msg.timestamp)),
twitch_clickable ?
e('div', {className: 'chat-line__username-container tw-inline-block'}, user_bits)
: user_bits,
//twitch_clickable ?
// e('div', {className: 'chat-line__username-container tw-inline-block'}, user_bits) :
user_bits,
e('span', {'aria-hidden': true}, is_action ? ' ' : ': '),
show && has_replies && reply_tokens ?
t.chat.renderTokens(reply_tokens, e)
@ -549,41 +549,6 @@ other {# messages were deleted by a moderator.}
}, JSON.stringify([tokens, msg.emotes], null, 2))*/
] : null;
if ( twitch_clickable ) {
let icon, title;
if ( can_reply ) {
icon = e('figure', {className: 'ffz-i-reply'});
title = t.i18n.t('chat.actions.reply', 'Reply to Message');
} else {
icon = e('figure', {className: 'ffz-i-threads'});
title = t.i18n.t('chat.actions.reply.thread', 'Open Thread');
}
out = [
e('div', {
className: 'chat-line__message-highlight tw-absolute tw-border-radius-medium tw-top-0 tw-bottom-0 tw-right-0 tw-left-0',
'data-test-selector': 'chat-message-highlight'
}),
e('div', {
className: 'chat-line__message-container tw-relative'
}, [
this.renderReplyLine(),
out
]),
e('div', {
className: 'chat-line__reply-icon tw-absolute tw-border-radius-medium tw-c-background-base tw-elevation-1'
}, e('button', {
className: 'tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-button-icon tw-core-button tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ffz-tooltip ffz-tooltip--no-mouse',
'data-test-selector': 'chat-reply-button',
'aria-label': title,
'data-title': title,
onClick: this.ffz_open_reply
}, e('span', {
className: 'tw-button-icon__icon'
}, icon)))
];
}
if ( msg.ffz_type === 'sub_mystery' ) {
const mystery = msg.mystery;
if ( mystery )
@ -643,7 +608,7 @@ other {# messages were deleted by a moderator.}
}, the_list);
}
cls = `ffz-notice-line user-notice-line tw-pd-y-05 ffz--subscribe-line${show_class ? ' ffz--deleted-message' : ''}`;
cls = `ffz-notice-line user-notice-line tw-pd-y-05 ffz--subscribe-line${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`;
out = [
e('div', {
className: 'tw-flex tw-c-text-alt-2',
@ -716,7 +681,7 @@ other {# messages were deleted by a moderator.}
count: msg.sub_total
}));
cls = `ffz-notice-line user-notice-line tw-pd-y-05 tw-pd-r-2 ffz--subscribe-line${show_class ? ' ffz--deleted-message' : ''}`;
cls = `ffz-notice-line user-notice-line tw-pd-y-05 tw-pd-r-2 ffz--subscribe-line${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`;
out = [
e('div', {className: 'tw-flex tw-c-text-alt-2'}, [
t.chat.context.get('chat.subs.compact') ? null :
@ -778,7 +743,7 @@ other {# messages were deleted by a moderator.}
));
}
cls = `ffz-notice-line user-notice-line tw-pd-y-05 tw-pd-r-2 ffz--subscribe-line${show_class ? ' ffz--deleted-message' : ''}`;
cls = `ffz-notice-line user-notice-line tw-pd-y-05 tw-pd-r-2 ffz--subscribe-line${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`;
out = [
e('div', {className: 'tw-flex tw-c-text-alt-2'}, [
t.chat.context.get('chat.subs.compact') ? null :
@ -816,7 +781,7 @@ other {# messages were deleted by a moderator.}
]);
if ( system_msg ) {
cls = `ffz-notice-line user-notice-line tw-pd-y-05 tw-pd-r-2 ffz--ritual-line${show_class ? ' ffz--deleted-message' : ''}`;
cls = `ffz-notice-line user-notice-line tw-pd-y-05 tw-pd-r-2 ffz--ritual-line${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`;
out = [
system_msg,
out && e('div', {
@ -839,7 +804,7 @@ other {# messages were deleted by a moderator.}
const can_highlight = t.chat.context.get('chat.points.allow-highlight'),
highlight = can_highlight && isHighlightedReward(msg.ffz_reward);
cls = `ffz-notice-line ffz--points-line tw-pd-l-1 tw-pd-y-05 tw-pd-r-2${highlight ? ' ffz--points-highlight' : ''}${show_class ? ' ffz--deleted-message' : ''}`;
cls = `ffz-notice-line ffz--points-line tw-pd-l-1 tw-pd-y-05 tw-pd-r-2${highlight ? ' ffz--points-highlight' : ''}${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`;
out = [
e('div', {className: 'tw-c-text-alt-2'}, [
out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e),
@ -869,6 +834,41 @@ other {# messages were deleted by a moderator.}
if ( ! out )
return null;
if ( twitch_clickable ) {
let icon, title;
if ( can_reply ) {
icon = e('figure', {className: 'ffz-i-reply'});
title = t.i18n.t('chat.actions.reply', 'Reply to Message');
} else {
icon = e('figure', {className: 'ffz-i-threads'});
title = t.i18n.t('chat.actions.reply.thread', 'Open Thread');
}
out = [
e('div', {
className: 'chat-line__message-highlight tw-absolute tw-border-radius-medium tw-top-0 tw-bottom-0 tw-right-0 tw-left-0',
'data-test-selector': 'chat-message-highlight'
}),
e('div', {
className: 'chat-line__message-container'
}, [
this.renderReplyLine(),
out
]),
e('div', {
className: 'chat-line__reply-icon tw-absolute tw-border-radius-medium tw-c-background-base tw-elevation-1'
}, e('button', {
className: 'tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-button-icon tw-core-button tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ffz-tooltip ffz-tooltip--no-mouse',
'data-test-selector': 'chat-reply-button',
'aria-label': title,
'data-title': title,
onClick: this.ffz_open_reply
}, e('span', {
className: 'tw-button-icon__icon'
}, icon)))
];
}
return e('div', {
className: `${cls}${msg.mentioned ? ' ffz-mentioned' : ''}${bg_css ? ' ffz-custom-color' : ''}`,
style: {backgroundColor: bg_css},

View file

@ -26,7 +26,7 @@
:data-id="host.id"
class="tw-border-t ffz--host-user"
>
<div class="tw-interactable tw-interactable--inverted">
<div class="tw-interactable tw-interactable--default">
<div class="tw-align-items-center tw-flex tw-flex-row tw-flex-nowrap tw-mg-x-1">
<figure class="ffz-i-ellipsis-vert handle" />
<div class="ffz-channel-avatar">

View file

@ -21,8 +21,8 @@ const COLORS = [
const ACCENT_COLORS = {
dark: {'c':{'accent': 9,'background-accent':8,'background-accent-alt':7,'background-accent-alt-2':6,'background-button':7,'background-button-active':7,'background-button-focus':8,'background-button-hover':8,'background-button-primary-active':7,'background-button-primary-default':9,'background-button-primary-hover':8,'background-graph':2,'background-graph-fill':8,'background-input-checkbox-checked':9,'background-input-checked':8,'background-interactable-active':9,'background-interactable-hover':8,'background-progress-countdown-status':9,'background-progress-status':9,'background-range-fill':9,'background-subscriber-stream-tag-active':4,'background-subscriber-stream-tag-default':4,'background-subscriber-stream-tag-hover':3,'background-toggle-checked':9,/*'background-tooltip':1,*/'background-top-nav':6,'border-brand':9,'border-button':7,'border-button-active':8,'border-button-focus':9,'border-button-hover':8,'border-input-checkbox-checked':9,'border-input-checkbox-focus':9,'border-input-focus':9,'border-interactable-selected':10,'border-subscriber-stream-tag':5,'border-tab-active':11,'border-tab-focus':11,'border-tab-hover':11,'border-toggle-focus':7,'border-toggle-hover':7,'border-whisper-incoming':10,'fill-brand':9,'text-button-text':10,'text-button-text-focus':'o1','text-button-text-hover':'o1','text-link':10,'text-link-active':10,'text-link-focus':10,'text-link-hover':10,'text-link-visited':10,'text-overlay-link-active':13,'text-overlay-link-focus':13,'text-overlay-link-hover':13,'text-tab-active':11,'background-chat':1,'background-chat-alt':3,'background-chat-header':2,'background-modal':3,'text-button-text-active':'o2'/*,'text-tooltip':1*/},'s':{'button-active':[8,'0 0 6px 0',''],'button-focus':[8,'0 0 6px 0',''],'input-focus':[8,'0 0 10px -2px',''],'interactable-focus':[8,'0 0 6px 0',''],'tab-focus':[11,'0 4px 6px -4px',''],'input':[5,'inset 0 0 0 1px','']}},
light: {'c':{'accent': 9,'background-accent':8,'background-accent-alt':7,'background-accent-alt-2':6,'background-button':7,'background-button-active':7,'background-button-focus':8,'background-button-hover':8,'background-button-primary-active':7,'background-button-primary-default':9,'background-button-primary-hover':8,'background-graph':15,'background-graph-fill':9,'background-input-checkbox-checked':9,'background-input-checked':8,'background-interactable-active':9,'background-interactable-hover':8,'background-progress-countdown-status':8,'background-progress-status':8,'background-range-fill':9,'background-subscriber-stream-tag-active':13,'background-subscriber-stream-tag-default':13,'background-subscriber-stream-tag-hover':14,'background-toggle-checked':9,/*'background-tooltip':1,*/'background-top-nav':7,'border-brand':9,'border-button':7,'border-button-active':8,'border-button-focus':9,'border-button-hover':8,'border-input-checkbox-checked':9,'border-input-checkbox-focus':9,'border-input-focus':9,'border-interactable-selected':9,'border-subscriber-stream-tag':10,'border-tab-active':8,'border-tab-focus':8,'border-tab-hover':8,'border-toggle-focus':8,'border-toggle-hover':8,'border-whisper-incoming':10,'fill-brand':9,'text-button-text':8,'text-button-text-focus':'o1','text-button-text-hover':'o1','text-link':8,'text-link-active':9,'text-link-focus':9,'text-link-hover':9,'text-link-visited':9,'text-overlay-link-active':13,'text-overlay-link-focus':13,'text-overlay-link-hover':13,'text-tab-active':8},'s':{'button-active':[8,'0 0 6px 0',''],'button-focus':[8,'0 0 6px 0',''],'input-focus':[10,'0 0 10px -2px',''],'interactable-focus':[8,'0 0 6px 1px',''],'tab-focus':[8,'0 4px 6px -4px','']}},
dark: {'c':{'accent': 9,'background-accent':8,'background-accent-alt':7,'background-accent-alt-2':6,'background-button':7,'background-button-active':7,'background-button-focus':8,'background-button-hover':8,'background-button-primary-active':7,'background-button-primary-default':9,'background-button-primary-hover':8,'background-graph':2,'background-graph-fill':8,'background-input-checkbox-checked':9,'background-input-checked':8,'background-interactable-active':9,'background-interactable-selected':9,'background-interactable-hover':8,'background-progress-countdown-status':9,'background-progress-status':9,'background-range-fill':9,'background-subscriber-stream-tag-active':4,'background-subscriber-stream-tag-default':4,'background-subscriber-stream-tag-hover':3,'background-toggle-checked':9,/*'background-tooltip':1,*/'background-top-nav':6,'border-brand':9,'border-button':7,'border-button-active':8,'border-button-focus':9,'border-button-hover':8,'border-input-checkbox-checked':9,'border-input-checkbox-focus':9,'border-input-focus':9,'border-interactable-selected':10,'border-subscriber-stream-tag':5,'border-tab-active':11,'border-tab-focus':11,'border-tab-hover':11,'border-toggle-focus':7,'border-toggle-hover':7,'border-whisper-incoming':10,'fill-brand':9,'text-button-text':10,'text-button-text-focus':'o1','text-button-text-hover':'o1','text-link':10,'text-link-active':10,'text-link-focus':10,'text-link-hover':10,'text-link-visited':10,'text-overlay-link-active':13,'text-overlay-link-focus':13,'text-overlay-link-hover':13,'text-tab-active':11,'background-chat':1,'background-chat-alt':3,'background-chat-header':2,'background-modal':3,'text-button-text-active':'o2'/*,'text-tooltip':1*/},'s':{'button-active':[8,'0 0 6px 0',''],'button-focus':[8,'0 0 6px 0',''],'input-focus':[8,'0 0 10px -2px',''],'interactable-focus':[8,'0 0 6px 0',''],'tab-focus':[11,'0 4px 6px -4px',''],'input':[5,'inset 0 0 0 1px','']}},
light: {'c':{'accent': 9,'background-accent':8,'background-accent-alt':7,'background-accent-alt-2':6,'background-button':7,'background-button-active':7,'background-button-focus':8,'background-button-hover':8,'background-button-primary-active':7,'background-button-primary-default':9,'background-button-primary-hover':8,'background-graph':15,'background-graph-fill':9,'background-input-checkbox-checked':9,'background-input-checked':8,'background-interactable-active':9,'background-interactable-selected':9,'background-interactable-hover':8,'background-progress-countdown-status':8,'background-progress-status':8,'background-range-fill':9,'background-subscriber-stream-tag-active':13,'background-subscriber-stream-tag-default':13,'background-subscriber-stream-tag-hover':14,'background-toggle-checked':9,/*'background-tooltip':1,*/'background-top-nav':7,'border-brand':9,'border-button':7,'border-button-active':8,'border-button-focus':9,'border-button-hover':8,'border-input-checkbox-checked':9,'border-input-checkbox-focus':9,'border-input-focus':9,'border-interactable-selected':9,'border-subscriber-stream-tag':10,'border-tab-active':8,'border-tab-focus':8,'border-tab-hover':8,'border-toggle-focus':8,'border-toggle-hover':8,'border-whisper-incoming':10,'fill-brand':9,'text-button-text':8,'text-button-text-focus':'o1','text-button-text-hover':'o1','text-link':8,'text-link-active':9,'text-link-focus':9,'text-link-hover':9,'text-link-visited':9,'text-overlay-link-active':13,'text-overlay-link-focus':13,'text-overlay-link-hover':13,'text-tab-active':8},'s':{'button-active':[8,'0 0 6px 0',''],'button-focus':[8,'0 0 6px 0',''],'input-focus':[10,'0 0 10px -2px',''],'interactable-focus':[8,'0 0 6px 1px',''],'tab-focus':[8,'0 4px 6px -4px','']}},
accent_dark: {'c':{'accent-hover':10,'accent':9,'accent-primary-1':1,'accent-primary-2':5,'accent-primary-3':6,'accent-primary-4':7,'accent-primary-5':8},'s':{}},
accent_light: {'c':{'accent-hover':10,'accent':9,'accent-primary-1':1,'accent-primary-2':5,'accent-primary-3':6,'accent-primary-4':7,'accent-primary-5':8},'s':{}}
};

View file

@ -302,7 +302,7 @@ export default class VideoChatHook extends Module {
{hide_timestamps || (<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--hover-enabled tw-interactable--inverted tw-interactive" onClick={this.onTimestampClickHandler}>
<button class="tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive" onClick={this.onTimestampClickHandler}>
<div class="tw-pd-x-05">
<p class="tw-font-size-7">{print_duration(context.comment.contentOffset)}</p>
</div>

View file

@ -34,6 +34,25 @@
display: none;
}
.chat-line__message-highlight {
pointer-events: none;
}
.ffz-notice-line {
&:focus-within,
&:hover,
&:focus {
.chat-line__message-highlight {
background-color: var(--color-background-interactable-alpha-hover);
}
.chat-line__reply-icon {
display: block;
}
}
}
.autocomplete-balloon {
.autocomplete-balloon__item {
> .tw-flex {

View file

@ -46,7 +46,7 @@
:id="'ffz-autocomplete-item-' + id + '-' + idx"
:key="has(item, 'id') ? item.id : idx"
:class="{'tw-interactable--hover' : idx === index}"
class="tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive"
class="tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive"
tabindex="-1"
data-selectable="true"
@mouseenter="index = idx"

View file

@ -48,7 +48,7 @@
:aria-checked="val === i[0]"
:class="{'tw-interactable--selected': val === i[0]}"
:data-title="i[1]"
class="ffz-tooltip ffz-icon tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive"
class="ffz-tooltip ffz-icon tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive"
role="radio"
tabindex="0"
@keydown.space.stop.prevent=""

View file

@ -102,7 +102,7 @@ export function createElement(tag, props, ...children) {
else
for(const k in prop)
if ( has(prop, k) ) {
if ( has(el.style, k) )
if ( has(el.style, k) || has(Object.getPrototypeOf(el.style), k) )
el.style[k] = prop[k];
else
el.style.setProperty(k, prop[k]);