mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-02 00:58:32 +00:00
4.20.54
* Added: New UI for clearing FrankerFaceZ settings (Data Management > Storage). * Fixed: Do not display duplicate bot badges in tool-tips when a bot has both a global bot badge and a channel-specific bot badge. * Fixed: Do not add click URLs for FFZ badges to add-on badges. * Fixed: Remove `debugger;` from automatic error reporting method. * Fixed: IndexedDBProvider not synchronizing settings correctly. * Fixed: Change a CSS class name used when changing emote visibility to avoid bad UX due to misbehaving third-party extensions. * Fixed: Color calculations for chat messages, including highlight colors. (Closes #947) * Changed: Use rounded images from the CDN for FFZ badges in tool-tips. * Changed: Use Twitch IDs rather than usernames for assigning channel-specific badges to users. * API Added: `settings.addClearable(key, definition)` for adding new UI to `Data Management > Storage >> Clear`.
This commit is contained in:
parent
7adee6556c
commit
ef4ff0c13a
15 changed files with 394 additions and 27 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.20.53",
|
"version": "4.20.54",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -493,8 +493,6 @@ export default class Badges extends Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.log.info('badge-click', event.target);
|
|
||||||
|
|
||||||
if ( url ) {
|
if ( url ) {
|
||||||
const link = createElement('a', {
|
const link = createElement('a', {
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
|
@ -586,8 +584,15 @@ export default class Badges extends Module {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handled_ids = new Set;
|
||||||
|
|
||||||
for(const badge of badges)
|
for(const badge of badges)
|
||||||
if ( badge && badge.id != null ) {
|
if ( badge && badge.id != null ) {
|
||||||
|
if ( handled_ids.has(badge.id) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
handled_ids.add(badge.id);
|
||||||
|
|
||||||
const full_badge = this.badges[badge.id] || {},
|
const full_badge = this.badges[badge.id] || {},
|
||||||
is_hidden = hidden_badges[badge.id];
|
is_hidden = hidden_badges[badge.id];
|
||||||
|
|
||||||
|
@ -609,6 +614,12 @@ export default class Badges extends Module {
|
||||||
title: badge.title || full_badge.title
|
title: badge.title || full_badge.title
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Hacky nonsense.
|
||||||
|
if ( ! full_badge.addon ) {
|
||||||
|
bd.image = `//cdn.frankerfacez.com/badge/${badge.id}/4/rounded`;
|
||||||
|
bd.color = null;
|
||||||
|
}
|
||||||
|
|
||||||
let style;
|
let style;
|
||||||
|
|
||||||
if ( old_badge ) {
|
if ( old_badge ) {
|
||||||
|
@ -821,7 +832,7 @@ export default class Badges extends Module {
|
||||||
data.replaces = true;
|
data.replaces = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( data.name === 'developer' || data.name === 'supporter' )
|
if ( ! data.addon && (data.name === 'developer' || data.name === 'supporter') )
|
||||||
data.click_url = 'https://www.frankerfacez.com/donate';
|
data.click_url = 'https://www.frankerfacez.com/donate';
|
||||||
|
|
||||||
if ( generate_css )
|
if ( generate_css )
|
||||||
|
|
|
@ -276,6 +276,13 @@ export default class Room {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const old_badges = this.data?.user_badge_ids;
|
||||||
|
if ( old_badges )
|
||||||
|
for(const badge_id in old_badges )
|
||||||
|
if ( has(old_badges, badge_id) )
|
||||||
|
for(const user of old_badges[badge_id])
|
||||||
|
this.getUser(user, undefined).removeBadge('ffz', badge_id);
|
||||||
|
|
||||||
const d = data.room,
|
const d = data.room,
|
||||||
id = `${d.twitch_id}`;
|
id = `${d.twitch_id}`;
|
||||||
|
|
||||||
|
@ -306,14 +313,12 @@ export default class Room {
|
||||||
if ( has(data.sets, set_id) )
|
if ( has(data.sets, set_id) )
|
||||||
this.manager.emotes.loadSetData(set_id, data.sets[set_id]);
|
this.manager.emotes.loadSetData(set_id, data.sets[set_id]);
|
||||||
|
|
||||||
|
const badges = d.user_badge_ids;
|
||||||
const badges = d.user_badges;
|
|
||||||
if ( badges )
|
if ( badges )
|
||||||
for(const badge_id in badges)
|
for(const badge_id in badges)
|
||||||
if ( has(badges, badge_id) )
|
if ( has(badges, badge_id) )
|
||||||
for(const user of badges[badge_id])
|
for(const user of badges[badge_id])
|
||||||
this.getUser(undefined, user).addBadge('ffz', badge_id);
|
this.getUser(user, undefined).addBadge('ffz', badge_id);
|
||||||
|
|
||||||
|
|
||||||
if ( d.css )
|
if ( d.css )
|
||||||
this.style.set('css', d.css);
|
this.style.set('css', d.css);
|
||||||
|
|
216
src/modules/main_menu/components/clear-settings.vue
Normal file
216
src/modules/main_menu/components/clear-settings.vue
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
<template lang="html">
|
||||||
|
<div class="ffz--clear-settings tw-pd-t-05">
|
||||||
|
<div class="tw-c-background-accent tw-c-text-overlay tw-pd-1 tw-mg-b-2">
|
||||||
|
<h3 class="ffz-i-attention">
|
||||||
|
{{ t('setting.clear.warning', 'Be careful! This is permanent.') }}
|
||||||
|
</h3>
|
||||||
|
<markdown :source="t('setting.clear.warning-explain', 'Deleting your data with this tool cannot be reversed. Make sure you have a backup!')" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="! started">
|
||||||
|
<div class="tw-mg-b-1">
|
||||||
|
{{ t('setting.clear.step-1', 'Please select which types of data you wish to clear:') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-mg-l-5">
|
||||||
|
<div
|
||||||
|
v-for="(type, key) in types"
|
||||||
|
:key="key"
|
||||||
|
class="tw-checkbox tw-relative tw-mg-y-05"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
:id="key"
|
||||||
|
:ref="key"
|
||||||
|
type="checkbox"
|
||||||
|
class="tw-checkbox__input"
|
||||||
|
>
|
||||||
|
|
||||||
|
<label :for="key" class="tw-checkbox__label">
|
||||||
|
<span class="tw-mg-l-1">
|
||||||
|
{{ t(`setting.clear.opt.${key}`, type.label || key) }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-mg-t-1 tw-border-t tw-pd-t-1 tw-mg-b-1">
|
||||||
|
<markdown :source="t('setting.clear.step-2', 'Are you really sure? Please enter `{code}` in the text box below to confirm.', {code})" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-mg-l-5">
|
||||||
|
<input
|
||||||
|
v-model="entered"
|
||||||
|
type="text"
|
||||||
|
class="tw-block tw-border-radius-medium tw-font-size-6 tw-input tw-pd-x-1 tw-pd-y-05"
|
||||||
|
autocapitalize="off"
|
||||||
|
autocorrect="off"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-mg-t-1 tw-border-t tw-pd-t-1 tw-mg-b-1">
|
||||||
|
<div class="tw-mg-l-5">
|
||||||
|
<button
|
||||||
|
class="tw-button"
|
||||||
|
:class="{'tw-button--disabled': ! enabled}"
|
||||||
|
@click="clear"
|
||||||
|
>
|
||||||
|
<span class="tw-button__icon tw-button__icon--left">
|
||||||
|
<figure class="ffz-i-trash" />
|
||||||
|
</span>
|
||||||
|
<span class="tw-button__text">
|
||||||
|
{{ t('setting.clear.start', 'Clear My Data') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="started && running">
|
||||||
|
{{ t('setting.clear.running', 'Clearing settings. Please wait...') }}
|
||||||
|
</div>
|
||||||
|
<div v-if="started && ! running">
|
||||||
|
{{ t('setting.clear.done', 'Your settings have been cleared. Please refresh any applicable Twitch pages to ensure no cached data remains.') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import {generateHex} from 'utilities/object';
|
||||||
|
import { maybe_call } from 'src/utilities/object';
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['item', 'context'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
const ffz = this.context.getFFZ(),
|
||||||
|
settings = ffz.resolve('settings');
|
||||||
|
|
||||||
|
return {
|
||||||
|
types: settings.getClearables(),
|
||||||
|
entered: '',
|
||||||
|
code: generateHex(8),
|
||||||
|
|
||||||
|
started: false,
|
||||||
|
running: false,
|
||||||
|
|
||||||
|
message: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
enabled() {
|
||||||
|
return this.code === this.entered
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async clear() {
|
||||||
|
if ( ! this.enabled )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.started = true;
|
||||||
|
this.running = true;
|
||||||
|
|
||||||
|
const ffz = this.context.getFFZ(),
|
||||||
|
settings = ffz.resolve('settings'),
|
||||||
|
provider = settings.provider;
|
||||||
|
|
||||||
|
for(const [key, type] of Object.entries(this.types)) {
|
||||||
|
if ( ! this.$refs[key]?.[0]?.checked )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ( type.clear )
|
||||||
|
await type.clear.call(this, provider, settings); // eslint-disable-line no-await-in-loop
|
||||||
|
else {
|
||||||
|
let keys = maybe_call(type.keys, this, provider, settings);
|
||||||
|
if ( keys instanceof Promise )
|
||||||
|
keys = await keys; // eslint-disable-line no-await-in-loop
|
||||||
|
|
||||||
|
if ( Array.isArray(keys) )
|
||||||
|
for(const key of keys)
|
||||||
|
provider.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.running = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async backup() {
|
||||||
|
this.error = false;
|
||||||
|
this.message = null;
|
||||||
|
|
||||||
|
let blob;
|
||||||
|
try {
|
||||||
|
const settings = this.item.getFFZ().resolve('settings'),
|
||||||
|
data = await settings.getFullBackup();
|
||||||
|
blob = new Blob([JSON.stringify(data)], {type: 'application/json;charset=utf-8'});
|
||||||
|
} catch(err) {
|
||||||
|
this.error_desc = this.t('setting.backup-restore.dump-error', 'Unable to export settings data to JSON.');
|
||||||
|
this.error = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
saveAs(blob, 'ffz-settings.json');
|
||||||
|
} catch(err) {
|
||||||
|
this.error_desc = this.t('setting.backup-restore.save-error', 'Unable to save.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async restore() {
|
||||||
|
this.error = false;
|
||||||
|
this.message = null;
|
||||||
|
|
||||||
|
let contents;
|
||||||
|
try {
|
||||||
|
contents = await readFile(await openFile('application/json'));
|
||||||
|
} catch(err) {
|
||||||
|
this.error_desc = this.t('setting.backup-restore.read-error', 'Unable to read file.');
|
||||||
|
this.error = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = JSON.parse(contents);
|
||||||
|
} catch(err) {
|
||||||
|
this.error_desc = this.t('setting.backup-restore.json-error', 'Unable to parse file as JSON.');
|
||||||
|
this.error = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! data || data.version !== 2 ) {
|
||||||
|
this.error_desc = this.t('setting.backup-restore.old-file', 'This file is invalid or was created in another version of FrankerFaceZ and cannot be loaded.');
|
||||||
|
this.error = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( data.type !== 'full' ) {
|
||||||
|
this.error_desc = this.t('setting.backup-restore.non-full', 'This file is not a full backup and cannot be restored with this tool.');
|
||||||
|
this.error = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = this.item.getFFZ().resolve('settings'),
|
||||||
|
provider = settings.provider;
|
||||||
|
|
||||||
|
await provider.awaitReady();
|
||||||
|
|
||||||
|
provider.clear();
|
||||||
|
let i = 0;
|
||||||
|
for(const key of Object.keys(data.values)) {
|
||||||
|
const val = data.values[key];
|
||||||
|
provider.set(key, val);
|
||||||
|
provider.emit('changed', key, val, false);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.message = this.t('setting.backup-restore.restored', '{count,number} items have been restored. Please refresh this page.', {
|
||||||
|
count: i
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -60,6 +60,12 @@ export default class MainMenu extends Module {
|
||||||
getFFZ: () => this.resolve('core')
|
getFFZ: () => this.resolve('core')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.settings.addUI('clear', {
|
||||||
|
path: 'Data Management > Storage @{"profile_warning": false} >> tabs ~> Clear',
|
||||||
|
component: 'clear-settings',
|
||||||
|
force_seen: true
|
||||||
|
});
|
||||||
|
|
||||||
this.settings.addUI('home', {
|
this.settings.addUI('home', {
|
||||||
path: 'Home @{"sort": -1000, "profile_warning": false}',
|
path: 'Home @{"sort": -1000, "profile_warning": false}',
|
||||||
component: 'home-page'
|
component: 'home-page'
|
||||||
|
|
|
@ -191,7 +191,6 @@ export default class RavenLogger extends Module {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
shouldSendCallback: data => {
|
shouldSendCallback: data => {
|
||||||
debugger;
|
|
||||||
if ( this.settings && ! this.settings.get('reports.error.enable') ) {
|
if ( this.settings && ! this.settings.get('reports.error.enable') ) {
|
||||||
if ( data.tags && data.tags.example && this.__example_waiter ) {
|
if ( data.tags && data.tags.example && this.__example_waiter ) {
|
||||||
this.__example_waiter(null);
|
this.__example_waiter(null);
|
||||||
|
|
68
src/settings/clearables.js
Normal file
68
src/settings/clearables.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Clearable Settings
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const Experiments = {
|
||||||
|
label: 'Experiment Overrides',
|
||||||
|
keys: [
|
||||||
|
'exp-lock',
|
||||||
|
'experiment-overrides'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const HiddenEmotes = {
|
||||||
|
label: 'Hidden Emotes',
|
||||||
|
keys(provider) {
|
||||||
|
const keys = ['emote-menu.hidden-sets'];
|
||||||
|
for(const key of provider.keys())
|
||||||
|
if ( key.startsWith('hidden-emotes.') )
|
||||||
|
keys.push(key);
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FavoriteEmotes = {
|
||||||
|
label: 'Favorited Emotes',
|
||||||
|
keys(provider) {
|
||||||
|
const keys = [];
|
||||||
|
for(const key of provider.keys())
|
||||||
|
if ( key.startsWith('favorite-emotes.') )
|
||||||
|
keys.push(key);
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Overrides = {
|
||||||
|
label: 'Name and Color Overrides',
|
||||||
|
keys: [
|
||||||
|
'overrides.colors',
|
||||||
|
'overrides.names'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Profiles = {
|
||||||
|
label: 'Profiles',
|
||||||
|
clear(provider, settings) {
|
||||||
|
const keys = ['profiles'];
|
||||||
|
for(const key of provider.keys())
|
||||||
|
if ( /^p:\d+:/.test(key) )
|
||||||
|
keys.push(key);
|
||||||
|
|
||||||
|
for(const key of keys)
|
||||||
|
provider.delete(key);
|
||||||
|
|
||||||
|
settings.loadProfiles();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Everything = {
|
||||||
|
label: 'Absolutely Everything',
|
||||||
|
clear(provider, settings) {
|
||||||
|
provider.clear();
|
||||||
|
settings.loadProfiles();
|
||||||
|
}
|
||||||
|
};
|
|
@ -5,7 +5,7 @@
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
import {deep_equals, has, debounce} from 'utilities/object';
|
import {deep_equals, has, debounce, deep_copy} from 'utilities/object';
|
||||||
|
|
||||||
import {IndexedDBProvider, LocalStorageProvider} from './providers';
|
import {IndexedDBProvider, LocalStorageProvider} from './providers';
|
||||||
import SettingsProfile from './profile';
|
import SettingsProfile from './profile';
|
||||||
|
@ -13,6 +13,7 @@ import SettingsContext from './context';
|
||||||
import MigrationManager from './migration';
|
import MigrationManager from './migration';
|
||||||
|
|
||||||
import * as FILTERS from './filters';
|
import * as FILTERS from './filters';
|
||||||
|
import * as CLEARABLES from './clearables';
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
@ -51,6 +52,13 @@ export default class SettingsManager extends Module {
|
||||||
this.ui_structures = new Map;
|
this.ui_structures = new Map;
|
||||||
this.definitions = new Map;
|
this.definitions = new Map;
|
||||||
|
|
||||||
|
// Clearable Data Rules
|
||||||
|
this.clearables = {};
|
||||||
|
|
||||||
|
for(const key in CLEARABLES)
|
||||||
|
if ( has(CLEARABLES, key) )
|
||||||
|
this.clearables[key] = CLEARABLES[key];
|
||||||
|
|
||||||
// Filters
|
// Filters
|
||||||
this.filters = {};
|
this.filters = {};
|
||||||
|
|
||||||
|
@ -607,6 +615,22 @@ export default class SettingsManager extends Module {
|
||||||
this.ui_structures.set(key, definition);
|
this.ui_structures.set(key, definition);
|
||||||
this.emit(':added-definition', key, definition);
|
this.emit(':added-definition', key, definition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
addClearable(key, definition) {
|
||||||
|
if ( typeof key === 'object' ) {
|
||||||
|
for(const k in key)
|
||||||
|
if ( has(key, k) )
|
||||||
|
this.addClearable(k, key[k]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clearables[key] = definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
getClearables() {
|
||||||
|
return deep_copy(this.clearables);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -300,9 +300,10 @@ export class IndexedDBProvider extends SettingsProvider {
|
||||||
const {type, key} = event.data;
|
const {type, key} = event.data;
|
||||||
|
|
||||||
if ( type === 'set' ) {
|
if ( type === 'set' ) {
|
||||||
const val = JSON.parse(localStorage.getItem(this.prefix + key));
|
this._get(key).then(val => {
|
||||||
this._cached.set(key, val);
|
this._cached.set(key, val);
|
||||||
this.emit('changed', key, val, false);
|
this.emit('changed', key, val, false);
|
||||||
|
}).catch(err => this.manager.log.error(`Error getting setting "${key}" from database`, err));
|
||||||
|
|
||||||
} else if ( type === 'delete' ) {
|
} else if ( type === 'delete' ) {
|
||||||
this._cached.delete(key);
|
this._cached.delete(key);
|
||||||
|
@ -343,8 +344,8 @@ export class IndexedDBProvider extends SettingsProvider {
|
||||||
|
|
||||||
this._cached.set(key, value);
|
this._cached.set(key, value);
|
||||||
this._set(key, value)
|
this._set(key, value)
|
||||||
.catch(err => this.manager.log.error(`Error saving setting "${key}" to database`, err))
|
.then(() => this.broadcast({type: 'set', key}))
|
||||||
.then(() => this.broadcast({type: 'set', key}));
|
.catch(err => this.manager.log.error(`Error saving setting "${key}" to database`, err));
|
||||||
|
|
||||||
this.emit('set', key, value, false);
|
this.emit('set', key, value, false);
|
||||||
}
|
}
|
||||||
|
@ -473,6 +474,23 @@ export class IndexedDBProvider extends SettingsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async _get(key) {
|
||||||
|
const db = await this.getDB(),
|
||||||
|
trx = db.transaction(['settings'], 'readonly'),
|
||||||
|
store = trx.objectStore('settings');
|
||||||
|
|
||||||
|
return new Promise((s,f) => {
|
||||||
|
store.onerror = f;
|
||||||
|
|
||||||
|
const req = store.get(key);
|
||||||
|
req.onerror = f;
|
||||||
|
req.onsuccess = () => {
|
||||||
|
s(req.result.v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async _set(key, value) {
|
async _set(key, value) {
|
||||||
const db = await this.getDB(),
|
const db = await this.getDB(),
|
||||||
trx = db.transaction(['settings'], 'readwrite'),
|
trx = db.transaction(['settings'], 'readwrite'),
|
||||||
|
@ -480,9 +498,10 @@ export class IndexedDBProvider extends SettingsProvider {
|
||||||
|
|
||||||
return new Promise((s,f) => {
|
return new Promise((s,f) => {
|
||||||
store.onerror = f;
|
store.onerror = f;
|
||||||
store.onsuccess = s;
|
|
||||||
|
|
||||||
store.put({k: key, v: value});
|
const req = store.put({k: key, v: value});
|
||||||
|
req.onerror = f;
|
||||||
|
req.onsuccess = s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,9 +513,10 @@ export class IndexedDBProvider extends SettingsProvider {
|
||||||
|
|
||||||
return new Promise((s,f) => {
|
return new Promise((s,f) => {
|
||||||
store.onerror = f;
|
store.onerror = f;
|
||||||
store.onsuccess = s;
|
|
||||||
|
|
||||||
store.delete(key);
|
const req = store.delete(key);
|
||||||
|
req.onerror = f;
|
||||||
|
req.onsuccess = s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,9 +528,10 @@ export class IndexedDBProvider extends SettingsProvider {
|
||||||
|
|
||||||
return new Promise((s,f) => {
|
return new Promise((s,f) => {
|
||||||
store.onerror = f;
|
store.onerror = f;
|
||||||
store.onsuccess = s;
|
|
||||||
|
|
||||||
store.clear();
|
const req = store.clear();
|
||||||
|
req.onerror = f;
|
||||||
|
req.onsuccess = s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -762,7 +762,7 @@ export default class EmoteMenu extends Module {
|
||||||
|
|
||||||
return (<button
|
return (<button
|
||||||
key={emote.id}
|
key={emote.id}
|
||||||
class={`ffz-tooltip emote-picker__emote-link${!visibility && locked ? ' locked' : ''}${hidden ? ' hidden' : ''}`}
|
class={`ffz-tooltip emote-picker__emote-link${!visibility && locked ? ' locked' : ''}${hidden ? ' emote-hidden' : ''}`}
|
||||||
data-tooltip-type="emote"
|
data-tooltip-type="emote"
|
||||||
data-provider={emote.provider}
|
data-provider={emote.provider}
|
||||||
data-id={emote.id}
|
data-id={emote.id}
|
||||||
|
|
|
@ -606,7 +606,7 @@ export default class ChatHook extends Module {
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( chat_color )
|
if ( chat_color )
|
||||||
chat_dark = chat_color.luminance() < 0.5;
|
chat_dark = chat_color.toHSLA().l < 0.5;
|
||||||
|
|
||||||
chat_color = chat_dark ? '#191919' : '#E0E0E0';
|
chat_color = chat_dark ? '#191919' : '#E0E0E0';
|
||||||
|
|
||||||
|
@ -617,7 +617,7 @@ export default class ChatHook extends Module {
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( chat_text )
|
if ( chat_text )
|
||||||
text_dark = chat_text.luminance() < 0.5;
|
text_dark = chat_text.toHSLA().l < 0.5;
|
||||||
|
|
||||||
chat_text = text_dark ? '#19171c' : '#dad8de';
|
chat_text = text_dark ? '#19171c' : '#dad8de';
|
||||||
|
|
||||||
|
|
|
@ -338,7 +338,7 @@ export default class ChatLine extends Module {
|
||||||
override_mode = t.chat.context.get('chat.filtering.display-deleted'),
|
override_mode = t.chat.context.get('chat.filtering.display-deleted'),
|
||||||
|
|
||||||
msg = t.chat.standardizeMessage(this.props.message),
|
msg = t.chat.standardizeMessage(this.props.message),
|
||||||
reply_tokens = (reply_mode === 2 || (reply_mode === 1 && this.props.repliesAppearancePreference !== 'expanded')) ? ( msg.ffz_reply = msg.ffz_reply || t.chat.tokenizeReply(this.props.reply) ) : null,
|
reply_tokens = (reply_mode === 2 || (reply_mode === 1 && this.props.repliesAppearancePreference && this.props.repliesAppearancePreference !== 'expanded')) ? ( msg.ffz_reply = msg.ffz_reply || t.chat.tokenizeReply(this.props.reply) ) : null,
|
||||||
is_action = msg.messageType === types.Action,
|
is_action = msg.messageType === types.Action,
|
||||||
|
|
||||||
user = msg.user,
|
user = msg.user,
|
||||||
|
@ -864,7 +864,7 @@ other {# messages were deleted by a moderator.}
|
||||||
e('div', {
|
e('div', {
|
||||||
className: 'chat-line__message-container'
|
className: 'chat-line__message-container'
|
||||||
}, [
|
}, [
|
||||||
this.props.repliesAppearancePreference === 'expanded' ? this.renderReplyLine() : null,
|
this.props.repliesAppearancePreference && this.props.repliesAppearancePreference === 'expanded' ? this.renderReplyLine() : null,
|
||||||
out
|
out
|
||||||
]),
|
]),
|
||||||
e('div', {
|
e('div', {
|
||||||
|
|
|
@ -371,7 +371,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.locked,
|
&.locked,
|
||||||
&.hidden {
|
&.emote-hidden {
|
||||||
img {
|
img {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
|
@ -585,6 +585,18 @@ export function truncate(str, target = 100, overage = 15, ellipsis = '…', brea
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function decimalToHex(number) {
|
||||||
|
return number.toString(16).padStart(2, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function generateHex(length = 40) {
|
||||||
|
const arr = new Uint8Array(length / 2);
|
||||||
|
window.crypto.getRandomValues(arr);
|
||||||
|
return Array.from(arr, decimalToHex).join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export class SourcedSet {
|
export class SourcedSet {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._cache = [];
|
this._cache = [];
|
||||||
|
|
|
@ -289,6 +289,7 @@ textarea.tw-input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ffz--clear-settings code,
|
||||||
.ffz--experiments code {
|
.ffz--experiments code {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
padding: 2px 5px;
|
padding: 2px 5px;
|
||||||
|
@ -302,6 +303,10 @@ textarea.tw-input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ffz--clear-settings code {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
.ffz--changelog,
|
.ffz--changelog,
|
||||||
.ffz--widget {
|
.ffz--widget {
|
||||||
code {
|
code {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue