mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 12:55:55 +00:00
4.42.0
* Fixed: Navigating between channels with the audio compressor enabled (or having previously been enabled) causing the player to become stuck loading infinitely. (Closes #1317) * Fixed: Features on the `clips` and `player` subdomains not working correctly. (Closes #1336) * Changed: Finish implementing the initial emote effects.
This commit is contained in:
parent
e433aa3340
commit
915ad89f58
33 changed files with 839 additions and 152 deletions
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1 +1 @@
|
|||
custom: ["https://www.frankerfacez.com/donate"]
|
||||
custom: ["https://www.frankerfacez.com/subscribe"]
|
||||
|
|
|
@ -867,6 +867,20 @@
|
|||
"css": "doc-text",
|
||||
"code": 61686,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "090b7864c67408ce29c67a49429b17a7",
|
||||
"css": "fx",
|
||||
"code": 59469,
|
||||
"src": "custom_icons",
|
||||
"selected": true,
|
||||
"svg": {
|
||||
"path": "M435.1 30C411.8 34 382.3 46.4 357.4 62.8 339.8 74.4 313.5 100.9 301.3 119.5 284.3 145.2 268.2 183 257.7 222.2L252.8 240.2 212.5 240.5 172.4 241 163.9 273.9C159.2 292.1 155.4 307.8 155.4 308.6 155.3 310.1 164.1 310.5 194.8 310.5 221 310.5 234.4 311 234.4 312 234.4 315.4 169.5 572.6 165.4 585.8 158.6 606.7 151.8 620.2 146.2 623.7 135.2 630.3 118.7 631.2 79.5 627.5 49.1 624.6 38.7 625.2 26.7 630.6-5.7 645.1-8.9 689.1 20.9 709.6 32.8 717.8 43.7 721 62.3 721.7 112.1 723.9 160.4 702.7 202.9 660.1 224.1 638.8 237 621.1 250.8 594 272 552.2 272.8 549.5 321.5 350.2L331.3 310.3 396.1 311.1C431.5 311.6 468.6 312.5 478.4 312.9L496.1 313.8 504.3 349.8C509 369.7 512.7 387 512.7 388.2 512.7 390.5 484.9 432.3 471.7 449.7 460.5 464.2 438.3 489.3 436.2 489.3 435.4 489.3 432.1 486.9 428.9 484 411.3 468.2 389.1 467.7 372.2 482.8 361.5 492.5 356.8 502.9 356.8 517.1 356.8 527.1 357.6 529.8 361.5 538 369.1 553.3 381.9 562.5 400.6 566.5 414.6 569.2 428.6 567.5 444.7 561 468.3 551.7 492.5 529.8 520.3 493.2 528.1 483 534.8 475 535.3 475.8 535.5 476.4 537.6 481.3 539.6 486.8 550.3 515.3 573.8 553.3 591.8 571.4 611 590.8 636.3 600.7 666.5 600.7 684.1 600.7 695.5 597.4 710.4 587.7 732.3 573.5 743.6 560.4 747.8 544.2 752.5 526 749.3 513.3 736.8 501 727.9 491.9 721.4 489.3 708.7 489.3 702.7 489.3 698.4 490.4 691.7 493.8 680.7 499.2 676.3 504.2 669.1 519.6 666.1 526.3 663.1 531.7 662.5 531.7 656.8 531.7 643.8 516.2 635.2 499.5 626.8 483.4 625.8 479.9 613.8 423.5L603.5 375.9 608.8 367.8C625.8 342.2 649.2 317.3 661.4 311.9 665.6 310 675.3 308.1 686.7 306.7 697 305.6 709.1 303.7 713.8 302.5 733.4 297.5 746.9 281.4 747.5 262.2 748.4 229.1 718.8 208.4 683.3 217.5 655.7 224.7 616.6 252.5 593.3 281.8 589.6 286.4 586.4 290 585.9 290 585.5 290 583.4 284.2 581.2 277.1 573.9 253.9 563.1 240.7 548.3 237.5 543.3 236.4 515 236.7 452.8 238.2 404 239.4 360.8 240.2 356.7 239.9L349.2 239.5 355.4 213.1C367.1 163.2 377.9 136.2 392.1 121.9 399 115 400.3 114.3 406.3 114.3 409.9 114.3 422 115.9 433.2 118.1 481.8 127 494.5 125.5 510.6 109 528.1 91 526.8 61.8 507.6 44.1 491.7 29.6 466.6 24.6 435.1 30Z",
|
||||
"width": 1000
|
||||
},
|
||||
"search": [
|
||||
"fx"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Binary file not shown.
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Copyright (C) 2022 by original authors @ fontello.com</metadata>
|
||||
<metadata>Copyright (C) 2023 by original authors @ fontello.com</metadata>
|
||||
<defs>
|
||||
<font id="ffz-fontello" horiz-adv-x="1000" >
|
||||
<font-face font-family="ffz-fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
||||
|
@ -160,6 +160,8 @@
|
|||
|
||||
<glyph glyph-name="mange-suspicious" unicode="" d="M450 300v-100h100v100h-100z m-200 353a500 500 0 0 1 226 81l24 16 24-16a500 500 0 0 1 278-84h48l-25-282a450 450 0 0 0-202-338l-123-80-123 80a450 450 0 0 0-202 338l-25 282h49c17 0 34 1 51 3z m9-100l15-176a350 350 0 0 1 158-263l68-45 68 45a350 350 0 0 1 158 263l15 176a600 600 0 0 0-191 52v-205h-100v205a600 600 0 0 0-191-52z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="fx" unicode="" d="M435 820c-23-4-53-16-78-33-17-11-43-38-56-56-17-26-33-64-43-103l-5-18-40 0-41-1-8-33c-5-18-9-34-9-35 0-1 9-1 40-1 26 0 39-1 39-2 0-3-64-261-69-274-6-21-13-34-19-38-11-6-27-7-66-3-31 2-41 2-53-4-33-14-36-58-6-79 12-8 23-11 41-12 50-2 98 19 141 62 21 21 34 39 48 66 21 42 22 45 71 244l9 40 65-1c36-1 73-1 82-2l18-1 8-36c5-20 9-37 9-38 0-2-28-44-41-62-11-14-34-39-36-39-1 0-4 2-7 5-18 16-40 16-57 1-10-9-15-20-15-34 0-10 1-13 5-21 7-15 20-24 39-28 14-3 28-1 44 5 23 9 48 31 75 68 8 10 15 18 15 17 1 0 3-5 5-11 10-28 34-66 52-84 19-20 44-30 75-30 17 0 29 4 43 13 22 15 34 28 38 44 5 18 1 31-11 43-9 9-16 12-28 12-6 0-11-1-17-5-11-5-16-10-23-26-3-6-6-12-6-12-6 0-19 16-28 33-8 16-9 19-21 76l-10 47 5 8c17 26 40 51 52 56 5 2 14 4 26 5 10 1 22 3 27 5 19 5 33 21 34 40 0 33-29 54-65 45-27-8-66-35-90-65-3-4-7-8-7-8 0 0-3 6-5 13-7 23-18 36-33 40-5 1-33 0-95-1-49-1-92-2-96-2l-8 1 6 26c12 50 23 77 37 91 7 7 8 8 14 8 4 0 16-2 27-4 49-9 62-7 78 9 17 18 16 47-3 65-16 14-41 19-73 14z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="move" unicode="" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-215v-215h72q14 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-11 10-11 25t11 25 25 10h72v215h-215v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h215v215h-72q-14 0-25 10t-11 25 11 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26t-11-25-25-10h-72v-215h215v72q0 14 10 25t25 11 26-11l142-143q11-10 11-25z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="link-ext" unicode="" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
||||
|
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -13,6 +13,7 @@ import SettingsManager from './settings/index';
|
|||
import AddonManager from './addons';
|
||||
import ExperimentManager from './experiments';
|
||||
import {TranslationManager} from './i18n';
|
||||
import StagingSelector from './staging';
|
||||
|
||||
import Site from './sites/clips';
|
||||
import Tooltips from 'src/modules/tooltips';
|
||||
|
@ -52,6 +53,7 @@ class FrankerFaceZ extends Module {
|
|||
this.inject('settings', SettingsManager);
|
||||
this.inject('experiments', ExperimentManager);
|
||||
this.inject('i18n', TranslationManager);
|
||||
this.inject('staging', StagingSelector);
|
||||
this.inject('site', Site);
|
||||
this.inject('addons', AddonManager);
|
||||
|
||||
|
|
|
@ -800,6 +800,10 @@ export class TranslationManager extends Module {
|
|||
return this._.formatNumber(...args);
|
||||
}
|
||||
|
||||
formatCurrency(...args) {
|
||||
return this._.formatCurrency(...args);
|
||||
}
|
||||
|
||||
formatDuration(...args) {
|
||||
return this._.formatDuration(...args);
|
||||
}
|
||||
|
|
|
@ -995,7 +995,7 @@ export default class Badges extends Module {
|
|||
name = badge?.name;
|
||||
let c = 0;
|
||||
|
||||
if ( name === 'supporter' || name === 'bot' ) {
|
||||
if ( name === 'supporter' || name === 'subwoofer' || name === 'bot' ) {
|
||||
this.setBulk('ffz-global', badge_id, data.users[badge_id].map(x => String(x)));
|
||||
/*this.supporter_id = badge_id;
|
||||
for(const user_id of data.users[badge_id])
|
||||
|
@ -1032,8 +1032,8 @@ export default class Badges extends Module {
|
|||
data.replaces = true;
|
||||
}
|
||||
|
||||
if ( ! data.addon && (data.name === 'developer' || data.name === 'supporter') )
|
||||
data.click_url = 'https://www.frankerfacez.com/donate';
|
||||
if ( ! data.addon && (data.name === 'developer' || data.name === 'subwoofer' || data.name === 'supporter') )
|
||||
data.click_url = 'https://www.frankerfacez.com/subscribe';
|
||||
}
|
||||
|
||||
if ( generate_css )
|
||||
|
|
|
@ -11,6 +11,7 @@ import {NEW_API, IS_OSX, EmoteTypes, TWITCH_GLOBAL_SETS, TWITCH_POINTS_SETS, TWI
|
|||
|
||||
import GET_EMOTE from './emote_info.gql';
|
||||
import GET_EMOTE_SET from './emote_set_info.gql';
|
||||
import { FFZEvent } from 'src/utilities/events';
|
||||
|
||||
const HoverRAF = Symbol('FFZ:Hover:RAF');
|
||||
const HoverState = Symbol('FFZ:Hover:State');
|
||||
|
@ -32,7 +33,9 @@ export const MODIFIER_FLAGS = make_enum_flags(
|
|||
'Rainbow',
|
||||
'HyperRed',
|
||||
'Shake',
|
||||
'Photocopy'
|
||||
'Photocopy',
|
||||
'Jam',
|
||||
'Bounce'
|
||||
);
|
||||
|
||||
export const MODIFIER_KEYS = Object.values(MODIFIER_FLAGS).filter(x => typeof x === 'number');
|
||||
|
@ -48,19 +51,19 @@ const MODIFIER_FLAG_CSS = {
|
|||
},
|
||||
ShrinkX: {
|
||||
title: 'Squish Horizontal',
|
||||
transform: 'scaleX(0.5)'
|
||||
//transform: 'scaleX(0.5)'
|
||||
},
|
||||
GrowX: {
|
||||
title: 'Stretch Horizontal',
|
||||
transform: 'scaleX(2)'
|
||||
//transform: 'scaleX(2) translateX(25%)'
|
||||
},
|
||||
ShrinkY: {
|
||||
title: 'Squish Vertical',
|
||||
transform: 'scaleY(0.5)'
|
||||
//transform: 'scaleY(0.5)'
|
||||
},
|
||||
GrowY: {
|
||||
title: 'Stretch Vertical',
|
||||
transform: 'scaleY(2)'
|
||||
//transform: 'scaleY(2) translateY(25%)'
|
||||
},
|
||||
Rotate45: {
|
||||
title: 'Rotate 45 Degrees',
|
||||
|
@ -127,27 +130,89 @@ const MODIFIER_FLAG_CSS = {
|
|||
}`
|
||||
},
|
||||
Photocopy: {
|
||||
title: 'Photocopy',
|
||||
filter: 'grayscale(1) brightness(0.65) contrast(5)'
|
||||
title: 'Cursed',
|
||||
filter: 'grayscale(1) brightness(0.7) contrast(2.5)'
|
||||
},
|
||||
Jam: {
|
||||
title: 'Jam Animation',
|
||||
animation: 'ffz-effect-jam 0.6s linear infinite',
|
||||
animationTransform: 'ffz-effect-jam-transform 0.6s linear infinite',
|
||||
raw: `@keyframes ffz-effect-jam {
|
||||
0% { transform: translate(-2px, -2px) rotate(-6deg); }
|
||||
10% { transform: translate(-1.5px, -2px) rotate(-8deg); }
|
||||
20% { transform: translate(1px, -1.5px) rotate(-8deg); }
|
||||
30% { transform: translate(3px, 2.5px) rotate(-6deg); }
|
||||
40% { transform: translate(3px, 4px) rotate(-2deg); }
|
||||
50% { transform: translate(2px, 4px) rotate(3deg); }
|
||||
60% { transform: translate(1px, 4px) rotate(3deg); }
|
||||
70% { transform: translate(-0.5px, 3px) rotate(2deg); }
|
||||
80% { transform: translate(-1.25px, 1px) rotate(0deg); }
|
||||
90% { transform: translate(-1.75px, -0.5px) rotate(-2deg); }
|
||||
100% { transform: translate(-2px, -2px) rotate(-5deg); }
|
||||
}
|
||||
@keyframes ffz-effect-jam-transform {
|
||||
0% { transform: var(--ffz-effect-transforms) translate(-2px, -2px) rotate(-6deg); }
|
||||
10% { transform: var(--ffz-effect-transforms) translate(-1.5px, -2px) rotate(-8deg); }
|
||||
20% { transform: var(--ffz-effect-transforms) translate(1px, -1.5px) rotate(-8deg); }
|
||||
30% { transform: var(--ffz-effect-transforms) translate(3px, 2.5px) rotate(-6deg); }
|
||||
40% { transform: var(--ffz-effect-transforms) translate(3px, 4px) rotate(-2deg); }
|
||||
50% { transform: var(--ffz-effect-transforms) translate(2px, 4px) rotate(3deg); }
|
||||
60% { transform: var(--ffz-effect-transforms) translate(1px, 4px) rotate(3deg); }
|
||||
70% { transform: var(--ffz-effect-transforms) translate(-0.5px, 3px) rotate(2deg); }
|
||||
80% { transform: var(--ffz-effect-transforms) translate(-1.25px, 1px) rotate(0deg); }
|
||||
90% { transform: var(--ffz-effect-transforms) translate(-1.75px, -0.5px) rotate(-2deg); }
|
||||
100% { transform: var(--ffz-effect-transforms) translate(-2px, -2px) rotate(-5deg); }
|
||||
}`,
|
||||
},
|
||||
Bounce: {
|
||||
animation: 'ffz-effect-bounce 0.5s linear infinite',
|
||||
animationTransform: 'ffz-effect-bounce-transform 0.5s linear infinite',
|
||||
transformOrigin: 'bottom center',
|
||||
raw: `@keyframes ffz-effect-bounce {
|
||||
0% { transform: scale(0.8, 1); }
|
||||
10% { transform: scale(0.9, 0.8); }
|
||||
20% { transform: scale(1, 0.4); }
|
||||
25% { transform: scale(1.2, 0.3); }
|
||||
25.001% { transform: scale(-1.2, 0.3); }
|
||||
30% { transform: scale(-1, 0.4); }
|
||||
40% { transform: scale(-0.9, 0.8); }
|
||||
50% { transform: scale(-0.8, 1); }
|
||||
60% { transform: scale(-0.9, 0.8); }
|
||||
70% { transform: scale(-1, 0.4); }
|
||||
75% { transform: scale(-1.2, 0.3); }
|
||||
75.001% { transform: scale(1.2, 0.3); }
|
||||
80% { transform: scale(1, 0.4); }
|
||||
90% { transform: scale(0.9, 0.8); }
|
||||
100% { transform: scale(0.8, 1); }
|
||||
}
|
||||
@keyframes ffz-effect-bounce-transform {
|
||||
0% { transform: scale(0.8, 1) var(--ffz-effect-transforms); }
|
||||
10% { transform: scale(0.9, 0.8) var(--ffz-effect-transforms); }
|
||||
20% { transform: scale(1, 0.4) var(--ffz-effect-transforms); }
|
||||
25% { transform: scale(1.2, 0.3) var(--ffz-effect-transforms); }
|
||||
25.001% { transform: scale(-1.2, 0.3) var(--ffz-effect-transforms); }
|
||||
30% { transform: scale(-1, 0.4) var(--ffz-effect-transforms); }
|
||||
40% { transform: scale(-0.9, 0.8) var(--ffz-effect-transforms); }
|
||||
50% { transform: scale(-0.8, 1) var(--ffz-effect-transforms); }
|
||||
60% { transform: scale(-0.9, 0.8) var(--ffz-effect-transforms); }
|
||||
70% { transform: scale(-1, 0.4) var(--ffz-effect-transforms); }
|
||||
75% { transform: scale(-1.2, 0.3) var(--ffz-effect-transforms); }
|
||||
75.001% { transform: scale(1.2, 0.3) var(--ffz-effect-transforms); }
|
||||
80% { transform: scale(1, 0.4) var(--ffz-effect-transforms); }
|
||||
90% { transform: scale(0.9, 0.8) var(--ffz-effect-transforms); }
|
||||
100% { transform: scale(0.8, 1) var(--ffz-effect-transforms); }
|
||||
}`
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function generateBaseFilterCss() {
|
||||
console.log('flags', MODIFIER_FLAGS);
|
||||
console.log('css', MODIFIER_FLAG_CSS);
|
||||
|
||||
const out = [
|
||||
`.modified-emote[data-effects] > img {
|
||||
--ffz-effect-filters: none;
|
||||
--ffz-effect-transforms: initial;
|
||||
--ffz-effect-animations: initial;
|
||||
}`/*,
|
||||
`.modified-emote[data-effects] > img {
|
||||
filter: var(--ffz-effect-filters);
|
||||
transform: var(--ffz-effect-transforms);
|
||||
animation: var(--ffz-effect-animations);
|
||||
}`*/
|
||||
}`
|
||||
];
|
||||
|
||||
for(const [key, val] of Object.entries(MODIFIER_FLAG_CSS)) {
|
||||
|
@ -223,6 +288,7 @@ export default class Emotes extends Module {
|
|||
this.pending_effects = new Set();
|
||||
this.applyEffects = this.applyEffects.bind(this);
|
||||
|
||||
this.sub_sets = new SourcedSet;
|
||||
this.default_sets = new SourcedSet;
|
||||
this.global_sets = new SourcedSet;
|
||||
|
||||
|
@ -436,7 +502,7 @@ export default class Emotes extends Module {
|
|||
if ( ! this.parent.context.get('chat.effects.enable') )
|
||||
return null;
|
||||
|
||||
let filter, transform, animation, animations = [];
|
||||
let filter, transformOrigin, transform, animation, animations = [];
|
||||
|
||||
for(const key of MODIFIER_KEYS) {
|
||||
if ( (flags & key) !== key || ! this.effects_enabled[key] )
|
||||
|
@ -454,6 +520,9 @@ export default class Emotes extends Module {
|
|||
? `${filter} ${input.filter}`
|
||||
: input.filter;
|
||||
|
||||
if ( input.transformOrigin )
|
||||
transformOrigin = input.transformOrigin;
|
||||
|
||||
if ( input.transform )
|
||||
transform = transform
|
||||
? `${transform} ${input.transform}`
|
||||
|
@ -481,7 +550,8 @@ export default class Emotes extends Module {
|
|||
|
||||
return `.modified-emote[data-effects="${flags}"] > img {${filter ? `
|
||||
--ffz-effect-filters: ${filter};
|
||||
filter: var(--ffz-effect-filters);` : ''}${transform ? `
|
||||
filter: var(--ffz-effect-filters);` : ''}${transformOrigin ? `
|
||||
transform-origin: ${transformOrigin};` : ''}${transform ? `
|
||||
--ffz-effect-transforms: ${transform};
|
||||
transform: var(--ffz-effect-transforms);` : ''}${animation ? `
|
||||
--ffz-effect-animations: ${animation};
|
||||
|
@ -694,7 +764,6 @@ export default class Emotes extends Module {
|
|||
this.settings.provider.set(key, favs);
|
||||
}
|
||||
|
||||
|
||||
handleClick(event) {
|
||||
const target = event.target,
|
||||
ds = target && target.dataset;
|
||||
|
@ -710,7 +779,7 @@ export default class Emotes extends Module {
|
|||
let url;
|
||||
|
||||
if ( provider === 'twitch' ) {
|
||||
url = `https://twitchemotes.com/emotes/${ds.id}`;
|
||||
url = null; // = `https://twitchemotes.com/emotes/${ds.id}`;
|
||||
|
||||
if ( click_sub ) {
|
||||
const apollo = this.resolve('site.apollo');
|
||||
|
@ -803,6 +872,16 @@ export default class Emotes extends Module {
|
|||
return true;
|
||||
}
|
||||
|
||||
const evt = new FFZEvent({
|
||||
provider,
|
||||
id: ds.id,
|
||||
source: event
|
||||
});
|
||||
|
||||
this.emit('chat.emotes:click', evt);
|
||||
if ( evt.defaultPrevented )
|
||||
return true;
|
||||
|
||||
if ( provider === 'twitch' && this.parent.context.get('chat.emote-dialogs') ) {
|
||||
const fine = this.resolve('site.fine');
|
||||
if ( ! fine )
|
||||
|
@ -836,6 +915,55 @@ export default class Emotes extends Module {
|
|||
// Access
|
||||
// ========================================================================
|
||||
|
||||
getTargetEmote() {
|
||||
const me = this.resolve('site').getUser(),
|
||||
Input = me ? this.resolve('site.chat.input') : null,
|
||||
entered = Input ? Input.getInput() : null;
|
||||
|
||||
const menu = this.resolve('site.chat.emote_menu')?.MenuWrapper?.first,
|
||||
emote_sets = menu?.getAllSets?.(),
|
||||
emotes = emote_sets
|
||||
? emote_sets.map(x => x.emotes).flat().filter(x => ! x.effects)
|
||||
: null;
|
||||
|
||||
if ( entered && emotes ) {
|
||||
// Okay this is gonna be oof.
|
||||
const name_map = {};
|
||||
for(let i = 0; i < emotes.length; i++)
|
||||
if ( ! name_map[emotes[i].name] )
|
||||
name_map[emotes[i].name] = i;
|
||||
|
||||
const words = entered.split(' ');
|
||||
let i = words.length;
|
||||
while(i--) {
|
||||
const word = words[i];
|
||||
if ( name_map[word] != null )
|
||||
return emotes[name_map[word]];
|
||||
}
|
||||
}
|
||||
|
||||
// Random emote
|
||||
if ( emotes && emotes.length ) {
|
||||
const idx = Math.floor(Math.random() * emotes.length),
|
||||
emote = emotes[idx];
|
||||
|
||||
return emote;
|
||||
}
|
||||
|
||||
// Return LaterSooner
|
||||
return {
|
||||
provider: 'ffz',
|
||||
set_id: 3,
|
||||
id: 149346,
|
||||
name: 'LaterSooner',
|
||||
src: 'https://cdn.frankerfacez.com/emote/149346/1',
|
||||
srcSet: 'https://cdn.frankerfacez.com/emote/149346/1 1x, https://cdn.frankerfacez.com/emote/149346/2 2x, https://cdn.frankerfacez.com/emote/149346/4 4x',
|
||||
width: 25,
|
||||
height: 32
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getSetIDs(user_id, user_login, room_id, room_login) {
|
||||
const room = this.parent.getRoom(room_id, room_login, true),
|
||||
room_user = room && room.getUser(user_id, user_login, true),
|
||||
|
@ -949,6 +1077,21 @@ export default class Emotes extends Module {
|
|||
.map(([set_id, source]) => [this.emote_sets[set_id], source]);
|
||||
}
|
||||
|
||||
|
||||
getSubSetIDsWithSources() {
|
||||
const out = [], seen = new Set;
|
||||
|
||||
this._withSources(out, seen, this.sub_sets);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
getSubSetsWithSources() {
|
||||
return this.getSubSetIDsWithSources()
|
||||
.map(([set_id, source]) => [this.emote_sets[set_id], source]);
|
||||
}
|
||||
|
||||
|
||||
getGlobalSetIDs(user_id, user_login) {
|
||||
const user = this.parent.getUser(user_id, user_login, true);
|
||||
|
||||
|
@ -1073,6 +1216,45 @@ export default class Emotes extends Module {
|
|||
return false;
|
||||
}
|
||||
|
||||
addSubSet(provider, set_id, data) {
|
||||
if ( typeof set_id === 'number' )
|
||||
set_id = `${set_id}`;
|
||||
|
||||
let changed = false, added = false;
|
||||
if ( ! this.sub_sets.sourceIncludes(provider, set_id) ) {
|
||||
changed = ! this.sub_sets.includes(set_id);
|
||||
this.sub_sets.push(provider, set_id);
|
||||
added = true;
|
||||
}
|
||||
|
||||
if ( data )
|
||||
this.loadSetData(set_id, data);
|
||||
|
||||
if ( changed ) {
|
||||
this.refSet(set_id);
|
||||
this.emit(':update-sub-sets', provider, set_id, true);
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
removeSubSet(provider, set_id) {
|
||||
if ( typeof set_id === 'number' )
|
||||
set_id = `${set_id}`;
|
||||
|
||||
if ( this.sub_sets.sourceIncludes(provider, set_id) ) {
|
||||
this.sub_sets.remove(provider, set_id);
|
||||
if ( ! this.sub_sets.includes(set_id) ) {
|
||||
this.unrefSet(set_id);
|
||||
this.emit(':update-sub-sets', provider, set_id, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
refSet(set_id) {
|
||||
this._set_refs[set_id] = (this._set_refs[set_id] || 0) + 1;
|
||||
if ( this._set_timers[set_id] ) {
|
||||
|
@ -1101,7 +1283,7 @@ export default class Emotes extends Module {
|
|||
} catch(err) { /* do nothing */ }
|
||||
|
||||
try {
|
||||
response = await fetch(`${this.staging.api}/v1/set/global${this.staging.active ? '/ids' : ''}`)
|
||||
response = await fetch(`${this.staging.api}/v1/set/global/ids`)
|
||||
} catch(err) {
|
||||
tries++;
|
||||
if ( tries < 10 )
|
||||
|
@ -1127,8 +1309,12 @@ export default class Emotes extends Module {
|
|||
this.addDefaultSet('ffz-global', set_id);
|
||||
|
||||
for(const set_id in sets)
|
||||
if ( has(sets, set_id) )
|
||||
if ( has(sets, set_id) ) {
|
||||
const id = sets[set_id]?.id;
|
||||
this.loadSetData(set_id, sets[set_id]);
|
||||
if ( id && ! data.default_sets.includes(id) )
|
||||
this.addSubSet('ffz-global', set_id);
|
||||
}
|
||||
|
||||
if ( data.user_ids )
|
||||
this.loadSetUserIds(data.user_ids);
|
||||
|
@ -1262,6 +1448,7 @@ export default class Emotes extends Module {
|
|||
text: emote.hidden ? '???' : emote.name,
|
||||
length: emote.name.length,
|
||||
height: emote.height,
|
||||
width: emote.width,
|
||||
source_modifier_flags: emote.modifier_flags ?? 0
|
||||
};
|
||||
|
||||
|
|
|
@ -10,6 +10,15 @@ import {has, getTwitchEmoteURL, split_chars, getTwitchEmoteSrcSet} from 'utiliti
|
|||
import {EmoteTypes, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants';
|
||||
import {CATEGORIES, JOINER_REPLACEMENT} from './emoji';
|
||||
|
||||
import { MODIFIER_FLAGS } from './emotes';
|
||||
|
||||
const SHRINK_X = MODIFIER_FLAGS.ShrinkX,
|
||||
STRETCH_X = MODIFIER_FLAGS.GrowX,
|
||||
SHRINK_Y = MODIFIER_FLAGS.ShrinkY,
|
||||
STRETCH_Y = MODIFIER_FLAGS.GrowY,
|
||||
ROTATE_45 = MODIFIER_FLAGS.Rotate45,
|
||||
ROTATE_90 = MODIFIER_FLAGS.Rotate90;
|
||||
|
||||
|
||||
const EMOTE_CLASS = 'chat-image chat-line__message--emote',
|
||||
//WHITESPACE = /^\s*$/,
|
||||
|
@ -1213,12 +1222,45 @@ export const AddonEmotes = {
|
|||
hoverSrcSet = big ? token.animSrcSet2 : token.animSrcSet;
|
||||
}
|
||||
|
||||
let style = undefined;
|
||||
const effects = token.modifier_flags,
|
||||
is_big = (token.big && ! token.can_big && token.height);
|
||||
|
||||
if ( effects ) {
|
||||
this.emotes.ensureEffect(effects);
|
||||
style = {
|
||||
width: is_big ? token.width * 2 : token.width,
|
||||
height: is_big ? token.height * 2 : token.height
|
||||
}
|
||||
|
||||
if ( (effects & SHRINK_X) === SHRINK_X )
|
||||
style.width *= 0.5;
|
||||
if ( (effects & STRETCH_X) === STRETCH_X )
|
||||
style.width *= 2;
|
||||
if ( (effects & SHRINK_Y) === SHRINK_Y )
|
||||
style.height *= 0.5;
|
||||
if ( (effects & STRETCH_Y) === STRETCH_Y )
|
||||
style.height *= 2;
|
||||
|
||||
if ( (effects & ROTATE_90) === ROTATE_90 ) {
|
||||
const w = style.width;
|
||||
style.width = style.height;
|
||||
style.height = w;
|
||||
}
|
||||
|
||||
if ( style.width > 128 )
|
||||
style.width = 128;
|
||||
if ( style.height > 40 )
|
||||
style.height = 40;
|
||||
}
|
||||
|
||||
const mods = token.modifiers || [], ml = mods.length,
|
||||
emote = (<img
|
||||
class={`${EMOTE_CLASS} ffz--pointer-events ffz-tooltip${hoverSrc ? ' ffz-hover-emote' : ''}${token.provider === 'twitch' ? ' twitch-emote' : token.provider === 'ffz' ? ' ffz-emote' : token.provider === 'emoji' ? ' ffz-emoji' : ''}`}
|
||||
src={src}
|
||||
srcSet={srcSet}
|
||||
height={(token.big && ! token.can_big && token.height) ? `${token.height * 2}px` : undefined}
|
||||
style={style}
|
||||
height={style ? undefined : is_big ? `${token.height * 2}px` : undefined}
|
||||
alt={token.text}
|
||||
data-tooltip-type="emote"
|
||||
data-provider={token.provider}
|
||||
|
@ -1235,23 +1277,20 @@ export const AddonEmotes = {
|
|||
onClick={this.emotes.handleClick}
|
||||
/>);
|
||||
|
||||
if ( ! ml ) {
|
||||
if ( ! ml && ! token.modifier_flags ) {
|
||||
if ( wrapped )
|
||||
return emote;
|
||||
|
||||
return (<div class="ffz--inline" data-test-selector="emote-button">{emote}</div>);
|
||||
}
|
||||
|
||||
const effects = token.modifier_flags;
|
||||
if ( effects )
|
||||
this.emotes.ensureEffect(effects);
|
||||
|
||||
return (<div
|
||||
class="ffz--inline ffz--pointer-events modified-emote"
|
||||
class={`ffz--inline ffz--pointer-events modified-emote${style ? ' scaled-modified-emote' : ''}`}
|
||||
data-test-selector="emote-button"
|
||||
data-provider={token.provider}
|
||||
data-id={token.id}
|
||||
data-set={token.set}
|
||||
style={style}
|
||||
data-modifiers={ml ? mods.map(x => x.id).join(' ') : null}
|
||||
data-effects={effects ? effects : undefined}
|
||||
onClick={this.emotes.handleClick}
|
||||
|
@ -1369,6 +1408,76 @@ export const AddonEmotes = {
|
|||
else if ( emote.urls[2] )
|
||||
preview = emote.urls[2];
|
||||
}
|
||||
|
||||
if ( ds.effects && emote.modifier && emote.modifier_flags ) {
|
||||
owner = null;
|
||||
|
||||
const effects = emote.modifier_flags;
|
||||
this.emotes.ensureEffect(effects);
|
||||
|
||||
const target = this.emotes.getTargetEmote();
|
||||
|
||||
let style = {
|
||||
width: (target.width ?? 28) * 2,
|
||||
height: (target.height ?? 28) * 2
|
||||
};
|
||||
|
||||
let changed = false;
|
||||
|
||||
if ( (effects & SHRINK_X) === SHRINK_X ) {
|
||||
style.width *= 0.5;
|
||||
changed = true;
|
||||
}
|
||||
if ( (effects & STRETCH_X) === STRETCH_X ) {
|
||||
style.width *= 2;
|
||||
changed = true;
|
||||
}
|
||||
if ( (effects & SHRINK_Y) === SHRINK_Y ) {
|
||||
style.height *= 0.5;
|
||||
changed = true;
|
||||
}
|
||||
if ( (effects & STRETCH_Y) === STRETCH_Y ) {
|
||||
style.height *= 2;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( changed ) {
|
||||
if ( style.width > 512 )
|
||||
style.width = 512;
|
||||
if ( style.height > 160 )
|
||||
style.height = 160;
|
||||
}
|
||||
|
||||
style.width = `${style.width}px`;
|
||||
style.height = `${style.height}px`;
|
||||
|
||||
// Whip up a special preview.
|
||||
preview = (<div class="ffz-effect-tip">
|
||||
<img
|
||||
src={target.src}
|
||||
srcSet={target.srcSet}
|
||||
width={(target.width ?? 28) * 2}
|
||||
height={(target.height ?? 28) * 2}
|
||||
onLoad={tip.update}
|
||||
/>
|
||||
<span class="ffz-i-right-open"></span>
|
||||
<div
|
||||
class={`ffz--inline ffz--pointer-events modified-emote${style ? ' scaled-modified-emote' : ''}`}
|
||||
style={style}
|
||||
data-modifiers={emote.id}
|
||||
data-effects={effects}
|
||||
>
|
||||
<img
|
||||
class={`${EMOTE_CLASS} ffz--pointer-events ffz-tooltip ffz-emote`}
|
||||
src={target.src}
|
||||
srcSet={target.srcSet}
|
||||
style={style}
|
||||
height={style ? undefined : `${target.height * 2}px`}
|
||||
onLoad={tip.update}
|
||||
/>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
} else if ( provider === 'emoji' ) {
|
||||
|
@ -1750,6 +1859,7 @@ export const TwitchEmotes = {
|
|||
anim,
|
||||
big,
|
||||
can_big,
|
||||
width: 28,
|
||||
height: 28, // Not always accurate but close enough.
|
||||
text: text.slice(e_start - t_start, e_end - t_start).join(''),
|
||||
modifiers: [],
|
||||
|
|
|
@ -13,7 +13,7 @@ Due to performance problems with our current website, we have to use caching on
|
|||
|
||||
* *I don't want the `FFZ Supporter` badge.*
|
||||
|
||||
Users can toggle the visibility of their supporter badge at: [https://www.frankerfacez.com/donate](https://www.frankerfacez.com/donate)
|
||||
Users can toggle the visibility of their FFZ badges at: [https://www.frankerfacez.com/settings/profile](https://www.frankerfacez.com/settings/profile)
|
||||
|
||||
* *I can see my emotes, but someone in chat said they can't.*
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import SettingsManager from './settings/index';
|
|||
import AddonManager from './addons';
|
||||
import ExperimentManager from './experiments';
|
||||
import {TranslationManager} from './i18n';
|
||||
import StagingSelector from './staging';
|
||||
import Site from './sites/player';
|
||||
|
||||
class FrankerFaceZ extends Module {
|
||||
|
@ -49,6 +50,7 @@ class FrankerFaceZ extends Module {
|
|||
this.inject('settings', SettingsManager);
|
||||
this.inject('experiments', ExperimentManager);
|
||||
this.inject('i18n', TranslationManager);
|
||||
this.inject('staging', StagingSelector);
|
||||
this.inject('site', Site);
|
||||
this.inject('addons', AddonManager);
|
||||
|
||||
|
|
|
@ -1443,6 +1443,47 @@ export default class PlayerBase extends Module {
|
|||
tip.textContent = label;
|
||||
}
|
||||
|
||||
replaceVideoElement(player, video) {
|
||||
const new_vid = createElement('video'),
|
||||
vol = video?._ffz_pregain_volume ?? video?.volume ?? player.getVolume(),
|
||||
muted = player.isMuted();
|
||||
|
||||
new_vid._ffz_gain_value = video._ffz_gain_value;
|
||||
new_vid._ffz_state = video._ffz_state;
|
||||
new_vid._ffz_toggled = video._ffz_toggled;
|
||||
new_vid._ffz_maybe_compress = video._ffz_compressed;
|
||||
new_vid.volume = vol;
|
||||
if ( muted )
|
||||
new_vid.muted = true;
|
||||
new_vid.playsInline = true;
|
||||
|
||||
this.installPlaybackRate(new_vid);
|
||||
video.replaceWith(new_vid);
|
||||
player.attachHTMLVideoElement(new_vid);
|
||||
return new_vid;
|
||||
}
|
||||
|
||||
hookPlayerLoad(player) {
|
||||
if ( ! player || player._ffz_load )
|
||||
return;
|
||||
|
||||
player._ffz_load = player.load;
|
||||
|
||||
player.load = (...args) => {
|
||||
try {
|
||||
const video = player.getHTMLVideoElement();
|
||||
if ( video?._ffz_compressor && player.attachHTMLVideoElement ) {
|
||||
this.log.info('Recreating video element due to player load with compressor installed.');
|
||||
this.replaceVideoElement(player, video);
|
||||
}
|
||||
} catch(err) {
|
||||
t.log.error('Error while handling player load.', err);
|
||||
}
|
||||
|
||||
return player._ffz_load(...args);
|
||||
}
|
||||
}
|
||||
|
||||
compressPlayer(inst, e) {
|
||||
const player = inst.props.mediaPlayerInstance,
|
||||
core = player.core || player,
|
||||
|
@ -1451,6 +1492,21 @@ export default class PlayerBase extends Module {
|
|||
if ( ! video || ! HAS_COMPRESSOR )
|
||||
return;
|
||||
|
||||
// Backup the player load method.
|
||||
this.hookPlayerLoad(player);
|
||||
|
||||
// Backup and replace the setSrc method.
|
||||
if ( ! inst._ffz_setSrc ) {
|
||||
inst._ffz_setSrc = inst.setSrc;
|
||||
inst.setSrc = async function(...args) {
|
||||
console.log('setSrc', args);
|
||||
const vid = inst.props.mediaPlayerInstance?.core?.mediaSinkManager?.video;
|
||||
if ( vid && vid._ffz_compressor )
|
||||
await this.resetPlayer(inst);
|
||||
return inst._ffz_setSrc(...args);
|
||||
}
|
||||
}
|
||||
|
||||
// Backup the setVolume method.
|
||||
if ( ! core._ffz_setVolume ) {
|
||||
core._ffz_setVolume = core.setVolume;
|
||||
|
@ -2028,7 +2084,7 @@ export default class PlayerBase extends Module {
|
|||
}
|
||||
|
||||
|
||||
resetPlayer(inst, e) {
|
||||
async resetPlayer(inst, e) {
|
||||
const player = inst ? ((inst.mediaSinkManager || inst.core?.mediaSinkManager) ? inst : inst?.props?.mediaPlayerInstance) : null;
|
||||
|
||||
if ( e ) {
|
||||
|
@ -2064,40 +2120,20 @@ export default class PlayerBase extends Module {
|
|||
|
||||
const video = player.mediaSinkManager?.video || player.core?.mediaSinkManager?.video;
|
||||
if ( video?._ffz_compressor && player.attachHTMLVideoElement ) {
|
||||
const new_vid = createElement('video'),
|
||||
vol = video?._ffz_pregain_volume ?? video?.volume ?? player.getVolume(),
|
||||
muted = player.isMuted();
|
||||
|
||||
new_vid._ffz_gain_value = video._ffz_gain_value;
|
||||
new_vid._ffz_state = video._ffz_state;
|
||||
new_vid._ffz_toggled = video._ffz_toggled;
|
||||
const new_vid = this.replaceVideoElement(player, video);
|
||||
new_vid._ffz_maybe_compress = true;
|
||||
new_vid.volume = muted ? 0 : vol;
|
||||
new_vid.playsInline = true;
|
||||
|
||||
this.installPlaybackRate(new_vid);
|
||||
video.replaceWith(new_vid);
|
||||
player.attachHTMLVideoElement(new_vid);
|
||||
setTimeout(() => {
|
||||
player.setVolume(vol);
|
||||
player.setMuted(muted);
|
||||
|
||||
//localStorage.volume = vol;
|
||||
//localStorage.setItem('video-muted', JSON.stringify({default: muted}));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
this.PlayerSource.check();
|
||||
for(const inst of this.PlayerSource.instances) {
|
||||
if ( ! player || player === inst.props?.mediaPlayerInstance )
|
||||
inst.setSrc({isNewMediaPlayerInstance: false});
|
||||
await inst.setSrc({isNewMediaPlayerInstance: false});
|
||||
}
|
||||
|
||||
if ( position > 0 )
|
||||
setTimeout(() => player.seekTo(position), 250);
|
||||
}
|
||||
|
||||
|
||||
addMetadata(inst) {
|
||||
if ( ! this.metadata )
|
||||
return;
|
||||
|
|
|
@ -150,6 +150,7 @@ export default class EmoteMenu extends Module {
|
|||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.inject('staging');
|
||||
this.inject('settings');
|
||||
this.inject('i18n');
|
||||
this.inject('chat');
|
||||
|
@ -793,7 +794,7 @@ export default class EmoteMenu extends Module {
|
|||
|
||||
let source = data.source_i18n ? t.i18n.t(data.source_i18n, data.source) : data.source;
|
||||
if ( source == null )
|
||||
source = 'FrankerFaceZ';
|
||||
source = 'FFZ';
|
||||
|
||||
return (<section ref={this.saveRef} data-key={data.key} class={filtered ? 'filtered' : ''} onMouseEnter={this.mouseEnter}>
|
||||
{show_heading ? (<heading tabindex="0" class="tw-pd-1 tw-border-b tw-flex tw-flex-nowrap" onKeyDown={this.keyHeading} onClick={this.clickHeading}>
|
||||
|
@ -833,7 +834,9 @@ export default class EmoteMenu extends Module {
|
|||
let sellout = '';
|
||||
|
||||
if ( emote_lock ) {
|
||||
if ( emote_lock.id === 'cheer' ) {
|
||||
if ( emote_lock.id === 'subwoofer' ) {
|
||||
sellout = t.i18n.t('emote-menu.emote-subwoofer', 'Become an FFZ Subwoofer to unlock this emote.');
|
||||
} else if ( emote_lock.id === 'cheer' ) {
|
||||
sellout = t.i18n.t('emote-menu.emote-cheer', 'Cheer an additional {bits_remaining, plural, one {# bit} other {# bits}} to unlock this emote.', emote_lock);
|
||||
} else if ( emote_lock.id === 'follower' ) {
|
||||
sellout = t.i18n.t('emote-menu.emote-follower', 'Follow {user} to unlock this emote in their channel.', emote_lock);
|
||||
|
@ -886,6 +889,7 @@ export default class EmoteMenu extends Module {
|
|||
data-set={emote.set_id}
|
||||
data-code={emote.code}
|
||||
data-modifiers={modifiers}
|
||||
data-effects={emote.effects}
|
||||
data-variant={emote.variant}
|
||||
data-no-source={source}
|
||||
data-name={emote.name}
|
||||
|
@ -917,16 +921,27 @@ export default class EmoteMenu extends Module {
|
|||
if ( ! data.all_locked || ! data.locks )
|
||||
return null;
|
||||
|
||||
const lock = data.locks[this.state.unlocked],
|
||||
locks = Object.values(data.locks).filter(x => x.id !== 'cheer');
|
||||
let lock = data.locks[this.state.unlocked],
|
||||
locks = Object.values(data.locks).filter(x => x.id !== 'cheer'),
|
||||
has_ffz = locks.filter(x => x.is_ffz).length > 0;
|
||||
|
||||
if ( ! lock && data.locks.length === 1 )
|
||||
lock = data.locks[0];
|
||||
|
||||
if ( ! locks.length )
|
||||
return null;
|
||||
|
||||
return (<div class="tw-mg-1 tw-border-t tw-pd-t-1 tw-mg-b-0">
|
||||
{lock ?
|
||||
t.i18n.t('emote-menu.sub-unlock', 'Subscribe for {price} to unlock {count, plural, one {# emote} other {# emotes}}', {price: lock.price, count: lock.emotes.size}) :
|
||||
t.i18n.t('emote-menu.sub-basic', 'Subscribe to unlock some emotes')}
|
||||
{has_ffz
|
||||
? t.i18n.t('emote-menu.ffz-unlock', 'This feature is available to FFZ Subwoofers.')
|
||||
: (lock
|
||||
? t.i18n.t('emote-menu.sub-unlock', 'Subscribe for {price} to unlock {count, plural, one {# emote} other {# emotes}}', {price: lock.price, count: lock.emotes.size})
|
||||
: t.i18n.t('emote-menu.sub-basic', 'Subscribe to unlock some emotes')
|
||||
)
|
||||
}
|
||||
{has_ffz && this.props.ffz_sub_data?.has_free_sub
|
||||
? <div class="tw-pd-y-1">{t.i18n.t('emote-menu.free-sub.about', 'As thanks for supporting us in the past, you can get one month of FFZ Subwoofer for free.')}</div>
|
||||
: null}
|
||||
<div class="ffz--sub-buttons tw-mg-t-05">
|
||||
{locks.map(lock => lock.hide_button ? null : (<a
|
||||
key={lock.price}
|
||||
|
@ -939,7 +954,10 @@ export default class EmoteMenu extends Module {
|
|||
onMouseLeave={this.onMouseLeave}
|
||||
>
|
||||
<span class="tw-button__text">
|
||||
{lock.price}
|
||||
{has_ffz && this.props.ffz_sub_data?.has_free_sub
|
||||
? t.i18n.t('emote-menu.free-sub', 'Use My Free Month')
|
||||
: lock.price
|
||||
}
|
||||
</span>
|
||||
</a>))}
|
||||
</div>
|
||||
|
@ -1092,8 +1110,13 @@ export default class EmoteMenu extends Module {
|
|||
tone: t.settings.provider.get('emoji-tone', null)
|
||||
}
|
||||
|
||||
if ( props.visible )
|
||||
if ( props.visible ) {
|
||||
this.loadData();
|
||||
if ( this.state.wants_plan_info )
|
||||
this.loadFFZPlanData();
|
||||
if ( this.state.wants_resub_info )
|
||||
this.loadFFZSubData();
|
||||
}
|
||||
|
||||
this.rebuildData();
|
||||
|
||||
|
@ -1310,6 +1333,9 @@ export default class EmoteMenu extends Module {
|
|||
case 'channel':
|
||||
sets = this.state.filtered_channel_sets;
|
||||
break;
|
||||
case 'effects':
|
||||
sets = this.state.filtered_effect_sets;
|
||||
break;
|
||||
case 'emoji':
|
||||
sets = this.state.filtered_emoji_sets;
|
||||
break;
|
||||
|
@ -1437,6 +1463,46 @@ export default class EmoteMenu extends Module {
|
|||
return true;
|
||||
}
|
||||
|
||||
loadFFZPlanData(force = false, props, state) {
|
||||
state = state || this.state;
|
||||
if ( ! state || state.ffz_plan_loading )
|
||||
return false;
|
||||
|
||||
if ( state.ffz_sub_data && ! force )
|
||||
return false;
|
||||
|
||||
this.setState({ffz_plan_loading: true}, () => {
|
||||
t.getFFZSubPrices().then(d => {
|
||||
this.setState(this.filterState(this.state.filter, this.buildState(
|
||||
this.props,
|
||||
Object.assign({}, this.state, {ffz_plan_data: d, ffz_plan_loading: false})
|
||||
)));
|
||||
})
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
loadFFZSubData(force = false, props, state) {
|
||||
state = state || this.state;
|
||||
if ( ! state || state.ffz_loading )
|
||||
return false;
|
||||
|
||||
if ( state.ffz_sub_data && ! force )
|
||||
return false;
|
||||
|
||||
this.setState({ffz_loading: true}, () => {
|
||||
t.getFFZSubData().then(d => {
|
||||
this.setState(this.filterState(this.state.filter, this.buildState(
|
||||
this.props,
|
||||
Object.assign({}, this.state, {ffz_sub_data: d, ffz_loading: false})
|
||||
)));
|
||||
})
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
filterState(input, old_state, visibility_control) {
|
||||
const state = Object.assign({}, old_state);
|
||||
|
||||
|
@ -1449,6 +1515,7 @@ export default class EmoteMenu extends Module {
|
|||
state.filtered = input && input.length > 0 && input !== ':' || false;
|
||||
|
||||
state.filtered_channel_sets = this.filterSets(input, state.channel_sets, visibility_control);
|
||||
state.filtered_effect_sets = this.filterSets(input, state.effect_sets, visibility_control);
|
||||
state.filtered_all_sets = this.filterSets(input, state.all_sets, visibility_control);
|
||||
state.filtered_fav_sets = this.filterSets(input, state.fav_sets, visibility_control);
|
||||
state.filtered_emoji_sets = this.filterSets(input, state.emoji_sets, visibility_control);
|
||||
|
@ -1619,6 +1686,13 @@ export default class EmoteMenu extends Module {
|
|||
return state;
|
||||
}
|
||||
|
||||
getAllSets() {
|
||||
return [
|
||||
...(this.state.channel_sets || []),
|
||||
...(this.state.effect_sets || []),
|
||||
...(this.state.all_sets || [])
|
||||
];
|
||||
}
|
||||
|
||||
getSorter() { // eslint-disable-line class-methods-use-this
|
||||
return EMOTE_SORTERS[t.chat.context.get('chat.emote-menu.sort-emotes')] || EMOTE_SORTERS[0] || (() => 0);
|
||||
|
@ -1631,6 +1705,7 @@ export default class EmoteMenu extends Module {
|
|||
modifiers = state.emote_modifiers = {},
|
||||
channel = state.channel_sets = [],
|
||||
all = state.all_sets = [],
|
||||
effects = state.effect_sets = [],
|
||||
favorites = state.favorites = [];
|
||||
|
||||
// If we're still loading, don't set any data.
|
||||
|
@ -1987,8 +2062,8 @@ export default class EmoteMenu extends Module {
|
|||
hide_button: true,
|
||||
emotes: lock_set = new Set()
|
||||
}
|
||||
else
|
||||
section.all_locked = false;
|
||||
/*else
|
||||
section.all_locked = false;*/
|
||||
|
||||
let order = 0;
|
||||
for(const emote of local.emotes) {
|
||||
|
@ -2178,56 +2253,106 @@ export default class EmoteMenu extends Module {
|
|||
}
|
||||
}
|
||||
|
||||
let wants_resub_info = false,
|
||||
wants_plan_info = false;
|
||||
|
||||
// Finally, emotes added by FrankerFaceZ.
|
||||
if ( t.chat.context.get('chat.emotes.enabled') > 1 ) {
|
||||
const me = t.site.getUser();
|
||||
if ( me ) {
|
||||
const ffz_room = t.emotes.getRoomSetsWithSources(me.id, me.login, props.channel_id, null),
|
||||
ffz_global = t.emotes.getGlobalSetsWithSources(me.id, me.login),
|
||||
seen_favorites = {};
|
||||
|
||||
let grouped_sets = {};
|
||||
const ffz_room = t.emotes.getRoomSetsWithSources(me?.id, me?.login, props.channel_id, null),
|
||||
ffz_subs = t.emotes.getSubSetsWithSources(),
|
||||
ffz_global = t.emotes.getGlobalSetsWithSources(me?.id, me?.login),
|
||||
seen_sets = new Set(),
|
||||
seen_favorites = {};
|
||||
|
||||
for(const [emote_set, provider] of ffz_room) {
|
||||
const section = this.processFFZSet(emote_set, provider, favorites, seen_favorites, grouped_sets);
|
||||
if ( section ) {
|
||||
section.emotes.sort(sort_emotes);
|
||||
let grouped_sets = {};
|
||||
|
||||
if ( ! channel.includes(section) )
|
||||
channel.push(section);
|
||||
}
|
||||
for(const [emote_set, provider] of ffz_room) {
|
||||
if ( seen_sets.has(emote_set) )
|
||||
continue;
|
||||
seen_sets.add(emote_set);
|
||||
|
||||
const section = this.processFFZSet(emote_set, provider, favorites, seen_favorites, grouped_sets);
|
||||
if ( section ) {
|
||||
section.emotes.sort(sort_emotes);
|
||||
|
||||
if ( ! channel.includes(section) )
|
||||
channel.push(section);
|
||||
}
|
||||
}
|
||||
|
||||
grouped_sets = {};
|
||||
grouped_sets = {};
|
||||
|
||||
for(const [emote_set, provider] of ffz_global) {
|
||||
const section = this.processFFZSet(emote_set, provider, favorites, seen_favorites, grouped_sets);
|
||||
if ( section ) {
|
||||
section.emotes.sort(sort_emotes);
|
||||
const global_set_ids = ffz_global.map(x => x?.[0]?.id);
|
||||
|
||||
if ( ! all.includes(section) )
|
||||
all.push(section);
|
||||
for(const [emote_set, provider] of ffz_subs) {
|
||||
if ( seen_sets.has(emote_set) )
|
||||
continue;
|
||||
seen_sets.add(emote_set);
|
||||
|
||||
if ( ! channel.includes(section) && maybe_call(section.force_global, this, emote_set, props.channel_data && props.channel_data.user, me) )
|
||||
channel.push(section);
|
||||
}
|
||||
const locked = ! global_set_ids.includes(emote_set.id);
|
||||
|
||||
wants_resub_info = true;
|
||||
|
||||
const section = this.processFFZSet(emote_set, provider, favorites, seen_favorites, grouped_sets, locked, state);
|
||||
if ( section ) {
|
||||
section.emotes.sort(sort_emotes);
|
||||
|
||||
if ( ! effects.includes(section) && section.has_effects )
|
||||
effects.push(section);
|
||||
else if ( ! all.includes(section) )
|
||||
all.push(section);
|
||||
}
|
||||
}
|
||||
|
||||
grouped_sets = {};
|
||||
|
||||
for(const [emote_set, provider] of ffz_global) {
|
||||
if ( seen_sets.has(emote_set) )
|
||||
continue;
|
||||
seen_sets.add(emote_set);
|
||||
|
||||
const section = this.processFFZSet(emote_set, provider, favorites, seen_favorites, grouped_sets);
|
||||
if ( section ) {
|
||||
section.emotes.sort(sort_emotes);
|
||||
|
||||
if ( ! effects.includes(section) && section.has_effects )
|
||||
effects.push(section);
|
||||
|
||||
else if ( ! all.includes(section) )
|
||||
all.push(section);
|
||||
|
||||
if ( ! channel.includes(section) && maybe_call(section.force_global, this, emote_set, props.channel_data && props.channel_data.user, me) )
|
||||
channel.push(section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load FFZ sub data.
|
||||
state.wants_resub_info = wants_resub_info;
|
||||
state.wants_plan_info = wants_plan_info;
|
||||
|
||||
if ( this.props.visible ) {
|
||||
if ( wants_plan_info )
|
||||
this.loadFFZPlanData();
|
||||
if ( wants_resub_info )
|
||||
this.loadFFZSubData();
|
||||
}
|
||||
|
||||
// Sort Sets
|
||||
channel.sort(sort_sets);
|
||||
effects.sort(sort_sets);
|
||||
all.sort(sort_sets);
|
||||
|
||||
state.has_channel_tab = channel.length > 0;
|
||||
state.has_effect_Tab = effects.length > 0;
|
||||
|
||||
return this.buildEmoji(state);
|
||||
}
|
||||
|
||||
|
||||
processFFZSet(emote_set, provider, favorites, seen_favorites, grouped_sets) { // eslint-disable-line class-methods-use-this
|
||||
processFFZSet(emote_set, provider, favorites, seen_favorites, grouped_sets, locked = false, state) { // eslint-disable-line class-methods-use-this
|
||||
if ( ! emote_set || ! emote_set.emotes )
|
||||
return null;
|
||||
|
||||
|
@ -2242,7 +2367,7 @@ export default class EmoteMenu extends Module {
|
|||
(pdata.i18n_key ?
|
||||
t.i18n.t(pdata.i18n_key, pdata.name, pdata) :
|
||||
pdata.name) :
|
||||
emote_set.source || 'FrankerFaceZ',
|
||||
emote_set.source || 'FFZ',
|
||||
|
||||
title = provider === 'main' ?
|
||||
t.i18n.t('emote-menu.main-set', 'Channel Emotes') :
|
||||
|
@ -2252,7 +2377,7 @@ export default class EmoteMenu extends Module {
|
|||
if ( sort_key == null )
|
||||
sort_key = emote_set.title.toLowerCase().includes('global') ? 100 : 0;
|
||||
|
||||
let section, emotes;
|
||||
let section, emotes, locks;
|
||||
|
||||
if ( grouped_sets[key] ) {
|
||||
section = grouped_sets[key];
|
||||
|
@ -2277,10 +2402,31 @@ export default class EmoteMenu extends Module {
|
|||
title,
|
||||
source,
|
||||
emotes,
|
||||
force_global: emote_set.force_global
|
||||
force_global: emote_set.force_global,
|
||||
all_locked: true
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get resub info.
|
||||
const resub = (state || this.state)?.ffz_sub_data?.sets?.[emote_set.id];
|
||||
if ( resub ) {
|
||||
section.renews = resub.next_bill_date;
|
||||
section.ends = resub.expires_at;
|
||||
}
|
||||
|
||||
if ( locked ) {
|
||||
section.locks = section.locks || {};
|
||||
section.locks[emote_set.id] = {
|
||||
set_id: emote_set.id,
|
||||
id: 'subwoofer',
|
||||
is_ffz: true,
|
||||
price: 'More Info',
|
||||
url: 'https://www.frankerfacez.com/subscribe',
|
||||
emotes: locks = new Set()
|
||||
}
|
||||
} else
|
||||
section.all_locked = false;
|
||||
|
||||
for(const emote of Object.values(emote_set.emotes))
|
||||
if ( ! emote.hidden ) {
|
||||
const is_fav = known_favs.includes(emote.id),
|
||||
|
@ -2292,18 +2438,27 @@ export default class EmoteMenu extends Module {
|
|||
srcSet: emote.srcSet,
|
||||
animSrc: emote.animSrc,
|
||||
animSrcSet: emote.animSrcSet,
|
||||
effects: emote.modifier ? emote.modifier_flags : 0,
|
||||
name: emote.name,
|
||||
favorite: is_fav,
|
||||
locked: locked,
|
||||
hidden: known_hidden.includes(emote.id),
|
||||
height: emote.height,
|
||||
width: emote.width
|
||||
};
|
||||
|
||||
emotes.push(em);
|
||||
if ( is_fav && ! seen_favs.has(emote.id) ) {
|
||||
|
||||
if ( ! locked && is_fav && ! seen_favs.has(emote.id) ) {
|
||||
favorites.push(em);
|
||||
seen_favs.add(emote.id);
|
||||
}
|
||||
|
||||
if ( locked )
|
||||
locks.add(emote.id);
|
||||
|
||||
if ( emote.modifier && emote.modifier_flags )
|
||||
section.has_effects = true;
|
||||
}
|
||||
|
||||
if ( emotes.length )
|
||||
|
@ -2320,7 +2475,11 @@ export default class EmoteMenu extends Module {
|
|||
componentDidUpdate(old_props) {
|
||||
if ( this.props.visible && ! old_props.visible ) {
|
||||
this.loadData();
|
||||
return;
|
||||
|
||||
if ( this.state.wants_plan_info )
|
||||
this.loadFFZPlanData();
|
||||
if ( this.state.wants_resub_info )
|
||||
this.loadFFZSubData();
|
||||
}
|
||||
|
||||
if ( ! this.props.visible && old_props.visible ) {
|
||||
|
@ -2406,23 +2565,25 @@ export default class EmoteMenu extends Module {
|
|||
if ( ! loading )
|
||||
this.loadedOnce = true;
|
||||
|
||||
let tab, sets, is_emoji, is_favs;
|
||||
let tab, sets, is_emoji, is_favs, is_effect;
|
||||
|
||||
if ( no_tabs ) {
|
||||
sets = [
|
||||
this.state.filtered_fav_sets,
|
||||
this.state.filtered_channel_sets,
|
||||
this.state.filtered_effect_sets,
|
||||
this.state.filtered_all_sets,
|
||||
this.state.filtered_emoji_sets
|
||||
].flat();
|
||||
|
||||
} else {
|
||||
tab = this.state.tab || t.chat.context.get('chat.emote-menu.default-tab');
|
||||
if ( (tab === 'channel' && ! this.state.has_channel_tab) || (tab === 'emoji' && ! this.state.has_emoji_tab) )
|
||||
if ( (tab === 'effect' && ! this.state.has_effect_Tab) || (tab === 'channel' && ! this.state.has_channel_tab) || (tab === 'emoji' && ! this.state.has_emoji_tab) )
|
||||
tab = 'all';
|
||||
|
||||
is_emoji = tab === 'emoji';
|
||||
is_favs = tab === 'fav';
|
||||
is_effect = tab === 'effect';
|
||||
|
||||
switch(tab) {
|
||||
case 'fav':
|
||||
|
@ -2431,6 +2592,9 @@ export default class EmoteMenu extends Module {
|
|||
case 'channel':
|
||||
sets = this.state.filtered_channel_sets;
|
||||
break;
|
||||
case 'effect':
|
||||
sets = this.state.filtered_effect_sets;
|
||||
break;
|
||||
case 'emoji':
|
||||
sets = this.state.filtered_emoji_sets;
|
||||
break;
|
||||
|
@ -2467,6 +2631,7 @@ export default class EmoteMenu extends Module {
|
|||
key: data.key,
|
||||
idx,
|
||||
data,
|
||||
ffz_sub_data: this.state.ffz_sub_data,
|
||||
emote_modifiers: this.state.emote_modifiers,
|
||||
animated: this.state.animated,
|
||||
combineTabs: this.state.combineTabs,
|
||||
|
@ -2606,6 +2771,20 @@ export default class EmoteMenu extends Module {
|
|||
</div>
|
||||
</button>
|
||||
</div>}
|
||||
{this.state.has_effect_Tab && <div class={`emote-picker-tab-item${tab === 'effect' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
|
||||
<button
|
||||
class={`ffz-tooltip tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive${tab === 'effect' ? ' ffz-interactable--selected' : ''}`}
|
||||
id="emote-picker__effect"
|
||||
data-tab="effect"
|
||||
data-tooltip-type="html"
|
||||
data-title={t.i18n.t('emote-menu.effects', 'Emote Effects')}
|
||||
onClick={this.clickTab}
|
||||
>
|
||||
<div class="tw-inline-flex tw-pd-x-1 tw-pd-y-05 tw-font-size-4">
|
||||
<figure class="ffz-i-fx" />
|
||||
</div>
|
||||
</button>
|
||||
</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 ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive${tab === 'all' ? ' ffz-interactable--selected' : ''}`}
|
||||
|
@ -2660,6 +2839,105 @@ export default class EmoteMenu extends Module {
|
|||
}
|
||||
|
||||
|
||||
async getFFZSubPrices() {
|
||||
let result;
|
||||
try {
|
||||
result = await fetch(`${this.staging.api}/payment/plans`)
|
||||
.then(r => r.ok ? r.json() : null);
|
||||
} catch(err) {
|
||||
this.log.error('Unable to load subscription prices from server.', err);
|
||||
result = null;
|
||||
}
|
||||
|
||||
// We only care about:
|
||||
// 1. What collections are granted by the available plan.
|
||||
// 2. How much they cost.
|
||||
|
||||
const out = {
|
||||
sets: {}
|
||||
};
|
||||
|
||||
for(const plan of Object.values(result.plans)) {
|
||||
if ( ! Array.isArray(plan.temporary_collections) )
|
||||
continue;
|
||||
|
||||
let prices;
|
||||
for(const gw_plan of Object.values(result.gateway_plans)) {
|
||||
if ( gw_plan.plan_id === plan.id && gw_plan.months === 1 ) {
|
||||
prices = gw_plan.prices;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( prices )
|
||||
for(const set_id of plan.temporary_collections) {
|
||||
out.sets[set_id] = {
|
||||
plan_id: plan.id,
|
||||
prices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
async getFFZSubData() {
|
||||
const me = this.resolve('site').getUser();
|
||||
if ( ! me )
|
||||
return null;
|
||||
|
||||
const token = await this.resolve('socket').getBareAPIToken();
|
||||
if ( ! token )
|
||||
return null;
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await fetch(`${this.staging.api}/v2/subscription/status?include=plan`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(r => r.ok ? r.json() : null);
|
||||
} catch(err) {
|
||||
this.log.error('Unable to load subscription status from server.', err);
|
||||
result = null;
|
||||
}
|
||||
|
||||
// We only care about:
|
||||
// 1. If the user has a free sub available
|
||||
// 2. What collections can expire/renew
|
||||
// 3. When they expire/renew
|
||||
|
||||
if ( ! result )
|
||||
return null;
|
||||
|
||||
const out = {
|
||||
has_free_sub: result.user?.bonus_month_eligible ?? false,
|
||||
sets: {}
|
||||
};
|
||||
|
||||
if ( result.user?.active_subs )
|
||||
for(const entry of Object.values(result.user.active_subs)) {
|
||||
const plan = result.plans?.[entry.id];
|
||||
if ( Array.isArray(plan?.temporary_collections) ) {
|
||||
for(const set_id of plan.temporary_collections)
|
||||
out.sets[set_id] = {
|
||||
plan_id: entry.id,
|
||||
expires_at: entry.expires_at
|
||||
? new Date(entry.expires_at)
|
||||
: null,
|
||||
next_bill_date: entry.next_bill_date
|
||||
? new Date(entry.next_bill_date)
|
||||
: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
async getData(sets, force, cursor = null, nodes = []) {
|
||||
if ( this._data ) {
|
||||
if ( ! force && set_equals(sets, this._data_sets) )
|
||||
|
|
|
@ -2177,41 +2177,6 @@ export default class ChatHook extends Module {
|
|||
}
|
||||
}
|
||||
|
||||
/*const old_chat = this.onChatMessageEvent;
|
||||
this.onChatMessageEvent = function(e) {
|
||||
/*if ( e && e.sentByCurrentUser ) {
|
||||
try {
|
||||
e.message.user.emotes = findEmotes(
|
||||
e.message.body,
|
||||
i.ffzGetEmotes()
|
||||
);
|
||||
|
||||
} catch(err) {
|
||||
t.log.capture(err, {extra: e});
|
||||
}
|
||||
}* /
|
||||
|
||||
return old_chat.call(i, e);
|
||||
}
|
||||
|
||||
|
||||
const old_action = this.onChatActionEvent;
|
||||
this.onChatActionEvent = function(e) {
|
||||
/*if ( e && e.sentByCurrentUser ) {
|
||||
try {
|
||||
e.message.user.emotes = findEmotes(
|
||||
e.message.body,
|
||||
i.ffzGetEmotes()
|
||||
);
|
||||
|
||||
} catch(err) {
|
||||
t.log.capture(err, {extra: e});
|
||||
}
|
||||
}* /
|
||||
|
||||
return old_action.call(i, e);
|
||||
}*/
|
||||
|
||||
const old_announce = this.onAnnouncementEvent;
|
||||
this.onAnnouncementEvent = function(e) {
|
||||
//console.log('announcement', e);
|
||||
|
|
|
@ -1013,6 +1013,18 @@ export default class Input extends Module {
|
|||
return results;*/
|
||||
}
|
||||
|
||||
getInput() {
|
||||
for(const inst of this.ChatInput.instances) {
|
||||
if ( ! inst.autocompleteInputRef || ! inst.state )
|
||||
continue;
|
||||
|
||||
if ( inst.state.value )
|
||||
return inst.state.value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pasteMessage(room, message) {
|
||||
for(const inst of this.ChatInput.instances) {
|
||||
if ( inst?.props?.channelLogin !== room )
|
||||
|
|
|
@ -348,7 +348,7 @@ export default class SettingsMenu extends Module {
|
|||
'chat.ffz-badge.about',
|
||||
'This badge appears globally for users with FrankerFaceZ. Please visit the {website} to change this badge.',
|
||||
{
|
||||
website: (<a href="https://www.frankerfacez.com/donate" class="ffz-link" rel="noopener noreferrer" target="_blank">
|
||||
website: (<a href="https://www.frankerfacez.com/subscribe" class="ffz-link" rel="noopener noreferrer" target="_blank">
|
||||
{this.i18n.t('chat.ffz-badge.site-link', 'FrankerFaceZ website')}
|
||||
</a>)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
.message > .modified-emote > .chat-line__message--emote {
|
||||
vertical-align: bottom !important;
|
||||
}
|
||||
|
||||
.message > .scaled-modified-emote {
|
||||
vertical-align: baseline;
|
||||
|
||||
& > .chat-line__message--emote { vertical-align: unset !important; }
|
||||
}
|
||||
|
||||
.message > div > .chat-line__message--emote {
|
||||
vertical-align: baseline;
|
||||
padding-top: 5px;
|
||||
|
|
|
@ -16,6 +16,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
.scaled-modified-emote {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
& > .chat-line__message--emote {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-author__display-name,
|
||||
.chat-author__intl-login {
|
||||
cursor: pointer;
|
||||
|
@ -337,6 +348,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.emote-picker__tab-content {
|
||||
height: unset !important;
|
||||
max-height: 30.5rem;
|
||||
}
|
||||
|
||||
&.ffz--emote-picker__tall {
|
||||
.whispers-thread .emote-picker-and-button & .emote-picker__tab-content {
|
||||
max-height: 30rem;
|
||||
|
|
|
@ -168,7 +168,7 @@ export default class SocketClient extends Module {
|
|||
if ( ! user || ! user.id )
|
||||
return fail(new Error('Unable to get current user or not logged in.'));
|
||||
|
||||
const es = new EventSource(`https://api-test.frankerfacez.com/auth/ext_verify/${user.id}`);
|
||||
const es = new EventSource(`https://api.frankerfacez.com/auth/ext_verify/${user.id}`);
|
||||
|
||||
on(es, 'challenge', event => {
|
||||
const conn = this.resolve('site.chat')?.ChatService?.first?.client?.connection;
|
||||
|
|
|
@ -43,4 +43,4 @@ export default class StagingSelector extends Module {
|
|||
|
||||
this.emit(':updated', this.api, this.cdn);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -162,7 +162,7 @@ export const WS_CLUSTERS = {
|
|||
],
|
||||
|
||||
Development: [
|
||||
['wss://127.0.0.1:8003/', 1]
|
||||
['ws://127.0.0.1:7999/', 1]
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -111,5 +111,6 @@ export default [
|
|||
"mod",
|
||||
"flag",
|
||||
"mange-suspicious",
|
||||
"doc-text"
|
||||
"doc-text",
|
||||
"fx"
|
||||
];
|
|
@ -68,6 +68,20 @@ export const DEFAULT_TYPES = {
|
|||
return this.formatNumber(val, node.f);
|
||||
},
|
||||
|
||||
currency(val, node) {
|
||||
if ( typeof val !== 'number' ) {
|
||||
let new_val = parseFloat(val);
|
||||
if ( isNaN(new_val) || ! isFinite(new_val) )
|
||||
new_val = parseInt(val, 10);
|
||||
if ( isNaN(new_val) || ! isFinite(new_val) )
|
||||
return val;
|
||||
|
||||
val = new_val;
|
||||
}
|
||||
|
||||
return this.formatCurrency(val, node.f);
|
||||
},
|
||||
|
||||
date(val, node) {
|
||||
return this.formatDate(val, node.f);
|
||||
},
|
||||
|
@ -216,6 +230,7 @@ export default class TranslationCore {
|
|||
this.cache = new Map;
|
||||
|
||||
this.numberFormats = new Map;
|
||||
this.currencyFormats = new Map;
|
||||
|
||||
this.formats = Object.assign({}, DEFAULT_FORMATS);
|
||||
if ( options.formats )
|
||||
|
@ -237,6 +252,7 @@ export default class TranslationCore {
|
|||
if ( val !== this._locale ) {
|
||||
this._locale = val;
|
||||
this.numberFormats.clear();
|
||||
this.currencyFormats.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,6 +273,20 @@ export default class TranslationCore {
|
|||
}
|
||||
}
|
||||
|
||||
formatCurrency(value, currency) {
|
||||
let formatter = this.currencyFormats.get(currency);
|
||||
if ( ! formatter ) {
|
||||
formatter = new Intl.NumberFormat(navigator.languages, {
|
||||
style: 'currency',
|
||||
currency
|
||||
});
|
||||
|
||||
this.currencyFormats.set(currency, formatter);
|
||||
}
|
||||
|
||||
return formatter.format(value);
|
||||
}
|
||||
|
||||
formatNumber(value, format) {
|
||||
let formatter = this.numberFormats.get(format);
|
||||
if ( ! formatter ) {
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
.ffz-i-mod:before { content: '\e84a'; } /* '' */
|
||||
.ffz-i-flag:before { content: '\e84b'; } /* '' */
|
||||
.ffz-i-mange-suspicious:before { content: '\e84c'; } /* '' */
|
||||
.ffz-i-fx:before { content: '\e84d'; } /* '' */
|
||||
.ffz-i-move:before { content: '\f047'; } /* '' */
|
||||
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
|
||||
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -76,6 +76,7 @@
|
|||
.ffz-i-mod { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-flag { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-mange-suspicious { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-fx { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
.ffz-i-mod { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-flag { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-mange-suspicious { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-fx { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
@font-face {
|
||||
font-family: 'ffz-fontello';
|
||||
src: url('../font/ffz-fontello.eot?30569253');
|
||||
src: url('../font/ffz-fontello.eot?30569253#iefix') format('embedded-opentype'),
|
||||
url('../font/ffz-fontello.woff2?30569253') format('woff2'),
|
||||
url('../font/ffz-fontello.woff?30569253') format('woff'),
|
||||
url('../font/ffz-fontello.ttf?30569253') format('truetype'),
|
||||
url('../font/ffz-fontello.svg?30569253#ffz-fontello') format('svg');
|
||||
src: url('../font/ffz-fontello.eot?59946237');
|
||||
src: url('../font/ffz-fontello.eot?59946237#iefix') format('embedded-opentype'),
|
||||
url('../font/ffz-fontello.woff2?59946237') format('woff2'),
|
||||
url('../font/ffz-fontello.woff?59946237') format('woff'),
|
||||
url('../font/ffz-fontello.ttf?59946237') format('truetype'),
|
||||
url('../font/ffz-fontello.svg?59946237#ffz-fontello') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
|||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||
@font-face {
|
||||
font-family: 'ffz-fontello';
|
||||
src: url('../font/ffz-fontello.svg?30569253#ffz-fontello') format('svg');
|
||||
src: url('../font/ffz-fontello.svg?59946237#ffz-fontello') format('svg');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -131,6 +131,7 @@
|
|||
.ffz-i-mod:before { content: '\e84a'; } /* '' */
|
||||
.ffz-i-flag:before { content: '\e84b'; } /* '' */
|
||||
.ffz-i-mange-suspicious:before { content: '\e84c'; } /* '' */
|
||||
.ffz-i-fx:before { content: '\e84d'; } /* '' */
|
||||
.ffz-i-move:before { content: '\f047'; } /* '' */
|
||||
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
|
||||
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
||||
|
|
|
@ -60,6 +60,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.ffz-i-fx:before {
|
||||
margin-top: 0.2rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ffz-i-zreknarf:before {
|
||||
width: 1.3em;
|
||||
margin: .5rem .05rem 0;
|
||||
|
|
|
@ -215,6 +215,14 @@ body {
|
|||
padding: .1rem .2rem;
|
||||
}
|
||||
|
||||
.ffz-effect-tip {
|
||||
margin: 3px auto 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.ffz-badge-tip {
|
||||
margin: .2rem .4rem;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue