mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-28 13:38:30 +00:00
4.13.3
* Changed: Shift-Click the FFZ Control Center icon in the top navigation to open the Control Center in a new window. * Fixed: Unnecessary localization calls for add-ons. * Fixed: Show an in-page notification rather than an alert box if the FFZ Control Center fails to load. * Fixed: Adding an event to an EventListener while the event firing potentially leading to an infinite loop. * Fixed: Pluralization rules for Ukrainian.
This commit is contained in:
parent
8c7e03119f
commit
02efd61f00
7 changed files with 180 additions and 118 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.13.2",
|
"version": "4.13.3",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -175,10 +175,17 @@ export default class AddonManager extends Module {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ( this.i18n.locale !== 'en' ) {
|
if ( this.i18n.locale !== 'en' ) {
|
||||||
terms.add(this.i18n.t(addon.name_i18n, addon.name));
|
if ( addon.name_i18n )
|
||||||
terms.add(this.i18n.t(addon.short_name_i18n, addon.short_name));
|
terms.add(this.i18n.t(addon.name_i18n, addon.name));
|
||||||
terms.add(this.i18n.t(addon.author_i18n, addon.author));
|
|
||||||
terms.add(this.i18n.t(addon.description_i18n, addon.description));
|
if ( addon.short_name_i18n )
|
||||||
|
terms.add(this.i18n.t(addon.short_name_i18n, addon.short_name));
|
||||||
|
|
||||||
|
if ( addon.author_i18n )
|
||||||
|
terms.add(this.i18n.t(addon.author_i18n, addon.author));
|
||||||
|
|
||||||
|
if ( addon.description_i18n )
|
||||||
|
terms.add(this.i18n.t(addon.description_i18n, addon.description));
|
||||||
}
|
}
|
||||||
|
|
||||||
addon.search_terms = [...terms].map(term => term ? term.toLocaleLowerCase() : '').join('\n');
|
addon.search_terms = [...terms].map(term => term ? term.toLocaleLowerCase() : '').join('\n');
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div class="ffz--i18n-entry tw-pd-x-1 tw-pd-y-05 tw-border-b">
|
<div class="ffz--i18n-entry tw-pd-x-1 tw-pd-y-05 tw-border-b">
|
||||||
<div class="tw-flex tw-full-width">
|
<div class="tw-flex tw-full-width">
|
||||||
<div class="ffz--i18n-sub-entry tw-mg-r-05 tw-flex-grow-1 tw-c-text-alt tw-mg-b-2">
|
<div class="ffz--i18n-sub-entry tw-mg-r-05 tw-c-text-alt tw-mg-b-2">
|
||||||
<div class="tw-font-size-7 tw-c-text-alt-2 tw-pd-b-05 tw-strong tw-upcase tw-ellipsis" :title="entry.key">
|
<div class="tw-font-size-7 tw-c-text-alt-2 tw-pd-b-05 tw-strong tw-upcase tw-ellipsis" :title="entry.key">
|
||||||
{{ entry.key }}
|
{{ entry.key }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
v-if="open"
|
v-if="open"
|
||||||
class="tw-flex tw-full-width tw-mg-t-05"
|
class="tw-flex tw-full-width tw-mg-t-05"
|
||||||
>
|
>
|
||||||
<div class="ffz--i18n-sub-entry tw-mg-r-05 tw-flex-grow-1 tw-c-text-alt tw-mg-b-2">
|
<div class="ffz--i18n-sub-entry tw-mg-r-05 tw-c-text-alt tw-mg-b-2">
|
||||||
<div class="tw-font-size-7 tw-c-text-alt-2 tw-pd-b-05 tw-strong tw-upcase tw-ellipsis">
|
<div class="tw-font-size-7 tw-c-text-alt-2 tw-pd-b-05 tw-strong tw-upcase tw-ellipsis">
|
||||||
{{ t('i18n.ui.preview', 'Preview') }}
|
{{ t('i18n.ui.preview', 'Preview') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -332,6 +332,7 @@ export default {
|
||||||
|
|
||||||
this.listen('i18n:got-keys', this.grabKeys, this);
|
this.listen('i18n:got-keys', this.grabKeys, this);
|
||||||
this.listen('i18n:loaded', this.grabKeys, this);
|
this.listen('i18n:loaded', this.grabKeys, this);
|
||||||
|
this.listen('i18n:strings-loaded', this.grabKeys, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -351,6 +352,7 @@ export default {
|
||||||
|
|
||||||
this.unlisten('i18n:got-keys', this.grabKeys, this);
|
this.unlisten('i18n:got-keys', this.grabKeys, this);
|
||||||
this.unlisten('i18n:loaded', this.grabKeys, this);
|
this.unlisten('i18n:loaded', this.grabKeys, this);
|
||||||
|
this.unlisten('i18n:strings-loaded', this.grabKeys, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -22,6 +22,7 @@ export default class MenuButton extends SiteModule {
|
||||||
this._has_update = false;
|
this._has_update = false;
|
||||||
this._important_update = false;
|
this._important_update = false;
|
||||||
this._new_settings = 0;
|
this._new_settings = 0;
|
||||||
|
this._error = null;
|
||||||
|
|
||||||
this.settings.add('ffz.show-new-settings', {
|
this.settings.add('ffz.show-new-settings', {
|
||||||
default: true,
|
default: true,
|
||||||
|
@ -39,6 +40,22 @@ export default class MenuButton extends SiteModule {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get has_error() {
|
||||||
|
return this._error != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get error() {
|
||||||
|
return this._error;
|
||||||
|
}
|
||||||
|
|
||||||
|
set error(val) {
|
||||||
|
if ( val === this._error )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._error = val;
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
get new_settings() {
|
get new_settings() {
|
||||||
return this._new_settings;
|
return this._new_settings;
|
||||||
}
|
}
|
||||||
|
@ -117,6 +134,10 @@ export default class MenuButton extends SiteModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
|
requestAnimationFrame(() => this._update());
|
||||||
|
}
|
||||||
|
|
||||||
|
_update() {
|
||||||
for(const inst of this.NavBar.instances)
|
for(const inst of this.NavBar.instances)
|
||||||
this.updateButton(inst);
|
this.updateButton(inst);
|
||||||
}
|
}
|
||||||
|
@ -170,7 +191,7 @@ export default class MenuButton extends SiteModule {
|
||||||
<div class="tw-inline-flex tw-relative tw-tooltip-wrapper">
|
<div class="tw-inline-flex tw-relative tw-tooltip-wrapper">
|
||||||
{btn = (<button
|
{btn = (<button
|
||||||
class="tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-button-icon tw-core-button tw-core-button--border tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative"
|
class="tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-button-icon tw-core-button tw-core-button--border tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative"
|
||||||
onClick={e => this.emit(':clicked', e, btn)} // eslint-disable-line react/jsx-no-bind
|
onClick={e => this.handleClick(e, btn)} // eslint-disable-line react/jsx-no-bind
|
||||||
>
|
>
|
||||||
<div class="tw-align-items-center tw-flex tw-flex-grow-0">
|
<div class="tw-align-items-center tw-flex tw-flex-grow-0">
|
||||||
<span class="tw-button-icon__icon">
|
<span class="tw-button-icon__icon">
|
||||||
|
@ -178,7 +199,24 @@ export default class MenuButton extends SiteModule {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</button>)}
|
</button>)}
|
||||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
{this.has_error && (<div class="tw-absolute tw-balloon tw-balloon--down tw-balloon--lg tw-balloon--right tw-block">
|
||||||
|
<div class="tw-border-radius-large tw-c-background-base tw-c-text-inherit tw-elevation-4 tw-pd-1">
|
||||||
|
<div class="tw-flex tw-align-items-center">
|
||||||
|
<div class="tw-flex-grow-1">
|
||||||
|
{ this.error.i18n ? this.i18n.t(this.error.i18n, this.error.text) : this.error.text }
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="tw-button-icon tw-mg-l-05 tw-relative tw-tooltip-wrapper"
|
||||||
|
onClick={() => this.error = null} // eslint-disable-line react/jsx-no-bind
|
||||||
|
>
|
||||||
|
<span class="tw-button-icon__icon">
|
||||||
|
<figure class="ffz-i-cancel" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>)}
|
||||||
|
{! this.has_error && (<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||||
{this.i18n.t('site.menu_button', 'FrankerFaceZ Control Center')}
|
{this.i18n.t('site.menu_button', 'FrankerFaceZ Control Center')}
|
||||||
{this.has_update && (<div class="tw-mg-t-1">
|
{this.has_update && (<div class="tw-mg-t-1">
|
||||||
{this.i18n.t('site.menu_button.update-desc', 'There is an update available. Please refresh your page.')}
|
{this.i18n.t('site.menu_button.update-desc', 'There is an update available. Please refresh your page.')}
|
||||||
|
@ -198,7 +236,7 @@ export default class MenuButton extends SiteModule {
|
||||||
{this.addons.has_dev && (<div class="tw-mg-t-1">
|
{this.addons.has_dev && (<div class="tw-mg-t-1">
|
||||||
{this.i18n.t('site.menu_button.addon-dev-desc', 'You have loaded add-on data from a local development server.')}
|
{this.i18n.t('site.menu_button.addon-dev-desc', 'You have loaded add-on data from a local development server.')}
|
||||||
</div>)}
|
</div>)}
|
||||||
</div>
|
</div>)}
|
||||||
</div>
|
</div>
|
||||||
{this.has_update && (<div class="ffz-menu__extra-pill tw-absolute">
|
{this.has_update && (<div class="ffz-menu__extra-pill tw-absolute">
|
||||||
<div class={`tw-pill ${this.important_update ? 'tw-pill--notification' : ''}`}>
|
<div class={`tw-pill ${this.important_update ? 'tw-pill--notification' : ''}`}>
|
||||||
|
@ -215,71 +253,22 @@ export default class MenuButton extends SiteModule {
|
||||||
{pill}
|
{pill}
|
||||||
</div>
|
</div>
|
||||||
</div>)}
|
</div>)}
|
||||||
</div>)
|
</div>);
|
||||||
|
|
||||||
/*el = (<div class="ffz-top-nav tw-align-self-center tw-flex-grow-0 tw-flex-shrink-0 tw-flex-nowrap tw-pd-r-1 tw-pd-l-05">
|
|
||||||
{btn = (<button
|
|
||||||
class="tw-button-icon tw-button-icon--overlay tw-button-icon--large"
|
|
||||||
onClick={e => this.emit(':clicked', e, btn)} //eslint-disable-line react/jsx-no-bind
|
|
||||||
>
|
|
||||||
<div class="tw-tooltip-wrapper">
|
|
||||||
<span class="tw-button-icon__icon">
|
|
||||||
<figure class="ffz-i-zreknarf" />
|
|
||||||
</span>
|
|
||||||
{this.has_update && (<div class="ffz-menu__extra-pill tw-absolute">
|
|
||||||
<div class={`tw-pill ${this.important_update ? ' tw-pill--notification' : ''}`}>
|
|
||||||
<figure class="ffz-i-arrows-cw" />
|
|
||||||
</div>
|
|
||||||
</div>)}
|
|
||||||
{!this.has_update && DEBUG && this.addons.has_dev && (<div class="ffz-menu__extra-pill tw-absolute">
|
|
||||||
<div class="tw-pill">
|
|
||||||
{this.i18n.t('site.menu_button.dev', 'dev')}
|
|
||||||
</div>
|
|
||||||
</div>)}
|
|
||||||
{!this.has_update && DEBUG && ! this.addons.has_dev && (<div class="ffz-menu__extra-pill tw-absolute">
|
|
||||||
<div class="tw-pill">
|
|
||||||
{this.i18n.t('site.menu_button.main-dev', 'm-dev')}
|
|
||||||
</div>
|
|
||||||
</div>)}
|
|
||||||
{!this.has_update && ! DEBUG && this.addons.has_dev && (<div class="ffz-menu__extra-pill tw-absolute">
|
|
||||||
<div class="tw-pill">
|
|
||||||
{this.i18n.t('site.menu_button.addon-dev', 'a-dev')}
|
|
||||||
</div>
|
|
||||||
</div>)}
|
|
||||||
{this.has_new && ! pill && (<div class="ffz-menu__pill tw-absolute">
|
|
||||||
<div class="tw-pill">
|
|
||||||
{this.i18n.formatNumber(this.new_settings)}
|
|
||||||
</div>
|
|
||||||
</div>)}
|
|
||||||
{pill && (<div class="ffz-menu__pill tw-absolute">
|
|
||||||
<div class="tw-animation tw-animation--animate tw-animation--duration-medium tw-animation--timing-ease-in tw-animation--bounce-in">
|
|
||||||
<div class="tw-pill tw-pill--notification">
|
|
||||||
{pill}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>)}
|
|
||||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
|
||||||
{this.i18n.t('site.menu_button', 'FrankerFaceZ Control Center')}
|
|
||||||
{this.has_update && (<div class="tw-mg-t-1">
|
|
||||||
{this.i18n.t('site.menu_button.update-desc', 'There is an update available. Please refresh your page.')}
|
|
||||||
</div>)}
|
|
||||||
{this.has_new && (<div class="tw-mg-t-1">
|
|
||||||
{this.i18n.t('site.menu_button.new-desc', 'There {count,plural,one {is one new setting} other {are # new settings}}.', {count: this._new_settings})}
|
|
||||||
</div>)}
|
|
||||||
{DEBUG && (<div class="tw-mg-t-1">
|
|
||||||
{this.i18n.t('site.menu_button.main-dev-desc', 'You are running a developer build of FrankerFaceZ.')}
|
|
||||||
</div>)}
|
|
||||||
{this.addons.has_dev && (<div class="tw-mg-t-1">
|
|
||||||
{this.i18n.t('site.menu_button.addon-dev-desc', 'You have loaded add-on data from a local development server.')}
|
|
||||||
</div>)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>)}
|
|
||||||
</div>);*/
|
|
||||||
|
|
||||||
container.insertBefore(el, container.lastElementChild);
|
container.insertBefore(el, container.lastElementChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleClick(event, btn) {
|
||||||
|
if ( event.shiftKey ) {
|
||||||
|
if ( DEBUG && event.ctrlKey )
|
||||||
|
return requestAnimationFrame(() => this.i18n.openUI());
|
||||||
|
|
||||||
|
return this.resolve('main_menu').openPopout();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit(':clicked', event, btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
loadMenu(event, btn) {
|
loadMenu(event, btn) {
|
||||||
const cl = btn && btn.classList;
|
const cl = btn && btn.classList;
|
||||||
|
@ -292,10 +281,12 @@ export default class MenuButton extends SiteModule {
|
||||||
|
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
this.log.capture(err);
|
this.log.capture(err);
|
||||||
|
|
||||||
// TODO: Show a proper dialog and not an alert.
|
|
||||||
this.log.error('Error enabling main menu.', err);
|
this.log.error('Error enabling main menu.', err);
|
||||||
alert('There was an error displaying the menu.'); // eslint-disable-line no-alert
|
|
||||||
|
this.error = {
|
||||||
|
i18n: 'site.menu_button.error',
|
||||||
|
text: 'There was an error loading the FFZ Control Center. Please refresh and try again.'
|
||||||
|
};
|
||||||
|
|
||||||
if ( cl )
|
if ( cl )
|
||||||
cl.remove('loading');
|
cl.remove('loading');
|
||||||
|
|
|
@ -25,6 +25,7 @@ String.prototype.toSnakeCase = function() {
|
||||||
export class EventEmitter {
|
export class EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.__listeners = {};
|
this.__listeners = {};
|
||||||
|
this.__running = new Set;
|
||||||
this.__dead_events = 0;
|
this.__dead_events = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,6 +94,9 @@ export class EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
off(event, fn, ctx) {
|
off(event, fn, ctx) {
|
||||||
|
if ( this.__running.has(event) )
|
||||||
|
throw new Error(`concurrent modification: tried removing event listener while event is running`);
|
||||||
|
|
||||||
let list = this.__listeners[event];
|
let list = this.__listeners[event];
|
||||||
if ( ! list )
|
if ( ! list )
|
||||||
return;
|
return;
|
||||||
|
@ -121,14 +125,23 @@ export class EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
emitUnsafe(event, ...args) {
|
emitUnsafe(event, ...args) {
|
||||||
const list = this.__listeners[event];
|
let list = this.__listeners[event];
|
||||||
if ( ! list )
|
if ( ! list )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if ( this.__running.has(event) )
|
||||||
|
throw new Error(`concurrent access: tried to emit event while event is running`);
|
||||||
|
|
||||||
// Track removals separately to make iteration over the event list
|
// Track removals separately to make iteration over the event list
|
||||||
// much, much simpler.
|
// much, much simpler.
|
||||||
const removed = new Set;
|
const removed = new Set;
|
||||||
|
|
||||||
|
// Set the current list of listeners to null because we don't want
|
||||||
|
// to enter some kind of loop if a new listener is added as the result
|
||||||
|
// of an existing listener.
|
||||||
|
this.__listeners[event] = null;
|
||||||
|
this.__running.add(event);
|
||||||
|
|
||||||
for(const item of list) {
|
for(const item of list) {
|
||||||
const [fn, ctx, ttl] = item,
|
const [fn, ctx, ttl] = item,
|
||||||
ret = fn.apply(ctx, args);
|
ret = fn.apply(ctx, args);
|
||||||
|
@ -146,33 +159,50 @@ export class EventEmitter {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove any dead listeners from the list.
|
||||||
if ( removed.size ) {
|
if ( removed.size ) {
|
||||||
// Re-grab the list to make sure it wasn't removed mid-iteration.
|
for(const item of removed) {
|
||||||
const new_list = this.__listeners[event];
|
const idx = list.indexOf(item);
|
||||||
if ( new_list ) {
|
if ( idx !== -1 )
|
||||||
for(const item of removed) {
|
list.splice(idx, 1);
|
||||||
const idx = new_list.indexOf(item);
|
|
||||||
if ( idx !== -1 )
|
|
||||||
new_list.splice(idx, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! list.length ) {
|
|
||||||
this.__listeners[event] = null;
|
|
||||||
this.__dead_events++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Were more listeners added while we were running? Just combine
|
||||||
|
// the two lists if so.
|
||||||
|
if ( this.__listeners[event] )
|
||||||
|
list = list.concat(this.__listeners[event]);
|
||||||
|
|
||||||
|
// If we have items, store the list back. Otherwise, mark that we
|
||||||
|
// have a dead listener.
|
||||||
|
if ( list.length )
|
||||||
|
this.__listeners[event] = list;
|
||||||
|
else {
|
||||||
|
this.__listeners[event] = null;
|
||||||
|
this.__dead_events++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__running.delete(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(event, ...args) {
|
emit(event, ...args) {
|
||||||
const list = this.__listeners[event];
|
let list = this.__listeners[event];
|
||||||
if ( ! list )
|
if ( ! list )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if ( this.__running.has(event) )
|
||||||
|
throw new Error(`concurrent access: tried to emit event while event is running`);
|
||||||
|
|
||||||
// Track removals separately to make iteration over the event list
|
// Track removals separately to make iteration over the event list
|
||||||
// much, much simpler.
|
// much, much simpler.
|
||||||
const removed = new Set;
|
const removed = new Set;
|
||||||
|
|
||||||
|
// Set the current list of listeners to null because we don't want
|
||||||
|
// to enter some kind of loop if a new listener is added as the result
|
||||||
|
// of an existing listener.
|
||||||
|
this.__listeners[event] = null;
|
||||||
|
this.__running.add(event);
|
||||||
|
|
||||||
for(const item of list) {
|
for(const item of list) {
|
||||||
const [fn, ctx, ttl] = item;
|
const [fn, ctx, ttl] = item;
|
||||||
let ret;
|
let ret;
|
||||||
|
@ -196,34 +226,51 @@ export class EventEmitter {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove any dead listeners from the list.
|
||||||
if ( removed.size ) {
|
if ( removed.size ) {
|
||||||
// Re-grab the list to make sure it wasn't removed mid-iteration.
|
for(const item of removed) {
|
||||||
const new_list = this.__listeners[event];
|
const idx = list.indexOf(item);
|
||||||
if ( new_list ) {
|
if ( idx !== -1 )
|
||||||
for(const item of removed) {
|
list.splice(idx, 1);
|
||||||
const idx = new_list.indexOf(item);
|
|
||||||
if ( idx !== -1 )
|
|
||||||
new_list.splice(idx, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! list.length ) {
|
|
||||||
this.__listeners[event] = null;
|
|
||||||
this.__dead_events++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Were more listeners added while we were running? Just combine
|
||||||
|
// the two lists if so.
|
||||||
|
if ( this.__listeners[event] )
|
||||||
|
list = list.concat(this.__listeners[event]);
|
||||||
|
|
||||||
|
// If we have items, store the list back. Otherwise, mark that we
|
||||||
|
// have a dead listener.
|
||||||
|
if ( list.length )
|
||||||
|
this.__listeners[event] = list;
|
||||||
|
else {
|
||||||
|
this.__listeners[event] = null;
|
||||||
|
this.__dead_events++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__running.delete(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async emitAsync(event, ...args) {
|
async emitAsync(event, ...args) {
|
||||||
const list = this.__listeners[event];
|
let list = this.__listeners[event];
|
||||||
if ( ! list )
|
if ( ! list )
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
|
if ( this.__running.has(event) )
|
||||||
|
throw new Error(`concurrent access: tried to emit event while event is running`);
|
||||||
|
|
||||||
// Track removals separately to make iteration over the event list
|
// Track removals separately to make iteration over the event list
|
||||||
// much, much simpler.
|
// much, much simpler.
|
||||||
const removed = new Set,
|
const removed = new Set,
|
||||||
promises = [];
|
promises = [];
|
||||||
|
|
||||||
|
// Set the current list of listeners to null because we don't want
|
||||||
|
// to enter some kind of loop if a new listener is added as the result
|
||||||
|
// of an existing listener.
|
||||||
|
this.__listeners[event] = null;
|
||||||
|
this.__running.add(event);
|
||||||
|
|
||||||
for(const item of list) {
|
for(const item of list) {
|
||||||
const [fn, ctx] = item;
|
const [fn, ctx] = item;
|
||||||
let ret;
|
let ret;
|
||||||
|
@ -260,23 +307,31 @@ export class EventEmitter {
|
||||||
|
|
||||||
const out = await Promise.all(promises);
|
const out = await Promise.all(promises);
|
||||||
|
|
||||||
|
// Remove any dead listeners from the list.
|
||||||
if ( removed.size ) {
|
if ( removed.size ) {
|
||||||
// Re-grab the list to make sure it wasn't removed mid-iteration.
|
for(const item of removed) {
|
||||||
const new_list = this.__listeners[event];
|
const idx = list.indexOf(item);
|
||||||
if ( new_list ) {
|
if ( idx !== -1 )
|
||||||
for(const item of removed) {
|
list.splice(idx, 1);
|
||||||
const idx = new_list.indexOf(item);
|
|
||||||
if ( idx !== -1 )
|
|
||||||
new_list.splice(idx, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! list.length ) {
|
|
||||||
this.__listeners[event] = null;
|
|
||||||
this.__dead_events++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Were more listeners added while we were running? Just combine
|
||||||
|
// the two lists if so.
|
||||||
|
if ( this.__listeners[event] )
|
||||||
|
list = list.concat(this.__listeners[event]);
|
||||||
|
|
||||||
|
// If we have items, store the list back. Otherwise, mark that we
|
||||||
|
// have a dead listener.
|
||||||
|
if ( list.length )
|
||||||
|
this.__listeners[event] = list;
|
||||||
|
else {
|
||||||
|
this.__listeners[event] = null;
|
||||||
|
this.__dead_events++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__running.delete(event);
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -488,7 +488,7 @@ const CARDINAL_TO_LANG = {
|
||||||
hebrew: ['he'],
|
hebrew: ['he'],
|
||||||
persian: ['fa'],
|
persian: ['fa'],
|
||||||
french: ['fr', 'pt'],
|
french: ['fr', 'pt'],
|
||||||
russian: ['ru']
|
russian: ['ru','uk']
|
||||||
}
|
}
|
||||||
|
|
||||||
const CARDINAL_TYPES = {
|
const CARDINAL_TYPES = {
|
||||||
|
@ -536,7 +536,8 @@ const ORDINAL_TO_LANG = {
|
||||||
hungarian: ['hu'],
|
hungarian: ['hu'],
|
||||||
italian: ['it'],
|
italian: ['it'],
|
||||||
one: ['fr', 'lo', 'ms'],
|
one: ['fr', 'lo', 'ms'],
|
||||||
swedish: ['sv']
|
swedish: ['sv'],
|
||||||
|
ukranian: ['uk']
|
||||||
};
|
};
|
||||||
|
|
||||||
const ORDINAL_TYPES = {
|
const ORDINAL_TYPES = {
|
||||||
|
@ -551,6 +552,12 @@ const ORDINAL_TYPES = {
|
||||||
return 5;
|
return 5;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ukranian(n) {
|
||||||
|
const n1 = n % 10, n2 = n % 100;
|
||||||
|
if ( n1 === 3 && n2 !== 13 ) return 3;
|
||||||
|
return 5;
|
||||||
|
},
|
||||||
|
|
||||||
hungarian: n => (n === 1 || n === 5) ? 1 : 5,
|
hungarian: n => (n === 1 || n === 5) ? 1 : 5,
|
||||||
italian: n => (n === 11 || n === 8 || n === 80 || n === 800) ? 4 : 5,
|
italian: n => (n === 11 || n === 8 || n === 80 || n === 800) ? 4 : 5,
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue