diff --git a/src/modules/chat/badges.js b/src/modules/chat/badges.js index c7f5b80e..49ae9f06 100644 --- a/src/modules/chat/badges.js +++ b/src/modules/chat/badges.js @@ -4,24 +4,409 @@ // Badge Handling // ============================================================================ +import {API_SERVER, WEBKIT_CSS as WEBKIT} from 'utilities/constants'; + +import {createElement as e, ManagedStyle} from 'utilities/dom'; +import {has} from 'utilities/object'; import Module from 'utilities/module'; export const CSS_BADGES = { - staff: { 1: { color: '#200f33', use_svg: true } }, - admin: { 1: { color: '#faaf19', use_svg: true } }, - global_mod: { 1: { color: '#0c6f20', use_svg: true } }, - broadcaster: { 1: { color: '#e71818', use_svg: true } }, - moderator: { 1: { color: '#34ae0a', use_svg: true } }, + staff: { 1: { color: '#200f33', svg: true, trans: { color: '#6441a5' } } }, + admin: { 1: { color: '#faaf19', svg: true } }, + global_mod: { 1: { color: '#0c6f20', svg: true } }, + broadcaster: { 1: { color: '#e71818', svg: true } }, + moderator: { 1: { color: '#34ae0a', svg: true } }, twitchbot: { 1: { color: '#34ae0a' } }, - partner: { 1: { color: 'transparent', has_trans: true, trans_color: '#6441a5' } }, + partner: { 1: { color: 'transparent', trans: { image: true, color: '#6441a5' } } }, - turbo: { 1: { color: '#6441a5', use_svg: true } }, + turbo: { 1: { color: '#6441a5', svg: true } }, premium: { 1: { color: '#009cdc' } }, subscriber: { 0: { color: '#6441a4' }, 1: { color: '#6441a4' }}, } -export default class Badges extends Module { +const NO_REPEAT = 'background-repeat:no-repeat;background-position:center;', + BASE_IMAGE = 'https://cdn.frankerfacez.com/badges/twitch/', + CSS_TEMPLATES = { + 0: data => `background:${data.image} ${data.color};${data.svg ? `background-size:${data.scale*18}px;` : `background-image:${data.image_set};`}${NO_REPEAT}`, + 1: data => `${CSS_TEMPLATES[0](data)}border-radius:${data.scale*2}px;`, + 2: data => `${CSS_TEMPLATES[0](data)}border-radius:${data.scale*9}px;background-size:${data.scale*16}px;`, + 3: data => `background:${data.color};border-radius:${data.scale*9}px;`, + 4: data => `${CSS_TEMPLATES[3](data)}height:${data.scale*10}px;min-width:${data.scale*10}px;`, + 5: data => `background:${data.image};${data.svg ? `background-size:${data.scale*18}px;` : `background-image:${data.image_set};`}${NO_REPEAT}`, + 6: data => `background:linear-gradient(${data.color},${data.color});${WEBKIT}mask-image:${data.image};${data.svg ? `${WEBKIT}mask-size:${data.scale*18}px ${data.scale*18}px;` : `${WEBKIT}mask-image:${data.image_set};`}` + }; + +export function generateBadgeCSS(badge, version, data, style, is_dark, scale = 1) { + let color = data.color || 'transparent', + base_image = data.image || `${BASE_IMAGE}${badge}${data.svg ? '.svg' : `/${version}/`}`, + trans = false, + invert = false, + svg, image, image_set; + + if ( style > 4 ) { + const td = data.trans || {}; + color = td.color || color; + + if ( td.image ) { + trans = true; + if ( td.image !== true ) + base_image = td.image; + + } + + if ( has(td, 'invert') ) + invert = td.invert && ! is_dark; + else + invert = style === 5 && ! is_dark; + } + + if ( style === 3 || style === 4 ) { + if ( color === 'transparent' && data.trans ) + color = data.trans.color || color; + + } else { + if ( style < 5 && color === 'transparent' ) + style = 0; + + svg = base_image.endsWith('.svg'); + image = `url("${svg ? base_image : `${base_image}${scale}${trans ? '_trans' : ''}.png`}")`; + + if ( svg && scale < 4 ) { + if ( scale === 1 ) + image_set = `${WEBKIT}image-set(${image} 1x, url("${base_image}2${trans ? '_trans' : ''}.png") 2x, url("${base_image}4${trans ? '_trans' : ''}.png") 4x)`; + + else if ( scale === 2 ) + image_set = `${WEBKIT}image-set(${image} 1x, url("${base_image}4${trans ? '_trans' : ''}.png") 2x)`; + + } else + image_set = svg; + } + + return `${invert ? 'filter:invert(100%);' : ''}${CSS_TEMPLATES[style]({ + scale, + color, + image, + image_set, + svg + })}`; +} + + +export default class Badges extends Module { + constructor(...args) { + super(...args); + + this.inject('i18n'); + this.inject('settings'); + this.inject('socket'); + this.inject('tooltips'); + + this.style = new ManagedStyle('badges'); + this.badges = {}; + + this.twitch_badges = new Map; + + this.settings.add('chat.badges.style', { + default: 0, + ui: { + path: 'Chat > Badges >> Appearance', + title: 'Style', + component: 'setting-select-box', + data: [ + {value: 0, title: 'Default'}, + {value: 1, title: 'Rounded'}, + {value: 2, title: 'Circular'}, + {value: 3, title: 'Circular (Color Only)'}, + {value: 4, title: 'Circular (Color Only, Small)'}, + {value: 5, title: 'Transparent'}, + {value: 6, title: 'Transparent (Colored)'} + ] + } + }); + } + + + onEnable() { + this.parent.context.on('changed:chat.badges.style', this.rebuildAllCSS, this); + this.parent.context.on('changed:theme.is-dark', this.rebuildAllCSS, this); + this.parent.context.on('changed:theme.tooltips-dark', this.rebuildAllCSS, this); + + this.rebuildAllCSS(); + this.loadGlobalBadges(); + + this.tooltips.types.badge = (target, tip) => { + const container = target.parentElement.parentElement, + provider = target.dataset.provider, + badge = target.dataset.badge, + version = target.dataset.version, + room_id = container.dataset.roomId, + room_login = container.dataset.room; + + let preview, title; + + if ( provider === 'twitch' ) { + const data = this.getTwitchBadge(badge, version, room_id, room_login); + if ( ! data ) + return; + + preview = data.image4x; + title = data.title; + + } else if ( provider === 'ffz' ) { + const data = this.badges[badge]; + if ( ! data ) + return; + + preview = e('span', { + className: 'preview-image ffz-badge', + style: { + height: '72px', + width: '72px', + backgroundColor: data.color, + backgroundImage: `url("${data.urls[4]}")` + } + }) + + title = this.i18n.t(`badges.${data.name}`, data.title); + + } else + title = `Unknown Badge`; + + if ( preview ) + if ( this.parent.context.get('tooltip.badge-images') ) { + if ( typeof preview === 'string' ) + preview = e('img', { + className: 'preview-image ffz-badge', + src: preview, + }); + + } else + preview = null; + + return [ + preview, + title + ]; + }; + } + + + render(msg, e) { // eslint-disable-line class-methods-use-this + const out = [], + twitch_badges = msg.badges || {}; + + /*user = msg.user || {}, + user_id = user.userID, + user_login = user.userLogin, + room_id = msg.roomID, + room_login = msg.roomLogin, + + badges = this.getBadges(user_id, user_login, room_id, room_login);*/ + + for(const badge_id in twitch_badges) + if ( has(twitch_badges, badge_id) ) { + const version = twitch_badges[badge_id]; + out.push(e('span', { + className: 'ffz-tooltip ffz-badge', + 'data-tooltip-type': 'badge', + 'data-provider': 'twitch', + 'data-badge': badge_id, + 'data-version': version + })); + } + + /*for(const badge of badges) + if ( badge && badge.id ) { + const full_badge = this.badges[badge.id], + style = {}, + props = { + className: 'ffz-tooltip ffz-badge', + 'data-tooltip-type': 'badge', + 'data-provider': 'ffz', + 'data-badge': badge.id, + style + }; + + if ( full_badge.image ) + style.backgroundImage = `url("${full_badge.image}")`; + + if ( full_badge.color ) + style.backgroundColor = full_badge.color; + + out.push(e('span', props)); + }*/ + + return out; + } + + + rebuildAllCSS() { + for(const room of this.parent.iterateRooms()) + room.buildBadgeCSS(); + + this.buildTwitchBadgeCSS(); + this.buildTwitchCSSBadgeCSS(); + } + + + // ======================================================================== + // Extension Badges + // ======================================================================== + + getBadges(user_id, user_login, room_id, room_login) { + const room = this.parent.getRoom(room_id, room_login, true), + global_user = this.parent.getUser(user_id, user_login, true), + room_user = room && room.getUser(user_id, user_login, true); + + return (global_user ? global_user.badges._cache : []).concat( + room_user ? room_user.badges._cache : []); + } + + + async loadGlobalBadges(tries = 0) { + let response, data; + try { + response = await fetch(`${API_SERVER}/v1/badges`); + } catch(err) { + tries++; + if ( tries < 10 ) + return setTimeout(() => this.loadGlobalBadges(tries), 500 * tries); + + this.log.error('Error loading global badge data.', err); + return false; + } + + if ( ! response.ok ) + return false; + + try { + data = await response.json(); + } catch(err) { + this.log.error('Error parsing global badge data.', err); + return false; + } + + let badges = 0, users = 0; + + if ( data.badges ) + for(const badge of data.badges) + if ( badge && badge.id ) { + this.loadBadgeData(badge.id, badge); + badges++; + } + + if ( data.users ) + for(const badge_id in data.users) + if ( has(data.users, badge_id) ) { + const badge = this.badges[badge_id]; + let c = 0; + for(const user_login of data.users[badge_id]) { + const user = this.parent.getUser(undefined, user_login); + if ( user.addBadge('ffz-global', badge_id) ) { + c++; + users++; + } + } + + if ( c > 0 ) + this.log.info(`Added "${badge ? badge.name : `#${badge_id}`}" to ${c} users.`); + } + + this.log.info(`Loaded ${badges} badges and assigned them to ${users} users.`); + } + + + loadBadgeData(badge_id, data) { + this.badges[badge_id] = data; + + if ( data.replaces && ! data.replaces_type ) { + data.replaces_type = data.replaces; + data.replaces = true; + } + + if ( data.name === 'developer' || data.name === 'supporter' ) + data.click_url = 'https://www.frankerfacez.com/donate'; + } + + + // ======================================================================== + // Twitch Badges + // ======================================================================== + + getTwitchBadge(badge, version, room_id, room_login) { + const room = this.parent.getRoom(room_id, room_login, true); + let b; + + if ( room ) { + const versions = room.badges.get(badge); + b = versions && versions.get(version); + } + + if ( ! b ) { + const versions = this.twitch_badges.get(badge); + b = version && versions.get(version); + } + + return b; + } + + updateTwitchBadges(badges) { + this.twitch_badges = badges; + this.buildTwitchBadgeCSS(); + } + + + buildTwitchCSSBadgeCSS() { + const style = this.parent.context.get('chat.badges.style'), + is_dark = this.parent.context.get('theme.is-dark'); + + const out = []; + for(const key in CSS_BADGES) + if ( has(CSS_BADGES, key) ) { + const data = CSS_BADGES[key]; + for(const version in data) + if ( has(data, version) ) { + const d = data[version], + selector = `.ffz-badge[data-badge="${key}"][data-version="${version}"]`; + + out.push(`${selector}{${generateBadgeCSS(key, version, d, style, is_dark)}}`); + } + } + + this.style.set('css-badges', out.join('\n')); + } + + + buildTwitchBadgeCSS() { + if ( ! this.twitch_badges ) + this.style.delete('twitch-badges'); + + const out = []; + for(const [key, versions] of this.twitch_badges) { + if ( has(CSS_BADGES, key) ) + continue; + + for(const [version, data] of versions) { + out.push(`.ffz-badge[data-badge="${key}"][data-version="${version}"] { + background-color: transparent; + filter: none; + ${WEBKIT}mask-image: none; + background-image: url("${data.image1x}"); + background-image: ${WEBKIT}image-set( + url("${data.image1x}") 1x, + url("${data.image2x}") 2x, + url("${data.image4x}") 4x + ); +}`) + } + } + + if ( out.length ) + this.style.set('twitch-badges', out.join('\n')); + else + this.style.delete('twitch-badges'); + } } \ No newline at end of file diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js index 334a5452..911a0e18 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -4,9 +4,6 @@ // Chat // ============================================================================ -import {IS_WEBKIT} from 'utilities/constants'; -const WEBKIT = IS_WEBKIT ? '-webkit-' : ''; - import Module from 'utilities/module'; import {createElement, ManagedStyle} from 'utilities/dom'; import {timeout, has} from 'utilities/object'; @@ -242,13 +239,13 @@ export default class Chat extends Module { this.context.on('changed:theme.is-dark', () => { for(const key in this.rooms) if ( this.rooms[key] ) - this.rooms[key].updateBitsCSS(); + this.rooms[key].buildBitsCSS(); }); this.context.on('changed:chat.bits.animated', () => { for(const key in this.rooms) if ( this.rooms[key] ) - this.rooms[key].updateBitsCSS(); + this.rooms[key].buildBitsCSS(); }); } @@ -260,53 +257,6 @@ export default class Chat extends Module { } - getBadge(badge, version, room) { - let b; - if ( this.room_ids[room] ) { - const versions = this.room_ids[room].badges.get(badge); - b = versions && versions.get(version); - } - - if ( ! b ) { - const versions = this.badges.get(badge); - b = versions && versions.get(version); - } - - return b; - } - - - - updateBadges(badges) { - this.badges = badges; - this.updateBadgeCSS(); - } - - - updateBadgeCSS() { - if ( ! this.badges ) - this.style.delete('badges'); - - const out = []; - for(const [key, versions] of this.badges) - for(const [version, data] of versions) { - out.push(`.ffz-badge.badge--${key}.version--${version} { - background-color: transparent; - filter: none; - ${WEBKIT}mask-image: none; - background-image: url("${data.image1x}"); - background-image: ${WEBKIT}image-set( - url("${data.image1x}") 1x, - url("${data.image2x}") 2x, - url("${data.image4x}") 4x - ); -}`) - } - - this.style.set('badges', out.join('\n')); - } - - getUser(id, login, no_create, no_login) { let user; if ( id && typeof id === 'number' ) @@ -404,6 +354,27 @@ export default class Chat extends Module { } + * iterateRooms() { + const visited = new Set; + + for(const id in this.room_ids) + if ( has(this.room_ids, id) ) { + const room = this.room_ids[id]; + if ( room ) { + visited.add(room); + yield room; + } + } + + for(const login in this.rooms) + if ( has(this.rooms, login) ) { + const room = this.rooms[login]; + if ( room && ! visited.has(room) ) + yield room; + } + } + + formatTime(time) { if (!( time instanceof Date )) time = new Date(time); @@ -467,25 +438,6 @@ export default class Chat extends Module { } - renderBadges(msg, e) { // eslint-disable-line class-methods-use-this - const out = [], - badges = msg.badges || {}; - - for(const key in badges) - if ( has(badges, key) ) { - const version = badges[key]; - out.push(e('span', { - className: `ffz-tooltip ffz-badge badge--${key} version--${version}`, - 'data-tooltip-type': 'badge', - 'data-badge': key, - 'data-version': version - })) - } - - return out; - } - - renderTokens(tokens, e) { if ( ! e ) e = createElement; diff --git a/src/modules/chat/room.js b/src/modules/chat/room.js index f7301685..91b8c487 100644 --- a/src/modules/chat/room.js +++ b/src/modules/chat/room.js @@ -6,13 +6,11 @@ import User from './user'; -import {API_SERVER, IS_WEBKIT} from 'utilities/constants'; +import {API_SERVER, WEBKIT_CSS as WEBKIT} from 'utilities/constants'; import {ManagedStyle} from 'utilities/dom'; import {has, SourcedSet} from 'utilities/object'; -const WEBKIT = IS_WEBKIT ? '-webkit-' : ''; - export default class Room { constructor(manager, id, login) { @@ -270,10 +268,10 @@ export default class Room { updateBadges(badges) { this.badges = badges; - this.updateBadgeCSS(); + this.buildBadgeCSS(); } - updateBadgeCSS() { + buildBadgeCSS() { if ( ! this.badges ) return this.style.delete('badges'); @@ -282,7 +280,7 @@ export default class Room { for(const [key, versions] of this.badges) { for(const [version, data] of versions) { - const rule = `.ffz-badge.badge--${key}.version--${version}`; + const rule = `.ffz-badge[data-badge="${key}"][data-version="${version}"]`; out.push(`[data-room-id="${id}"] ${rule} { background-color: transparent; @@ -309,17 +307,16 @@ export default class Room { updateBitsConfig(config) { this.bitsConfig = config; - this.updateBitsCSS(); + this.buildBitsCSS(); } - updateBitsCSS() { + buildBitsCSS() { if ( ! this.bitsConfig ) return this.style.delete('bits'); const animated = this.manager.context.get('chat.bits.animated') ? 'animated' : 'static', - is_dark = this.manager.context.get('theme.is-dark'), - theme = is_dark ? 'DARK' : 'LIGHT', - antitheme = is_dark ? 'LIGHT' : 'DARK', + theme = this.manager.context.get('theme.is-dark') ? 'DARK' : 'LIGHT', + tt_theme = this.manager.context.get('theme.tooltips-dark') ? 'DARK' : 'LIGHT', out = []; for(const key in this.bitsConfig) @@ -331,7 +328,7 @@ export default class Room { for(let i=0; i < l; i++) { const images = tiers[i].images[theme][animated], - anti_images = tiers[i].images[antitheme][animated]; + tt_images = tiers[i].images[tt_theme][animated]; out.push(`.ffz-cheer[data-prefix="${prefix}"][data-tier="${i}"] { color: ${tiers[i].color}; @@ -343,15 +340,15 @@ export default class Room { ); } .ffz__tooltip .ffz-cheer[data-prefix="${prefix}"][data-tier="${i}"] { - background-image: url("${anti_images[1]}"); + background-image: url("${tt_images[1]}"); background-image: ${WEBKIT}image-set( - url("${anti_images[1]}") 1x, - url("${anti_images[2]}") 2x, - url("${anti_images[4]}") 4x + url("${tt_images[1]}") 1x, + url("${tt_images[2]}") 2x, + url("${tt_images[4]}") 4x ); } .ffz-cheer-preview[data-prefix="${prefix}"][data-tier="${i}"] { - background-image: url("${anti_images[4]}"); + background-image: url("${tt_images[4]}"); }`) } } diff --git a/src/modules/chat/user.js b/src/modules/chat/user.js index 6d3d45b3..60a08ebe 100644 --- a/src/modules/chat/user.js +++ b/src/modules/chat/user.js @@ -18,6 +18,7 @@ export default class User { (room || manager).user_ids[id] = this; this.emote_sets = new SourcedSet; + this.badges = new SourcedSet; } destroy() { @@ -59,18 +60,60 @@ export default class User { } + // ======================================================================== + // Add Badges + // ======================================================================== + + addBadge(provider, badge_id, data) { + if ( data ) + data.id = badge_id; + else + data = {id: badge_id}; + + if ( this.badges.has(provider) ) + for(const old_b of this.badges.get(provider)) + if ( old_b.id == badge_id ) { + Object.assign(old_b, data); + return false; + } + + this.badges.push(provider, data); + //this.manager.badges.refBadge(badge_id); + return true; + } + + + getBadge(badge_id) { + for(const badge of this.badges._cache) + if ( badge.id == badge_id ) + return badge; + } + + + removeBadge(provider, badge_id) { + if ( ! this.badges.has(provider) ) + return false; + + for(const old_b of this.badges.get(provider)) + if ( old_b.id == badge_id ) { + this.badges.remove(provider, old_b); + //this.manager.badges.unrefBadge(badge_id); + return true; + } + } + + + // ======================================================================== // Emote Sets // ======================================================================== - addSet(provider, set_id, data) { + addSet(provider, set_id) { if ( ! this.emote_sets.sourceIncludes(provider, set_id) ) { this.emote_sets.push(provider, set_id); this.manager.emotes.refSet(set_id); + return true; } - - if ( data ) - this.manager.emotes.loadSetData(set_id, data); } removeSet(provider, set_id) { diff --git a/src/modules/tooltips.js b/src/modules/tooltips.js index e4c72032..9e8d2a8f 100644 --- a/src/modules/tooltips.js +++ b/src/modules/tooltips.js @@ -17,9 +17,6 @@ export default class TooltipProvider extends Module { this.should_enable = true; - this.inject('i18n'); - this.inject('chat'); - this.types.json = target => { const title = target.dataset.title; return [ @@ -33,34 +30,6 @@ export default class TooltipProvider extends Module { }, target.dataset.data) ] } - - this.types.badge = (target, tip) => { - const container = target.parentElement.parentElement, - - badge = target.dataset.badge, - version = target.dataset.version, - room = container.dataset.roomId, - - data = this.chat.getBadge(badge, version, room); - - if ( ! data ) - return; - - return [ - this.chat.context.get('tooltip.badge-images') && e('img', { - className: 'preview-image', - src: data.image4x, - - style: { - height: '72px' - }, - - onLoad: tip.update - }), - - data.title - ] - }; } onEnable() { diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index 3f138bf9..32e29af5 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -443,16 +443,15 @@ export default class ChatHook extends Module { if ( u ) e.emotes = u.emotes; - if ( original.action ) { + if ( original.action ) e.message = original.action; - - // Twitch doesn't generate a proper emote tag for echoed back - // actions, so we have to regenerate it. Fun. :D - if ( u && u.username === i.userLogin ) - e.emotes = findEmotes(e.message, i.selfEmotes); - - } else + else e.message = original.message.body; + + // Twitch doesn't generate a proper emote tag for echoed back + // actions, so we have to regenerate it. Fun. :D + if ( u && u.username === i.userLogin ) + e.emotes = findEmotes(e.message, i.selfEmotes); } //e.original = original; @@ -610,7 +609,7 @@ export default class ChatHook extends Module { return; if ( props.badgeSets ) { - this.chat.updateBadges(props.badgeSets.globalsBySet); + this.chat.badges.updateTwitchBadges(props.badgeSets.globalsBySet); this.updateRoomBadges(cont, props.badgeSets.channelsBySet); } } @@ -637,7 +636,7 @@ export default class ChatHook extends Module { obscl = obs.channelsBySet && obs.channelsBySet.size || 0; if ( bsgl !== obsgl ) - this.chat.updateBadges(bs.globalsBySet); + this.chat.badges.updateTwitchBadges(bs.globalsBySet); if ( bscl !== obscl ) this.updateRoomBadges(cont, bs.channelsBySet); diff --git a/src/sites/twitch-twilight/modules/chat/line.js b/src/sites/twitch-twilight/modules/chat/line.js index 5f80a1ec..d7051f02 100644 --- a/src/sites/twitch-twilight/modules/chat/line.js +++ b/src/sites/twitch-twilight/modules/chat/line.js @@ -75,34 +75,33 @@ export default class ChatLine extends Module { if ( ! msg.message && msg.messageParts ) detokenizeMessage(msg); - const tokens = t.chat.tokenizeMessage(msg, {login: this.props.currentUserLogin, display: this.props.currentUserDisplayName}), - fragment = t.chat.renderTokens(tokens, e); + const tokens = msg.ffzTokens = msg.ffzTokens || t.chat.tokenizeMessage(msg, {login: this.props.currentUserLogin, display: this.props.currentUserDisplayName}); let cls = 'chat-line__message', - out = fragment.length ? [ + out = tokens.length ? [ this.props.showTimestamps && e('span', { className: 'chat-line__timestamp' }, t.chat.formatTime(msg.timestamp)), this.renderModerationIcons(), e('span', { className: 'chat-line__message--badges' - }, t.chat.renderBadges(msg, e)), + }, t.chat.badges.render(msg, e)), e('a', { - className: 'chat-author__display-name', + className: 'chat-author__display-name notranslate', style: { color }, onClick: this.usernameClickHandler - }, user.userDisplayName), - user.isIntl && e('span', { - className: 'chat-author__intl-login', - style: { color }, - onClick: this.usernameClickHandler - }, ` (${user.userLogin})`), + }, [ + user.userDisplayName, + user.isIntl && e('span', { + className: 'chat-author__intl-login' + }, ` (${user.userLogin})`) + ]), e('span', null, is_action ? ' ' : ': '), show ? e('span', { className:'message', style: is_action ? { color } : null - }, fragment) + }, t.chat.renderTokens(tokens, e)) : e('span', { className: 'chat-line__message--deleted', diff --git a/src/sites/twitch-twilight/modules/chat/scroller.js b/src/sites/twitch-twilight/modules/chat/scroller.js index 9c429728..ca77759d 100644 --- a/src/sites/twitch-twilight/modules/chat/scroller.js +++ b/src/sites/twitch-twilight/modules/chat/scroller.js @@ -45,7 +45,7 @@ export default class Scroller extends Module { } onEnable() { - this.i18n.on('i18n:update', () => { + this.on('i18n:update', () => { for(const inst of this.ChatScroller.instances) inst.ffzUpdateText(); }); diff --git a/src/sites/twitch-twilight/modules/theme/index.js b/src/sites/twitch-twilight/modules/theme/index.js index b5d074d6..d27f21af 100644 --- a/src/sites/twitch-twilight/modules/theme/index.js +++ b/src/sites/twitch-twilight/modules/theme/index.js @@ -42,6 +42,13 @@ export default class ThemeEngine extends Module { } }); + this.settings.add('theme.tooltips-dark', { + requires: ['theme.is-dark'], + process(ctx) { + return ! ctx.get('theme.is-dark') + } + }); + this._style = null; } diff --git a/src/utilities/constants.js b/src/utilities/constants.js index bc99c32d..23f09b05 100644 --- a/src/utilities/constants.js +++ b/src/utilities/constants.js @@ -21,4 +21,6 @@ export const WS_CLUSTERS = { export const IS_OSX = navigator.platform ? navigator.platform.indexOf('Mac') !== -1 : /OS X/.test(navigator.userAgent); export const IS_WIN = navigator.platform ? navigator.platform.indexOf('Win') !== -1 : /Windows/.test(navigator.userAgent); -export const IS_WEBKIT = navigator.userAgent.indexOf('AppleWebKit/') !== -1 && navigator.userAgent.indexOf('Edge/') === -1; \ No newline at end of file +export const IS_WEBKIT = navigator.userAgent.indexOf('AppleWebKit/') !== -1 && navigator.userAgent.indexOf('Edge/') === -1; + +export const WEBKIT_CSS = IS_WEBKIT ? '-webkit-' : ''; \ No newline at end of file diff --git a/src/utilities/object.js b/src/utilities/object.js index e1b4dab4..791928b2 100644 --- a/src/utilities/object.js +++ b/src/utilities/object.js @@ -170,6 +170,9 @@ export function maybe_call(fn, ctx, ...args) { const SPLIT_REGEX = /[^\uD800-\uDFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDFFF]/g; export function split_chars(str) { + if ( str === '' ) + return []; + return str.match(SPLIT_REGEX); } diff --git a/styles/tooltips.scss b/styles/tooltips.scss index 36db1f11..396609b3 100644 --- a/styles/tooltips.scss +++ b/styles/tooltips.scss @@ -80,6 +80,12 @@ body { text-align: center; .preview-image { + &.ffz-badge { + height: 72px; + width: 72px; + background-repeat: no-repeat; + } + display: block; margin: 3px auto 6px; }