1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-02 00:58:32 +00:00

4.0.0-rc21

* Added: In-Line Chat Actions can now be set to display with specific modifier keys being held. This feature currently requires that Freeze Chat Scrolling is enabled. (Though, why you'd want to use this without that feature is beyond me.)
* Fixed: Bug with custom chat width in theater mode.
* Fixed: The `Get Bits` button appearing when disabled.
* Fixed: Issue with highlighting chat usernames when using Firefox.
* Fixed: Misc minor bugs.
This commit is contained in:
SirStendec 2019-05-16 14:46:26 -04:00
parent 63783472b7
commit bd11a6f2aa
16 changed files with 299 additions and 23 deletions

View file

@ -155,6 +155,9 @@ export class TranslationManager extends Module {
handleMessage(event) { handleMessage(event) {
const msg = event.data; const msg = event.data;
if ( ! msg )
return;
if ( msg.type === 'seen' ) if ( msg.type === 'seen' )
this.see(msg.key, true); this.see(msg.key, true);

View file

@ -149,7 +149,7 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`
FrankerFaceZ.Logger = Logger; FrankerFaceZ.Logger = Logger;
const VER = FrankerFaceZ.version_info = { const VER = FrankerFaceZ.version_info = {
major: 4, minor: 0, revision: 0, extra: '-rc20.5', major: 4, minor: 0, revision: 0, extra: '-rc21',
commit: __git_commit__, commit: __git_commit__,
build: __webpack_hash__, build: __webpack_hash__,
toString: () => toString: () =>

View file

@ -77,6 +77,7 @@ export default class Actions extends Module {
component: 'chat-actions', component: 'chat-actions',
context: ['user', 'room', 'message'], context: ['user', 'room', 'message'],
inline: true, inline: true,
modifiers: true,
data: () => { data: () => {
const chat = this.resolve('site.chat'); const chat = this.resolve('site.chat');
@ -437,7 +438,10 @@ export default class Actions extends Module {
if ( current_level < 3 ) if ( current_level < 3 )
mod_icons = false; mod_icons = false;
const chat = this.resolve('site.chat'); const chat = this.resolve('site.chat'),
modified = [];
let had_action = false;
for(const data of this.parent.context.get('chat.actions.inline')) { for(const data of this.parent.context.get('chat.actions.inline')) {
if ( ! data.action || ! data.appearance ) if ( ! data.action || ! data.appearance )
@ -445,6 +449,7 @@ export default class Actions extends Module {
const ap = data.appearance || {}, const ap = data.appearance || {},
disp = data.display || {}, disp = data.display || {},
keys = disp.keys,
def = this.renderers[ap.type]; def = this.renderers[ap.type];
@ -459,8 +464,14 @@ export default class Actions extends Module {
color = has_color && (chat && chat.colors ? chat.colors.process(ap.color) : ap.color), color = has_color && (chat && chat.colors ? chat.colors.process(ap.color) : ap.color),
contents = def.render.call(this, ap, createElement, color); contents = def.render.call(this, ap, createElement, color);
actions.push(<button let list = actions;
class={`ffz-tooltip ffz-mod-icon mod-icon tw-c-text-alt-2${has_color ? ' colored' : ''}`}
if ( keys )
list = modified;
had_action = true;
list.push(<button
class={`ffz-tooltip ffz-mod-icon mod-icon tw-c-text-alt-2${has_color ? ' colored' : ''}${keys ? ` ffz-modifier-${keys}` : ''}`}
data-tooltip-type="action" data-tooltip-type="action"
data-action={data.action} data-action={data.action}
data-options={data.options ? JSON.stringify(data.options) : null} data-options={data.options ? JSON.stringify(data.options) : null}
@ -472,7 +483,7 @@ export default class Actions extends Module {
</button>); </button>);
} }
if ( ! actions.length ) if ( ! had_action )
return null; return null;
/*const room = current_room && JSON.stringify(current_room), /*const room = current_room && JSON.stringify(current_room),
@ -483,12 +494,25 @@ export default class Actions extends Module {
type: msg.user.type type: msg.user.type
});*/ });*/
return (<div let out = (<div
class="ffz--inline-actions ffz-action-data tw-inline-block tw-mg-r-05" class="ffz--inline-actions ffz-action-data tw-inline-block tw-mg-r-05"
data-source="line" data-source="line"
> >
{actions} {actions}
</div>); </div>);
if ( modified.length ) {
return [out,
(<div
class="ffz--inline-actions ffz--modifier-actions ffz-action-data"
data-source="line"
>
{modified}
</div>)
];
}
return out;
} }

View file

@ -291,7 +291,7 @@ export const whisper = {
me = site && site.getUser(), me = site && site.getUser(),
store = site && site.store; store = site && site.store;
if ( ! me || ! store || ! data.user.id || me.id == data.user.id ) if ( ! me || ! store || ! data.user || ! data.user.id || me.id == data.user.id )
return; return;
const id_1 = parseInt(me.id, 10), const id_1 = parseInt(me.id, 10),

View file

@ -935,7 +935,7 @@ export default class Chat extends Module {
for(const id in this.room_ids) for(const id in this.room_ids)
if ( has(this.room_ids, id) ) { if ( has(this.room_ids, id) ) {
const room = this.room_ids[id]; const room = this.room_ids[id];
if ( room ) { if ( room && ! room.destroyed ) {
visited.add(room); visited.add(room);
yield room; yield room;
} }
@ -944,7 +944,7 @@ export default class Chat extends Module {
for(const login in this.rooms) for(const login in this.rooms)
if ( has(this.rooms, login) ) { if ( has(this.rooms, login) ) {
const room = this.rooms[login]; const room = this.rooms[login];
if ( room && ! visited.has(room) ) if ( room && ! room.destroyed && ! visited.has(room) )
yield room; yield room;
} }
} }
@ -1347,6 +1347,7 @@ export default class Chat extends Module {
if ( type === 'text' ) if ( type === 'text' )
res = e('span', { res = e('span', {
className: 'text-fragment',
'data-a-target': 'chat-message-text' 'data-a-target': 'chat-message-text'
}, token.text); }, token.text);

View file

@ -364,14 +364,20 @@ export default class Room {
// ======================================================================== // ========================================================================
ref(referrer) { ref(referrer) {
if ( ! this.refs )
throw new Error('Attempting to use destroyed Room');
clearTimeout(this._destroy_timer); clearTimeout(this._destroy_timer);
this._destroy_timer = null; this._destroy_timer = null;
this.refs.add(referrer); this.refs.add(referrer);
} }
unref(referrer) { unref(referrer) {
if ( ! this.refs )
return;
this.refs.delete(referrer); this.refs.delete(referrer);
if ( ! this.users.size && ! this._destroy_timer ) if ( ! this.refs.size && ! this._destroy_timer )
this._destroy_timer = setTimeout(() => this.destroy(), 5000); this._destroy_timer = setTimeout(() => this.destroy(), 5000);
} }

View file

@ -29,7 +29,7 @@ export const Links = {
render(token, createElement) { render(token, createElement) {
return (<a return (<a
class="ffz-tooltip" class="ffz-tooltip link-fragment"
data-tooltip-type="link" data-tooltip-type="link"
data-url={token.url} data-url={token.url}
data-is-mail={token.is_mail} data-is-mail={token.is_mail}

View file

@ -119,6 +119,76 @@
<option :value="false">{{ t('setting.false', 'False') }}</option> <option :value="false">{{ t('setting.false', 'False') }}</option>
</select> </select>
</div> </div>
<div v-if="has_modifiers" class="tw-flex tw-align-items-start">
<label for="vis_modifiers">
{{ t('setting.actions.edit-visible.modifier', 'Modifiers') }}
</label>
<div>
<div class="ffz--inline tw-flex">
<div class="tw-pd-r-1 tw-checkbox">
<input
id="key_ctrl"
ref="key_ctrl"
:checked="edit_data.display.keys & 1"
type="checkbox"
class="tw-checkbox__input"
@change="onChangeKeys"
>
<label for="key_ctrl" class="tw-checkbox__label">
{{ t('setting.key.ctrl', 'Ctrl') }}
</label>
</div>
<div class="tw-pd-r-1 tw-checkbox">
<input
id="key_shift"
ref="key_shift"
:checked="edit_data.display.keys & 2"
type="checkbox"
class="tw-checkbox__input"
@change="onChangeKeys"
>
<label for="key_shift" class="tw-checkbox__label">
{{ t('setting.key.shift', 'Shift') }}
</label>
</div>
<div class="tw-pd-r-1 tw-checkbox">
<input
id="key_alt"
ref="key_alt"
:checked="edit_data.display.keys & 4"
type="checkbox"
class="tw-checkbox__input"
@change="onChangeKeys"
>
<label for="key_alt" class="tw-checkbox__label">
{{ t('setting.key.alt', 'Alt') }}
</label>
</div>
<div class="tw-pd-r-1 tw-checkbox">
<input
id="key_meta"
ref="key_meta"
:checked="edit_data.display.keys & 8"
type="checkbox"
class="tw-checkbox__input"
@change="onChangeKeys"
>
<label for="key_meta" class="tw-checkbox__label">
{{ t('setting.key.meta', 'Meta') }}
</label>
</div>
</div>
<div class="tw-pd-t-05">
Note: This currently requires Chat > Behavior > Freeze Chat Scrolling to be enabled.
</div>
</div>
</div>
</section> </section>
<section class="tw-mg-t-1 tw-border-t tw-pd-t-1"> <section class="tw-mg-t-1 tw-border-t tw-pd-t-1">
@ -215,7 +285,7 @@
import {has, maybe_call, deep_copy} from 'utilities/object'; import {has, maybe_call, deep_copy} from 'utilities/object';
export default { export default {
props: ['action', 'data', 'inline', 'context'], props: ['action', 'data', 'inline', 'context', 'modifiers'],
data() { data() {
return { return {
@ -237,6 +307,10 @@ export default {
return this.context && this.context.includes('message') return this.context && this.context.includes('message')
}, },
has_modifiers() {
return this.modifiers
},
vars() { vars() {
const out = [], const out = [],
ctx = this.context || []; ctx = this.context || [];
@ -373,6 +447,24 @@ export default {
else if ( disp.deleted === false ) else if ( disp.deleted === false )
out.push(this.t('setting.actions.visible.undeleted', 'if message not deleted')); out.push(this.t('setting.actions.visible.undeleted', 'if message not deleted'));
if ( disp.keys ) {
const key_out = [];
if ( disp.keys & 1 )
key_out.push(this.t('setting.key.ctrl', 'Ctrl'));
if ( disp.keys & 2 )
key_out.push(this.t('setting.key.shift', 'Shift'));
if ( disp.keys & 4 )
key_out.push(this.t('setting.key.alt', 'Alt'));
if ( disp.keys & 8 )
key_out.push(this.t('setting.key.meta', 'Meta'));
if ( key_out.length )
out.push(this.t('setting.actions.visible.modifier', 'when {key_list} {keys, plural, one {is} other {are}} held', {
key_list: key_out.join(' + '),
keys: key_out.length
}));
}
if ( ! out.length ) if ( ! out.length )
return this.t('setting.actions.visible.always', 'always'); return this.t('setting.actions.visible.always', 'always');
@ -392,6 +484,23 @@ export default {
this.edit_data.options = val; this.edit_data.options = val;
}, },
onChangeKeys() {
if ( ! this.editing )
return;
let i = 0;
if ( this.$refs.key_ctrl.checked )
i |= 1;
if ( this.$refs.key_shift.checked )
i |= 2;
if ( this.$refs.key_alt.checked )
i |= 4;
if ( this.$refs.key_meta.checked )
i |= 8;
this.edit_data.display.keys = i;
},
edit() { edit() {
if ( ! this.canEdit ) if ( ! this.canEdit )
return; return;

View file

@ -193,6 +193,7 @@
:data="data" :data="data"
:inline="item.inline" :inline="item.inline"
:context="item.context" :context="item.context"
:modifiers="item.modifiers"
@remove="remove(act)" @remove="remove(act)"
@save="save(act, $event)" @save="save(act, $event)"
/> />

View file

@ -38,7 +38,7 @@ export default class BitsButton extends Module {
this.BitsButton = this.fine.define( this.BitsButton = this.fine.define(
'bits-button', 'bits-button',
n => n.renderButton && n.toggleShowTutorial n => n.toggleBalloon && n.toggleShowTutorial
); );
} }

View file

@ -214,6 +214,16 @@ export default class ChatHook extends Module {
} }
}); });
this.settings.add('chat.use-width', {
requires: ['chat.width', 'context.ui.rightColumnExpanded'],
process(ctx) {
if ( ! ctx.get('context.ui.rightColumnExpanded') )
return false;
return ctx.get('chat.width') != 340;
}
});
this.settings.add('chat.bits.show-pinned', { this.settings.add('chat.bits.show-pinned', {
default: true, default: true,
ui: { ui: {
@ -349,6 +359,14 @@ export default class ChatHook extends Module {
updateChatCSS() { updateChatCSS() {
if ( ! this._update_chat_css_timer )
this._update_chat_css_timer = setTimeout(() => this._updateChatCSS(), 0);
}
_updateChatCSS() {
clearTimeout(this._update_chat_css_timer);
this._update_chat_css_timer = null;
const width = this.chat.context.get('chat.width'), const width = this.chat.context.get('chat.width'),
size = this.chat.context.get('chat.font-size'), size = this.chat.context.get('chat.font-size'),
emote_alignment = this.chat.context.get('chat.lines.emote-alignment'), emote_alignment = this.chat.context.get('chat.lines.emote-alignment'),
@ -364,7 +382,7 @@ export default class ChatHook extends Module {
this.css_tweaks.setVariable('chat-width', `${width/10}rem`); this.css_tweaks.setVariable('chat-width', `${width/10}rem`);
this.css_tweaks.toggle('chat-font', size !== 12 || font); this.css_tweaks.toggle('chat-font', size !== 12 || font);
this.css_tweaks.toggle('chat-width', width !== 340); this.css_tweaks.toggle('chat-width', this.chat.context.get('chat.use-width'));
this.css_tweaks.toggle('emote-alignment-padded', emote_alignment === 1); this.css_tweaks.toggle('emote-alignment-padded', emote_alignment === 1);
this.css_tweaks.toggle('emote-alignment-baseline', emote_alignment === 2); this.css_tweaks.toggle('emote-alignment-baseline', emote_alignment === 2);
@ -421,6 +439,7 @@ export default class ChatHook extends Module {
this.grabTypes(); this.grabTypes();
this.chat.context.on('changed:chat.width', this.updateChatCSS, this); this.chat.context.on('changed:chat.width', this.updateChatCSS, this);
this.chat.context.on('changed:chat.use-width', this.updateChatCSS, this);
this.chat.context.on('changed:chat.font-size', this.updateChatCSS, this); this.chat.context.on('changed:chat.font-size', this.updateChatCSS, this);
this.chat.context.on('changed:chat.font-family', this.updateChatCSS, this); this.chat.context.on('changed:chat.font-family', this.updateChatCSS, this);
this.chat.context.on('changed:chat.lines.emote-alignment', this.updateChatCSS, this); this.chat.context.on('changed:chat.lines.emote-alignment', this.updateChatCSS, this);

View file

@ -156,8 +156,9 @@ export default class ChatLine extends Module {
e('span', { e('span', {
className: 'chat-line__message--badges' className: 'chat-line__message--badges'
}, t.chat.badges.render(msg, e)), }, t.chat.badges.render(msg, e)),
e('button', { e('span', {
className: 'chat-line__username notranslate', className: 'chat-line__username notranslate',
role: 'button',
style: { color }, style: { color },
onClick: this.ffz_user_click_handler onClick: this.ffz_user_click_handler
}, [ }, [
@ -414,8 +415,9 @@ other {# messages were deleted by a moderator.}
e('span', { e('span', {
className: 'chat-line__message--badges' className: 'chat-line__message--badges'
}, t.chat.badges.render(msg, e)), }, t.chat.badges.render(msg, e)),
e('button', { e('span', {
className: 'chat-line__username notranslate', className: 'chat-line__username notranslate',
role: 'button',
style: { color }, style: { color },
onClick: this.ffz_user_click_handler onClick: this.ffz_user_click_handler
}, [ }, [
@ -774,8 +776,9 @@ other {# messages were deleted by a moderator.}
e('span', { e('span', {
className: 'chat-line__message--badges' className: 'chat-line__message--badges'
}, t.chat.badges.render(msg, e)), }, t.chat.badges.render(msg, e)),
e('button', { e('span', {
className: 'chat-line__username notranslate', className: 'chat-line__username notranslate',
role: 'button',
style: { color }, style: { color },
onClick: this.onExtensionNameClick onClick: this.onExtensionNameClick
}, e('span', { }, e('span', {

View file

@ -65,6 +65,19 @@ export default class Scroller extends Module {
}); });
} }
updateUseKeys() {
const old_use = this.use_keys;
this.use_keys = false;
for(const act of this.chat.context.get('chat.actions.inline'))
if ( act && act.display && act.display.keys )
this.use_keys = true;
if ( this.use_keys !== old_use ) {
for(const inst of this.ChatScroller.instances)
inst && inst.ffzUpdateKeys && inst.ffzUpdateKeys();
}
}
onEnable() { onEnable() {
this.on('i18n:update', () => { this.on('i18n:update', () => {
for(const inst of this.ChatScroller.instances) for(const inst of this.ChatScroller.instances)
@ -82,6 +95,9 @@ export default class Scroller extends Module {
} }
}); });
this.chat.context.on('changed:chat.actions.inline', this.updateUseKeys, this);
this.updateUseKeys();
this.smoothScroll = this.chat.context.get('chat.scroller.smooth-scroll'); this.smoothScroll = this.chat.context.get('chat.scroller.smooth-scroll');
this.chat.context.on('changed:chat.scroller.smooth-scroll', val => { this.chat.context.on('changed:chat.scroller.smooth-scroll', val => {
this.smoothScroll = val; this.smoothScroll = val;
@ -437,6 +453,8 @@ export default class Scroller extends Module {
this.ffz_ctrl = e.ctrlKey; this.ffz_ctrl = e.ctrlKey;
this.ffz_meta = e.metaKey; this.ffz_meta = e.metaKey;
this.ffzUpdateKeys();
if ( this.ffz_outside || t.freeze < 2 ) if ( this.ffz_outside || t.freeze < 2 )
return; return;
@ -451,8 +469,43 @@ export default class Scroller extends Module {
} }
cls.prototype.ffzUpdateKeys = function() {
if ( ! this._ffz_key_update )
this._ffz_key_update = requestAnimationFrame(() => this.ffz_updateKeys());
}
cls.prototype.ffz_updateKeys = function() {
cancelAnimationFrame(this._ffz_key_update);
this._ffz_key_update = null;
if ( ! t.use_keys && this.ffz_use_keys === t.use_keys )
return;
if ( ! this.scroll || ! this.scroll.root )
return;
this.ffz_use_keys = t.use_keys;
this.scroll.root.classList.toggle('ffz--keys', t.use_keys);
const ds = this.scroll.root.dataset;
if ( ! t.use_keys ) {
delete ds.alt;
delete ds.ctrl;
delete ds.shift;
delete ds.meta;
} else {
ds.alt = ! this.ffz_outside && this.ffz_alt;
ds.ctrl = ! this.ffz_outside && this.ffz_ctrl;
ds.shift = ! this.ffz_outside && this.ffz_shift;
ds.meta = ! this.ffz_outside && this.ffz_meta;
}
}
cls.prototype.ffzMouseMove = function(e) { cls.prototype.ffzMouseMove = function(e) {
this.ffz_last_move = Date.now(); this.ffz_last_move = Date.now();
const was_outside = this.ffz_outside;
this.ffz_outside = false; this.ffz_outside = false;
if ( this._ffz_outside ) { if ( this._ffz_outside ) {
clearTimeout(this._ffz_outside); clearTimeout(this._ffz_outside);
@ -465,8 +518,13 @@ export default class Scroller extends Module {
e.ctrlKey === this.ffz_ctrl && e.ctrlKey === this.ffz_ctrl &&
e.metaKey === this.ffz_meta && e.metaKey === this.ffz_meta &&
e.screenY === this.ffz_sy && e.screenY === this.ffz_sy &&
e.screenX === this.ffz_sx) e.screenX === this.ffz_sx) {
if ( was_outside )
this.ffzUpdateKeys();
return; return;
}
this.ffz_alt = e.altKey; this.ffz_alt = e.altKey;
this.ffz_shift = e.shiftKey; this.ffz_shift = e.shiftKey;
@ -475,6 +533,8 @@ export default class Scroller extends Module {
this.ffz_sy = e.screenY; this.ffz_sy = e.screenY;
this.ffz_sx = e.screenX; this.ffz_sx = e.screenX;
this.ffzUpdateKeys();
const should_freeze = this.ffzShouldBeFrozen(), const should_freeze = this.ffzShouldBeFrozen(),
changed = should_freeze !== this.ffz_frozen; changed = should_freeze !== this.ffz_frozen;
@ -492,6 +552,7 @@ export default class Scroller extends Module {
clearTimeout(this._ffz_outside); clearTimeout(this._ffz_outside);
this._ffz_outside = setTimeout(() => this.ffzMaybeUnfreeze(), 64); this._ffz_outside = setTimeout(() => this.ffzMaybeUnfreeze(), 64);
this.ffzUpdateKeys();
} }

View file

@ -61,9 +61,7 @@ export default class Layout extends Module {
const ratio = size.width / size.height; const ratio = size.width / size.height;
return ratio <= ctx.get('layout.portrait-threshold'); return ratio <= ctx.get('layout.portrait-threshold');
}, },
changed: val => { changed: () => this.updatePortraitMode()
this.updatePortraitMode();
}
}); });
this.settings.add('layout.inject-portrait', { this.settings.add('layout.inject-portrait', {

View file

@ -120,7 +120,9 @@ export default class Dialog extends EventEmitter {
container.classList.toggle('ffz-has-dialog', visible); container.classList.toggle('ffz-has-dialog', visible);
if ( ! visible ) { if ( ! visible ) {
this._element.remove(); if ( this._element )
this._element.remove();
this.emit('hide'); this.emit('hide');
if ( this.factory ) if ( this.factory )
@ -154,7 +156,7 @@ export default class Dialog extends EventEmitter {
} }
toggleSize(event) { toggleSize(event) {
if ( ! this._visible || event && event.button !== 0 ) if ( ! this._visible || event && event.button !== 0 || ! this._element )
return; return;
this._maximized = !this._maximized; this._maximized = !this._maximized;

View file

@ -53,6 +53,55 @@
color: #fff; color: #fff;
} }
.chat-line__message {
position: relative;
}
.ffz--modifier-actions {
position: absolute;
top: 0;
left: 0;
padding: .5rem 0;
background: rgba(255,255,255,0.5);
.dark-theme &,
.tw-root--theme-dark & {
background: rgba(0,0,0,0.5);
}
.ffz-mod-icon {
margin: 0 .5rem;
display: none;
}
}
.ffz--keys {
&[data-ctrl="true"] .ffz-modifier-1,
&[data-shift="true"] .ffz-modifier-2,
&[data-shift="true"][data-ctrl="true"] .ffz-modifier-3,
&[data-alt="true"] .ffz-modifier-4,
&[data-alt="true"][data-ctrl="true"] .ffz-modifier-5,
&[data-alt="true"][data-shift="true"] .ffz-modifier-6,
&[data-alt="true"][data-ctrl="true"][data-shift="true"] .ffz-modifier-7,
&[data-meta="true"] .ffz-modifier-8,
&[data-meta="true"][data-ctrl="true"] .ffz-modifier-9,
&[data-meta="true"][data-shift="true"] .ffz-modifier-10,
&[data-meta="true"][data-shift="true"][data-ctrl="true"] .ffz-modifier-11,
&[data-meta="true"][data-alt="true"] .ffz-modifier-12,
&[data-meta="true"][data-alt="true"][data-ctrl="true"] .ffz-modifier-13,
&[data-meta="true"][data-alt="true"][data-shift="true"] .ffz-modifier-14,
&[data-meta="true"][data-alt="true"][data-ctrl="true"][data-shift="true"] .ffz-modifier-15 {
display: inline-flex;
}
}
.ffz--modifier-actions:empty {
display: none;
}
.ffz--favorite { .ffz--favorite {
position: absolute; position: absolute;