mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.72.0
* Fixed: The button to expand chat not appearing when using theater mode along with Swap Sidebars. * Fixed: Emote / link cards not appearing correctly when using moderator view. * API Added: `searchParentNode(input: InputNode, criteria: (node: ReactNode) => boolean)` method to `site.fine` for finding React internal nodes, useful for locating stateless components for extracting props. * API Added: `getRenderers()`, `getActions()` methods for `chat.actions` to get bulk data. * API Added: `getBadge(id: string | number)` method for `chat.badges` to get a badge. * API Added: `getUser(...).getBadges()` method for `chat` to get a user's badges. * API Added: The `action-editor` component for `chat.actions` has an additional `extra_appearance_editor` field for adding additional inputs. * API Changed: The `removeAction(...keys: string[])` and `removeRenderer(...keys: string[])` methods for `chat.actions` now support multiple keys to allow for more efficient removal.
This commit is contained in:
parent
a02c14d84c
commit
38e557e809
11 changed files with 170 additions and 52 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.71.0",
|
||||
"version": "4.72.0",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
|
|
|
@ -27,6 +27,11 @@ export default class Actions extends Module {
|
|||
this.actions = {};
|
||||
this.renderers = {};
|
||||
|
||||
this.filterAction = (x) => x.appearance &&
|
||||
this.renderers[x.appearance.type] &&
|
||||
(! this.renderers[x.appearance.type].load || this.renderers[x.appearance.type].load(x.appearance)) &&
|
||||
(! x.action || this.actions[x.action]);
|
||||
|
||||
this.settings.add('chat.actions.size', {
|
||||
default: 16,
|
||||
ui: {
|
||||
|
@ -82,11 +87,7 @@ export default class Actions extends Module {
|
|||
|
||||
this.settings.add('chat.actions.hover', {
|
||||
process: (ctx, val) =>
|
||||
val.filter(x => x.appearance &&
|
||||
this.renderers[x.appearance.type] &&
|
||||
(! this.renderers[x.appearance.type].load || this.renderers[x.appearance.type].load(x.appearance)) &&
|
||||
(! x.action || this.actions[x.action])
|
||||
),
|
||||
val.filter(this.filterAction),
|
||||
|
||||
default: [
|
||||
{v: {action: 'pin', appearance: {type: 'icon', icon: 'ffz-i-pin'}, options: {}, display: {mod_icons: true}}},
|
||||
|
@ -119,11 +120,7 @@ export default class Actions extends Module {
|
|||
this.settings.add('chat.actions.inline', {
|
||||
// Filter out actions
|
||||
process: (ctx, val) =>
|
||||
val.filter(x => x.appearance &&
|
||||
this.renderers[x.appearance.type] &&
|
||||
(! this.renderers[x.appearance.type].load || this.renderers[x.appearance.type].load(x.appearance)) &&
|
||||
(! x.action || this.actions[x.action])
|
||||
),
|
||||
val.filter(this.filterAction),
|
||||
|
||||
default: [
|
||||
{v: {action: 'ban', appearance: {type: 'icon', icon: 'ffz-i-block'}, options: {}, display: {mod: true, mod_icons: true, deleted: false}}},
|
||||
|
@ -157,11 +154,7 @@ export default class Actions extends Module {
|
|||
this.settings.add('chat.actions.user-context', {
|
||||
// Filter out actions
|
||||
process: (ctx, val) =>
|
||||
val.filter(x => x.type || (x.appearance &&
|
||||
this.renderers[x.appearance.type] &&
|
||||
(! this.renderers[x.appearance.type].load || this.renderers[x.appearance.type].load(x.appearance)) &&
|
||||
(! x.action || this.actions[x.action])
|
||||
)),
|
||||
val.filter(x => x.type || this.filterAction(x)),
|
||||
|
||||
default: [],
|
||||
type: 'array_merge',
|
||||
|
@ -187,11 +180,7 @@ export default class Actions extends Module {
|
|||
this.settings.add('chat.actions.room', {
|
||||
// Filter out actions
|
||||
process: (ctx, val) =>
|
||||
val.filter(x => x.type || (x.appearance &&
|
||||
this.renderers[x.appearance.type] &&
|
||||
(! this.renderers[x.appearance.type].load || this.renderers[x.appearance.type].load(x.appearance)) &&
|
||||
(! x.action || this.actions[x.action])
|
||||
)),
|
||||
val.filter(x => x.type || this.filterAction(x)),
|
||||
|
||||
default: [],
|
||||
type: 'array_merge',
|
||||
|
@ -316,20 +305,24 @@ export default class Actions extends Module {
|
|||
}
|
||||
|
||||
if ( is_dev ) {
|
||||
overrides.removeAction = key => {
|
||||
const existing = this.actions[key];
|
||||
if ( existing && existing.__source !== addon_id )
|
||||
module.log.warn('[DEV-CHECK] Removed un-owned action with actions.removeAction:', key, ' owner:', existing.__source ?? 'ffz');
|
||||
overrides.removeAction = (...key) => {
|
||||
for(const entry of key) {
|
||||
const existing = this.actions[entry];
|
||||
if ( existing && existing.__source !== addon_id )
|
||||
module.log.warn('[DEV-CHECK] Removed un-owned action with actions.removeAction:', entry, ' owner:', existing.__source ?? 'ffz');
|
||||
}
|
||||
|
||||
return this.removeAction(key);
|
||||
return this.removeAction(...key);
|
||||
};
|
||||
|
||||
overrides.removeRenderer = key => {
|
||||
const existing = this.renderers[key];
|
||||
if ( existing && existing.__source !== addon_id )
|
||||
module.log.warn('[DEV-CHECK] Removed un-owned renderer with actions.removeRenderer:', key, ' owner:', existing.__source ?? 'ffz');
|
||||
overrides.removeRenderer = (...key) => {
|
||||
for(const entry of key) {
|
||||
const existing = this.renderers[entry];
|
||||
if ( existing && existing.__source !== addon_id )
|
||||
module.log.warn('[DEV-CHECK] Removed un-owned renderer with actions.removeRenderer:', entry, ' owner:', existing.__source ?? 'ffz');
|
||||
}
|
||||
|
||||
return this.removeRenderer(key);
|
||||
return this.removeRenderer(...key);
|
||||
}
|
||||
|
||||
warnings.actions = 'Please use addAction() or removeAction()';
|
||||
|
@ -367,22 +360,49 @@ export default class Actions extends Module {
|
|||
this._updateContexts();
|
||||
}
|
||||
|
||||
getActions() {
|
||||
return {...this.actions}
|
||||
}
|
||||
|
||||
removeAction(key) {
|
||||
if ( ! has(this.actions, key) )
|
||||
return;
|
||||
getAction(key) {
|
||||
return this.actions[key] ?? null;
|
||||
}
|
||||
|
||||
delete this.actions[key];
|
||||
this._updateContexts();
|
||||
getRenderer(key) {
|
||||
return this.renderers[key] ?? null;
|
||||
}
|
||||
|
||||
getRenderers() {
|
||||
return {...this.renderers}
|
||||
}
|
||||
|
||||
removeAction(...keys) {
|
||||
let changed = false;
|
||||
for(const entry of keys) {
|
||||
if ( ! has(this.actions, entry) )
|
||||
continue;
|
||||
|
||||
delete this.actions[entry];
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( changed )
|
||||
this._updateContexts();
|
||||
}
|
||||
|
||||
|
||||
removeRenderer(key) {
|
||||
if ( ! has(this.renderers, key) )
|
||||
return;
|
||||
removeRenderer(...keys) {
|
||||
let changed = false;
|
||||
for(const entry of keys) {
|
||||
if ( ! has(this.renderers, entry) )
|
||||
return;
|
||||
|
||||
delete this.renderers[key];
|
||||
this._updateContexts();
|
||||
delete this.renderers[entry];
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( changed )
|
||||
this._updateContexts();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1223,6 +1223,11 @@ export default class Badges extends Module {
|
|||
}
|
||||
|
||||
|
||||
getBadge(badge_id) {
|
||||
return this.badges[badge_id] ?? null;
|
||||
}
|
||||
|
||||
|
||||
removeBadge(badge_id, generate_css = true) {
|
||||
if ( ! this.badges[badge_id] )
|
||||
return;
|
||||
|
|
|
@ -154,6 +154,13 @@ export default class User {
|
|||
}
|
||||
|
||||
|
||||
getBadges() {
|
||||
if ( this.badges )
|
||||
return [...this.badges._cache];
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
getBadge(badge_id: string) {
|
||||
if ( this.badges )
|
||||
for(const badge of this.badges._cache)
|
||||
|
|
|
@ -370,8 +370,8 @@ export default {
|
|||
if ( ! parent )
|
||||
parent = document.body;
|
||||
|
||||
const box = el.getBoundingClientRect(),
|
||||
pbox = parent.getBoundingClientRect();
|
||||
const box = el.getBoundingClientRect();
|
||||
let pbox = parent.getBoundingClientRect();
|
||||
|
||||
if ( box.top < pbox.top ) {
|
||||
el.style.top = `${el.offsetTop + (pbox.top - box.top)}px`;
|
||||
|
@ -447,4 +447,4 @@ export default {
|
|||
|
||||
}
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -79,6 +79,12 @@
|
|||
v-if="renderer"
|
||||
v-model="edit_data.appearance"
|
||||
/>
|
||||
|
||||
<component
|
||||
:is="extra_appearance"
|
||||
v-if="extra_appearance"
|
||||
v-model="edit_data.appearance"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="tw-mg-t-1 tw-border-t tw-pd-t-1">
|
||||
|
@ -128,6 +134,28 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div v-if="has_following" class="tw-flex tw-align-items-center">
|
||||
<label for="vis_following">
|
||||
{{ t('setting.actions.edit-visible.following', 'Following User') }}
|
||||
</label>
|
||||
|
||||
<select
|
||||
id="vis_following"
|
||||
v-model="edit_data.display.following"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
>
|
||||
<option :value="undefined" selected>
|
||||
{{ t('setting.unset', 'Unset') }}
|
||||
</option>
|
||||
<option :value="true">
|
||||
{{ t('setting.true', 'True') }}
|
||||
</option>
|
||||
<option :value="false">
|
||||
{{ t('setting.false', 'False') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div v-if="has_message" class="tw-flex tw-align-items-center">
|
||||
<label for="vis_deleted">
|
||||
{{ t('setting.actions.edit-visible.deleted', 'Message Deleted') }}
|
||||
|
@ -459,7 +487,7 @@ import {has, maybe_call, deep_copy} from 'utilities/object';
|
|||
let id = 0;
|
||||
|
||||
export default {
|
||||
props: ['vuectx', 'action', 'data', 'inline', 'mod_icons', 'context', 'modifiers', 'hover_modifier'],
|
||||
props: ['vuectx', 'action', 'data', 'inline', 'mod_icons', 'extra_appearance', 'context', 'modifiers', 'hover_modifier'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
|
@ -487,6 +515,10 @@ export default {
|
|||
return this.context && this.context.includes('message')
|
||||
},
|
||||
|
||||
has_following() {
|
||||
return this.context && this.context.includes('following')
|
||||
},
|
||||
|
||||
has_mode() {
|
||||
return this.context && this.context.includes('room-mode')
|
||||
},
|
||||
|
@ -642,6 +674,12 @@ export default {
|
|||
if ( disp.disable )
|
||||
return this.t('setting.actions.visible.never', 'never');
|
||||
|
||||
if ( disp.following === true )
|
||||
out.push(this.t('setting.actions.visible.following', 'when following user'));
|
||||
|
||||
else if ( disp.following === false )
|
||||
out.push(this.t('setting.actions.visible.unfollowing', 'when not following user'));
|
||||
|
||||
if ( disp.mod === true )
|
||||
out.push(this.t('setting.actions.visible.mod', 'when moderator'));
|
||||
|
||||
|
@ -824,4 +862,4 @@ export default {
|
|||
|
||||
}
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -376,6 +376,7 @@
|
|||
:inline="item.inline"
|
||||
:mod_icons="has_icons"
|
||||
:context="item.context"
|
||||
:extra_appearance="item.extra_appearance_editor"
|
||||
:vuectx="context"
|
||||
:modifiers="item.modifiers"
|
||||
:hover_modifier="item.hover_modifier"
|
||||
|
@ -817,4 +818,4 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.right-column--theatre {
|
||||
/* 1 higher than default Twitch, to make the expand button appear properly */
|
||||
z-index: 3001;
|
||||
}
|
||||
|
||||
.channel-root {
|
||||
width: unset !important;
|
||||
}
|
||||
|
@ -98,4 +103,4 @@ body .whispers--theatre-mode.whispers--right-column-expanded-beside {
|
|||
left: calc(var(--ffz-chat-width) + 5rem) !important;
|
||||
right: 40rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
position: absolute !important;
|
||||
}
|
||||
|
||||
#root.ffz-has-dialog {
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
min-width: 100%;
|
||||
}
|
||||
|
|
|
@ -12,4 +12,4 @@
|
|||
//@import 'host_options';
|
||||
@import 'featured_follow';
|
||||
@import 'mod_card';
|
||||
@import 'easteregg';
|
||||
@import 'easteregg';
|
||||
|
|
|
@ -270,6 +270,47 @@ export default class Fine extends Module<'site.fine', FineEvents> {
|
|||
return null;
|
||||
}
|
||||
|
||||
searchParentNode<TNode extends ReactNode = ReactNode>(
|
||||
input: InputNode,
|
||||
criteria: NodeCriteria,
|
||||
max_depth = 15,
|
||||
depth = 0,
|
||||
traverse_roots = true
|
||||
): TNode | null {
|
||||
if ( depth > max_depth )
|
||||
return null;
|
||||
|
||||
const node = this.resolveNode(input);
|
||||
|
||||
// If we don't have a node, then stop.
|
||||
if ( ! node )
|
||||
return null;
|
||||
|
||||
if ( criteria(node) )
|
||||
return node as TNode;
|
||||
|
||||
if ( node.return ) {
|
||||
const result = this.searchParentNode<TNode>(node.return, criteria, max_depth, depth+1, traverse_roots);
|
||||
if ( result )
|
||||
return result as TNode;
|
||||
}
|
||||
|
||||
// Stupid code for traversing up into another React root.
|
||||
const inst = node.stateNode;
|
||||
if ( traverse_roots && (inst as any)?.containerInfo ) {
|
||||
const parent = (inst as any).containerInfo?.parentElement as Node | undefined,
|
||||
parent_node = parent && this.getReactInstance(parent);
|
||||
|
||||
if ( parent_node ) {
|
||||
const result = this.searchParentNode<TNode>(parent_node, criteria, max_depth, depth+1, traverse_roots);
|
||||
if ( result )
|
||||
return result as TNode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
searchNode<TNode extends ReactNode = ReactNode>(
|
||||
input: InputNode,
|
||||
criteria: NodeCriteria,
|
||||
|
@ -286,7 +327,7 @@ export default class Fine extends Module<'site.fine', FineEvents> {
|
|||
if ( ! node )
|
||||
return null;
|
||||
|
||||
if ( node && criteria(node) )
|
||||
if ( criteria(node) )
|
||||
return node as TNode;
|
||||
|
||||
// If the node has disabled scanning, don't scan into its children.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue