1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
* Added: Formatters for chat action variables. (Closes #1199)
* Changed: By default, open the user card to a badge when clicking a badge in chat. (Closes #1195)
* Fixed: The settings bridge functioning incorrectly for users without a set storage provider, causing pages that rely on the settings bridge including the Stream Dashboard to never correctly load FFZ.
This commit is contained in:
SirStendec 2022-04-19 15:34:20 -04:00
parent 084a3ee5e0
commit 2af7d5618b
8 changed files with 186 additions and 21 deletions

View file

@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
"version": "4.32.5",
"version": "4.33.0",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"private": true,
"license": "Apache-2.0",

View file

@ -191,7 +191,7 @@ export class TranslationManager extends Module {
},
data: () => {
const out = [], now = new Date;
for (const [key,fmt] of Object.entries(this._.formats.date)) {
for (const [key, fmt] of Object.entries(this._.formats.date)) {
out.push({
value: key, title: `${this.formatDate(now, key)} (${key})`
})
@ -815,6 +815,28 @@ export class TranslationManager extends Module {
const DOLLAR_REGEX = /\$/g;
const REPLACE = String.prototype.replace;
const FORMAT_REGEX = /^\s*([^(]+?)\s*(?:\(\s*([^)]+?)\s*\))?\s*$/;
export function parseFormatters(fmt) {
if (!fmt || ! fmt.length)
return;
const result = [];
for(const token of fmt.split(/\|/g)) {
const match = FORMAT_REGEX.exec(token);
if (!match)
continue;
result.push({
fmt: match[1],
extra: match[2]
});
}
return result;
}
export function transformPhrase(phrase, substitutions, locale, token_regex, formatters) {
const is_array = Array.isArray(phrase);
if ( substitutions == null )
@ -828,14 +850,23 @@ export function transformPhrase(phrase, substitutions, locale, token_regex, form
if ( typeof result === 'string' )
result = REPLACE.call(result, token_regex, (expr, arg, fmt) => {
let val = get(arg, options);
let val = get(arg.trim(), options);
if ( val == null )
return '';
const formatter = formatters[fmt];
if ( typeof formatter === 'function' )
val = formatter(val, locale, options);
else if ( typeof val === 'string' )
const fmts = parseFormatters(fmt);
let formatted = false;
if (fmts) {
for(const format of fmts) {
const formatter = formatters[format.fmt];
if (typeof formatter === 'function') {
val = formatter(val, locale, options, format.extra);
formatted = true;
}
}
}
if (! formatted && typeof val === 'string' )
val = REPLACE.call(val, DOLLAR_REGEX, '$$');
return val;

View file

@ -17,6 +17,10 @@
{{ t('setting.actions.variables', 'Available Variables: {vars}', {vars}) }}
</div>
<div class="tw-c-text-alt-2 tw-mg-b-1">
{{ t('setting.actions.formats', 'Available Formatters: {fmts}', {fmts}) }}
</div>
<div class="ffz-checkbox">
<input
:id="'chat-paste$' + id"
@ -41,7 +45,7 @@
let last_id = 0;
export default {
props: ['value', 'defaults', 'vars'],
props: ['value', 'defaults', 'vars', 'fmts'],
data() {
return {

View file

@ -245,7 +245,79 @@ export default class Actions extends Module {
data,
this.i18n.locale,
VAR_REPLACE,
{}
{
upper(val) {
return val.toString().toUpperCase();
},
uppercase(val) {
return val.toString().toUpperCase();
},
lower(val) {
return val.toString().toLowerCase();
},
lowercase(val) {
return val.toString().toLowerCase();
},
snakecase(val) {
return val.toString().toSnakeCase();
},
slugify(val, locale, options, extra) {
return val.toString().toSlug(extra && extra.length ? extra : '-');
},
word(val, locale, options, extra) {
if (! extra || ! extra.length)
return val;
let start, end;
const bits = extra.split(',');
try {
start = parseInt(bits[0], 10);
if (isNaN(start) || !isFinite(start))
return val;
} catch (err) {
this.log.warn('Invalid value for word(start)', bits[0]);
return val;
}
if (bits.length > 1) {
const bit = bits[1].trim();
if (! bit.length )
end = -1;
else
try {
end = parseInt(bits[1], 10);
if (isNaN(end) || !isFinite(end))
return val;
} catch(err) {
this.log.warn('Invalid value for word(end)', bits[1]);
return val;
}
}
const words = val.split(/\s+/);
if (start < 0)
start = words.length + start;
if (start < 0)
start = 0;
if (start >= words.length)
start = words.length - 1;
if (end != null) {
if (end < 0)
end = words.length + end;
if (end < start)
end = start;
if (end > words.length)
end = words.length;
return words.slice(start, end + 1).join(' ');
}
return words[start];
}
}
);
}

View file

@ -223,12 +223,24 @@ export default class Badges extends Module {
});
this.settings.add('chat.badges.clickable', {
default: true,
default: 2,
process(ctx, val) {
if (val === true)
return 2;
else if (val === false)
return 0;
return val;
},
ui: {
path: 'Chat > Badges >> Behavior',
title: 'Allow clicking badges.',
description: 'Certain badges, such as Prime Gaming, act as links when this is enabled.',
component: 'setting-check-box'
component: 'setting-select-box',
data: [
{value: 0, title: 'Disabled'},
{value: 1, title: 'Legacy (Open URLs)'},
{value: 2, title: 'Open Badge Card'}
]
}
});
@ -534,7 +546,8 @@ export default class Badges extends Module {
handleClick(event) {
if ( ! this.parent.context.get('chat.badges.clickable') )
const mode = this.parent.context.get('chat.badges.clickable');
if ( ! mode )
return;
const target = event.target;
@ -544,6 +557,7 @@ export default class Badges extends Module {
return;
let url = null;
let click_badge = null;
for(const d of ds.data) {
const p = d.provider;
@ -553,14 +567,14 @@ export default class Badges extends Module {
if ( ! bd )
continue;
if ( bd.click_url )
if ( mode == 1 && bd.click_url )
url = bd.click_url;
else if ( global_badge.click_url )
else if ( mode == 1 && global_badge.click_url )
url = global_badge.click_url;
else if ( (bd.click_action === 'sub' || global_badge.click_action === 'sub') && ds.room_login )
else if ( mode == 1 && (bd.click_action === 'sub' || global_badge.click_action === 'sub') && ds.room_login )
url = `https://www.twitch.tv/subs/${ds.room_login}`;
else
continue;
click_badge = bd;
break;
@ -578,6 +592,17 @@ export default class Badges extends Module {
}
}
if (click_badge) {
const fine = this.resolve('site.fine');
if (fine) {
const line = fine.searchParent(target, n => n.openBadgeDetails && n.props?.message);
if (line) {
line.openBadgeDetails(click_badge, event);
return;
}
}
}
if ( url ) {
const link = createElement('a', {
target: '_blank',

View file

@ -379,6 +379,7 @@
:value="edit_data.options"
:defaults="action_def.defaults"
:vars="vars"
:fmts="fmts"
@input="onChangeAction($event)"
/>
</section>
@ -493,6 +494,20 @@ export default {
return this.modifiers
},
fmts() {
const out = [];
out.push('word(start)');
out.push('word(start,end)');
out.push('upper');
out.push('lower');
out.push('snakecase');
out.push('slugify');
out.push('slugify(separator)');
return out.join(', ');
},
vars() {
const out = [],
ctx = this.context || [];

View file

@ -1027,9 +1027,9 @@ export class CrossOriginStorageBridge extends SettingsProvider {
this._last_id = 0;
const frame = this.frame = document.createElement('iframe');
frame.src = this.manager.root.host === 'twitch' ?
'//www.twitch.tv/p/ffz_bridge/' :
'//www.youtube.com/__ffz_bridge/';
frame.src = this.manager.root.host === 'youtube' ?
'//www.youtube.com/__ffz_bridge/' :
'//www.twitch.tv/p/ffz_bridge/';
frame.id = 'ffz-settings-bridge';
frame.style.width = 0;
frame.style.height = 0;
@ -1041,7 +1041,7 @@ export class CrossOriginStorageBridge extends SettingsProvider {
// Static Properties
static supported(manager) { return manager.root.host === 'twitch' ? NOT_WWW_TWITCH : NOT_WWW_YT; }
static supported() { return NOT_WWW_TWITCH && NOT_WWW_YT; }
static hasContent(manager) { return CrossOriginStorageBridge.supported(manager); }
static key = 'cosb';

View file

@ -12,9 +12,27 @@ const SNAKE_CAPS = /([a-z])([A-Z])/g,
SNAKE_SPACE = /[ \t\W]/g,
SNAKE_TRIM = /^_+|_+$/g;
String.prototype.toSlug = function(separator = '-') {
let result = this;
if (result.normalize)
result = result.normalize('NFD');
return result
.replace(/[\u0300-\u036f]/g, '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9 ]/g, '')
.replace(/\s+/g, separator);
}
String.prototype.toSnakeCase = function() {
return this
let result = this;
if (result.normalize)
result = result.normalize('NFD');
return result
.replace(/[\u0300-\u036f]/g, '')
.trim()
.replace(SNAKE_CAPS, '$1_$2')
.replace(SNAKE_SPACE, '_')
.replace(SNAKE_TRIM, '')