mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-02 16:08:31 +00:00
4.0.0-rc19.1
* Changed: Remember which sections of the settings menu a user has expanded and restore that state when re-opening the menu. * Changed: Begin tracking which settings a user has seen, so that a future update can begin highlighting newly added settings. * Changed: Clean up the new localization code a bit more.
This commit is contained in:
parent
35316b8827
commit
3e9e1b2ede
21 changed files with 242 additions and 38 deletions
10
src/i18n.js
10
src/i18n.js
|
@ -205,7 +205,7 @@ export class TranslationManager extends Module {
|
|||
if ( locale === 'en' )
|
||||
return {};
|
||||
|
||||
if ( locale === 'de' )
|
||||
/*if ( locale === 'de' )
|
||||
return {
|
||||
site: {
|
||||
menu_button: 'FrankerFaceZ Leitstelle'
|
||||
|
@ -272,8 +272,8 @@ export class TranslationManager extends Module {
|
|||
_: 'Erweiterung'
|
||||
},
|
||||
|
||||
'inherited-from': 'Vererbt von: %{title}',
|
||||
'overridden-by': 'Überschrieben von: %{title}'
|
||||
'inherited-from': 'Vererbt von: {title}',
|
||||
'overridden-by': 'Überschrieben von: {title}'
|
||||
},
|
||||
|
||||
'main-menu': {
|
||||
|
@ -306,7 +306,7 @@ export class TranslationManager extends Module {
|
|||
|
||||
'main-menu': {
|
||||
search: '検索設定',
|
||||
version: 'バージョン%{version}',
|
||||
version: 'バージョン{version}',
|
||||
|
||||
about: {
|
||||
_: '約',
|
||||
|
@ -314,7 +314,7 @@ export class TranslationManager extends Module {
|
|||
support: '対応'
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
const resp = await fetch(`${SERVER}/script/i18n/${locale}.json`);
|
||||
if ( ! resp.ok ) {
|
||||
|
|
|
@ -149,7 +149,7 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`
|
|||
FrankerFaceZ.Logger = Logger;
|
||||
|
||||
const VER = FrankerFaceZ.version_info = {
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc19',
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc19.1',
|
||||
commit: __git_commit__,
|
||||
build: __webpack_hash__,
|
||||
toString: () =>
|
||||
|
|
|
@ -39,6 +39,16 @@ export default class Actions extends Module {
|
|||
type: 'array_merge',
|
||||
always_inherit: true,
|
||||
|
||||
// Clean up after Vue being stupid.
|
||||
process(ctx, val) {
|
||||
if ( Array.isArray(val) )
|
||||
for(const entry of val)
|
||||
if ( entry.i18n && typeof entry.i18n !== 'string' )
|
||||
delete entry.i18n;
|
||||
|
||||
return val;
|
||||
},
|
||||
|
||||
ui: {
|
||||
path: 'Chat > Actions > Reasons >> Custom Reasons',
|
||||
component: 'chat-reasons',
|
||||
|
|
|
@ -21,7 +21,7 @@ export const open_url = {
|
|||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-url.vue'),
|
||||
|
||||
title: 'Open URL',
|
||||
description: '%{options.url}',
|
||||
description: '{options.url}',
|
||||
|
||||
tooltip(data) {
|
||||
const url = this.replaceVariables(data.options.url, data);
|
||||
|
@ -67,7 +67,7 @@ export const chat = {
|
|||
},
|
||||
|
||||
title: 'Chat Command',
|
||||
description: '%{options.command}',
|
||||
description: '{options.command}',
|
||||
|
||||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-chat.vue'),
|
||||
|
||||
|
@ -183,7 +183,7 @@ export const timeout = {
|
|||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-timeout.vue'),
|
||||
|
||||
title: 'Timeout User',
|
||||
description: '{options.duration,number} second%{options.duration,en_plural}',
|
||||
description: '{options.duration,number} second{options.duration,en_plural}',
|
||||
|
||||
reason_text(data) {
|
||||
return this.i18n.t('chat.actions.timeout-reason',
|
||||
|
|
|
@ -175,7 +175,7 @@ export const Videos = {
|
|||
});
|
||||
|
||||
else if ( game )
|
||||
desc_1 = this.i18n.t('clip.desc.1.playing', '{user} playing %{game}', {
|
||||
desc_1 = this.i18n.t('clip.desc.1.playing', '{user} playing {game}', {
|
||||
user,
|
||||
game: game_display
|
||||
});
|
||||
|
|
|
@ -65,6 +65,8 @@
|
|||
:modal="nav"
|
||||
:filter="filter"
|
||||
@change-item="changeItem"
|
||||
@mark-seen="markSeen"
|
||||
@mark-expanded="markExpanded"
|
||||
@navigate="navigate"
|
||||
/>
|
||||
</simplebar>
|
||||
|
@ -97,6 +99,7 @@
|
|||
:item="currentItem"
|
||||
:filter="filter"
|
||||
@change-item="changeItem"
|
||||
@mark-seen="markSeen"
|
||||
@navigate="navigate"
|
||||
/>
|
||||
</simplebar>
|
||||
|
@ -171,6 +174,8 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
this.markSeen(item);
|
||||
|
||||
this.currentItem = item;
|
||||
let current = item;
|
||||
while(current = current.parent) // eslint-disable-line no-cond-assign
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
>
|
||||
<a href="#" @click="$emit('change-item', i, false)">
|
||||
{{ t(i.i18n_key, i.title, i) }}
|
||||
<span v-if="i.unseen" class="tw-pill">{{ i.unseen }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -62,6 +63,7 @@
|
|||
:item="i"
|
||||
:filter="filter"
|
||||
@change-item="changeItem"
|
||||
@mark-seen="markSeen"
|
||||
@navigate="navigate"
|
||||
/>
|
||||
</div>
|
||||
|
@ -94,6 +96,10 @@ export default {
|
|||
return item.search_terms.includes(this.filter);
|
||||
},
|
||||
|
||||
markSeen(item) {
|
||||
this.$emit('mark-seen', item);
|
||||
},
|
||||
|
||||
changeItem(item) {
|
||||
this.$emit('change-item', item);
|
||||
},
|
||||
|
|
|
@ -38,6 +38,9 @@
|
|||
<span v-if="item.pill" class="tw-pill">
|
||||
{{ item.pill_i18n_key ? t(item.pill_i18n_key, item.pill, item) : item.pill }}
|
||||
</span>
|
||||
<span v-else-if="item.unseen" class="tw-pill">
|
||||
{{ item.unseen }}
|
||||
</span>
|
||||
</div>
|
||||
<menu-tree
|
||||
v-if="item.items && item.expanded"
|
||||
|
@ -46,6 +49,8 @@
|
|||
:modal="item.items"
|
||||
:filter="filter"
|
||||
@change-item="i => $emit('change-item', i)"
|
||||
@mark-seen="i => $emit('mark-seen', i)"
|
||||
@mark-expanded="i => $emit('mark-expanded', i)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -75,11 +80,16 @@ function findNextVisible(node, modal) {
|
|||
}
|
||||
|
||||
|
||||
function recursiveExpand(node) {
|
||||
function recursiveExpand(node, vue) {
|
||||
if ( ! node.expanded ) {
|
||||
node.expanded = true;
|
||||
if ( vue )
|
||||
vue.$emit('mark-expanded', node);
|
||||
}
|
||||
|
||||
if ( node.items )
|
||||
for(const item of node.items)
|
||||
recursiveExpand(item);
|
||||
recursiveExpand(item, vue);
|
||||
}
|
||||
|
||||
|
||||
|
@ -121,12 +131,13 @@ export default {
|
|||
else if ( this.currentItem === item )
|
||||
item.expanded = false;
|
||||
|
||||
this.$emit('mark-expanded', item);
|
||||
this.$emit('change-item', item);
|
||||
},
|
||||
|
||||
expandAll() {
|
||||
for(const item of this.modal)
|
||||
recursiveExpand(item);
|
||||
recursiveExpand(item, this);
|
||||
},
|
||||
|
||||
prevItem() {
|
||||
|
@ -164,9 +175,10 @@ export default {
|
|||
|
||||
const i = this.currentItem;
|
||||
|
||||
if ( i.expanded && i.items )
|
||||
if ( i.expanded && i.items ) {
|
||||
i.expanded = false;
|
||||
else if ( i.parent )
|
||||
this.$emit('mark-expanded', i);
|
||||
} else if ( i.parent )
|
||||
this.$emit('change-item', i.parent);
|
||||
},
|
||||
|
||||
|
@ -176,11 +188,13 @@ export default {
|
|||
const i = this.currentItem;
|
||||
if ( i.expanded && i.items )
|
||||
this.$emit('change-item', i.items[0]);
|
||||
else
|
||||
else {
|
||||
i.expanded = true;
|
||||
this.$emit('mark-expanded', i);
|
||||
}
|
||||
|
||||
if ( event.ctrlKey )
|
||||
recursiveExpand(this.currentItem);
|
||||
recursiveExpand(this.currentItem, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
<label :for="item.full_key" class="tw-checkbox__label">
|
||||
{{ t(item.i18n_key, item.title, item) }}
|
||||
<span v-if="unseen" class="tw-pill">{{ t('setting.new', 'New') }}</span>
|
||||
</label>
|
||||
|
||||
<button
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<div class="tw-flex tw-align-items-start">
|
||||
<label :for="item.full_key" class="tw-mg-y-05">
|
||||
{{ t(item.i18n_key, item.title, item) }}
|
||||
<span v-if="unseen" class="tw-pill">{{ t('setting.new', 'New') }}</span>
|
||||
</label>
|
||||
|
||||
<div class="tw-flex tw-flex-column tw-mg-05">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="tw-input">
|
||||
<header>
|
||||
{{ t(item.i18n_key, item.title, item) }}
|
||||
<span v-if="unseen" class="tw-pill">{{ t('setting.new', 'New') }}</span>
|
||||
</header>
|
||||
<section
|
||||
v-if="item.description"
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<div class="tw-flex tw-align-items-center">
|
||||
<label :for="item.full_key">
|
||||
{{ t(item.i18n_key, item.title, item) }}
|
||||
<span v-if="unseen" class="tw-pill">{{ t('setting.new', 'New') }}</span>
|
||||
</label>
|
||||
|
||||
<select
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<div class="tw-flex tw-align-items-center">
|
||||
<label :for="item.full_key">
|
||||
{{ t(item.i18n_key, item.title, item) }}
|
||||
<span v-if="unseen" class="tw-pill">{{ t('setting.new', 'New') }}</span>
|
||||
</label>
|
||||
|
||||
<input
|
||||
|
|
|
@ -41,6 +41,7 @@ export default class MainMenu extends Module {
|
|||
|
||||
this.dialog = new Dialog(() => this.buildDialog());
|
||||
this.has_update = false;
|
||||
this.opened = false;
|
||||
|
||||
this.settings.addUI('profiles', {
|
||||
path: 'Data Management @{"sort": 1000, "profile_warning": false} > Profiles @{"profile_warning": false}',
|
||||
|
@ -102,6 +103,8 @@ export default class MainMenu extends Module {
|
|||
if ( this._vue )
|
||||
this._vue.$children[0].context.has_update = true;
|
||||
});
|
||||
|
||||
this.scheduleUpdate();
|
||||
}
|
||||
|
||||
openPopout() {
|
||||
|
@ -130,7 +133,16 @@ export default class MainMenu extends Module {
|
|||
async onEnable() {
|
||||
await this.site.awaitElement(Dialog.EXCLUSIVE);
|
||||
|
||||
this.dialog.on('hide', this.destroyDialog, this);
|
||||
this.dialog.on('show', () => {
|
||||
this.opened = true;
|
||||
this.updateButtonUnseen();
|
||||
this.emit('show')
|
||||
});
|
||||
this.dialog.on('hide', () => {
|
||||
this.emit('hide');
|
||||
this.destroyDialog();
|
||||
});
|
||||
|
||||
this.dialog.on('resize', () => {
|
||||
if ( this._vue )
|
||||
this._vue.$children[0].maximized = this.dialog.maximized
|
||||
|
@ -146,6 +158,20 @@ export default class MainMenu extends Module {
|
|||
}
|
||||
|
||||
|
||||
getUnseen() {
|
||||
const pages = this.getSettingsTree();
|
||||
if ( ! Array.isArray(pages) )
|
||||
return 0;
|
||||
|
||||
let i=0;
|
||||
for(const page of pages)
|
||||
if ( page )
|
||||
i += (page.unseen || 0);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
buildDialog() {
|
||||
if ( this._menu )
|
||||
return this._menu;
|
||||
|
@ -174,10 +200,19 @@ export default class MainMenu extends Module {
|
|||
}
|
||||
|
||||
|
||||
updateButtonUnseen() {
|
||||
const mb = this.resolve('site.menu_button');
|
||||
if ( mb )
|
||||
mb.new_settings = this.opened ? 0 : this.getUnseen();
|
||||
}
|
||||
|
||||
|
||||
updateLiveMenu() {
|
||||
clearTimeout(this._update_timer);
|
||||
this._update_timer = null;
|
||||
|
||||
this.updateButtonUnseen();
|
||||
|
||||
if ( ! this._vue || ! this._vue.$children || ! this._vue.$children[0] )
|
||||
return;
|
||||
|
||||
|
@ -222,7 +257,6 @@ export default class MainMenu extends Module {
|
|||
return;
|
||||
|
||||
const tree = this._settings_tree,
|
||||
expanded = this.settings.provider.get('settings-expanded', {}),
|
||||
tokens = def.ui.path_tokens,
|
||||
len = tokens.length;
|
||||
|
||||
|
@ -241,16 +275,13 @@ export default class MainMenu extends Module {
|
|||
full_key: key,
|
||||
sort: 0,
|
||||
parent: prefix,
|
||||
expanded: prefix === null,
|
||||
expanded: prefix == null,
|
||||
i18n_key: `setting.${key}`,
|
||||
desc_i18n_key: `setting.${key}.description`
|
||||
};
|
||||
|
||||
Object.assign(token, raw_token);
|
||||
|
||||
if ( has(expanded, key) )
|
||||
token.expanded = expanded[key];
|
||||
|
||||
prefix = key;
|
||||
}
|
||||
|
||||
|
@ -268,6 +299,8 @@ export default class MainMenu extends Module {
|
|||
this.rebuildSettingsTree();
|
||||
|
||||
const tree = this._settings_tree,
|
||||
settings_seen = this.settings.provider.get('cfg-seen', []),
|
||||
collapsed = this.settings.provider.get('cfg-collapsed'),
|
||||
|
||||
root = {},
|
||||
copies = {},
|
||||
|
@ -291,6 +324,9 @@ export default class MainMenu extends Module {
|
|||
token.parent = p_key ? parent : null;
|
||||
token.page = token.page || parent.page;
|
||||
|
||||
if ( collapsed )
|
||||
token.expanded = ! collapsed.includes(token.full_key);
|
||||
|
||||
if ( token.page && ! token.component )
|
||||
needs_component.add(token);
|
||||
|
||||
|
@ -337,6 +373,18 @@ export default class MainMenu extends Module {
|
|||
|
||||
tok.search_terms = terms.map(format_term).join('\n');
|
||||
|
||||
if ( ! settings_seen.includes(setting_key)) {
|
||||
// Mark existing settings as unseen for now.
|
||||
// Let users run this for a while to build up their cache.
|
||||
settings_seen.push(setting_key);
|
||||
|
||||
/*let i = tok;
|
||||
while(i) {
|
||||
i.unseen = (i.unseen||0) + 1;
|
||||
i = i.parent;
|
||||
}*/
|
||||
}
|
||||
|
||||
list.push(tok);
|
||||
}
|
||||
|
||||
|
@ -401,9 +449,24 @@ export default class MainMenu extends Module {
|
|||
return a.key && a.key.localeCompare(b.key);
|
||||
});
|
||||
|
||||
this.log.info(`Built Tree in ${(performance.now() - started).toFixed(5)}ms with ${Object.keys(tree).length} structure nodes and ${this._settings_count} settings nodes.`);
|
||||
const items = root.items || [];
|
||||
items.keys = copies;
|
||||
|
||||
// Save for now, since we just want to mark everything as seen.
|
||||
this.settings.provider.set('cfg-seen', settings_seen);
|
||||
|
||||
if ( ! collapsed ) {
|
||||
const new_collapsed = [];
|
||||
for(const key of Object.keys(copies)) {
|
||||
const item = copies[key];
|
||||
if ( item && item.items && item.parent )
|
||||
new_collapsed.push(key);
|
||||
}
|
||||
|
||||
this.settings.provider.set('cfg-collapsed', new_collapsed);
|
||||
}
|
||||
|
||||
this.log.info(`Built Tree in ${(performance.now() - started).toFixed(5)}ms with ${Object.keys(tree).length} structure nodes and ${this._settings_count} settings nodes.`);
|
||||
return items;
|
||||
}
|
||||
|
||||
|
@ -560,9 +623,40 @@ export default class MainMenu extends Module {
|
|||
return _c;
|
||||
}
|
||||
|
||||
markSeen(item, seen) {
|
||||
let had_seen = true;
|
||||
if ( ! seen ) {
|
||||
had_seen = false;
|
||||
seen = this.settings.provider.get('cfg-seen', []);
|
||||
}
|
||||
|
||||
if ( Array.isArray(item.contents) ) {
|
||||
for(const child of item.contents)
|
||||
child && this.markSeen(child, seen);
|
||||
|
||||
} else if ( ! item.setting )
|
||||
return;
|
||||
|
||||
if ( ! seen.includes(item.setting) ) {
|
||||
seen.push(item.setting);
|
||||
|
||||
let i = item.parent;
|
||||
while(i) {
|
||||
i.unseen = (i.unseen || 1) - 1;
|
||||
i = i.parent;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! had_seen )
|
||||
this.settings.provider.set('cfg-seen', seen);
|
||||
}
|
||||
|
||||
getData() {
|
||||
const settings = this.getSettingsTree(),
|
||||
context = this.getContext();
|
||||
context = this.getContext(),
|
||||
current = this.has_update ? settings.keys['home.changelog'] : settings.keys['home'];
|
||||
|
||||
this.markSeen(current);
|
||||
|
||||
return {
|
||||
context,
|
||||
|
@ -571,14 +665,28 @@ export default class MainMenu extends Module {
|
|||
faded: false,
|
||||
|
||||
nav: settings,
|
||||
currentItem: this.has_update ?
|
||||
settings.keys['home.changelog'] :
|
||||
settings.keys['home'], // settings[0],
|
||||
currentItem: current,
|
||||
nav_keys: settings.keys,
|
||||
|
||||
maximized: this.dialog.maximized,
|
||||
exclusive: this.dialog.exclusive,
|
||||
|
||||
markSeen: item => this.markSeen(item),
|
||||
|
||||
markExpanded: item => {
|
||||
const collapsed = this.settings.provider.get('cfg-collapsed', []),
|
||||
included = collapsed.indexOf(item.full_key);
|
||||
|
||||
if ( item.expanded && included !== -1 )
|
||||
collapsed.splice(included, 1);
|
||||
else if ( ! item.expanded && included === -1 )
|
||||
collapsed.push(item.full_key);
|
||||
else
|
||||
return;
|
||||
|
||||
this.settings.provider.set('cfg-collapsed', collapsed);
|
||||
},
|
||||
|
||||
resize: e => ! this.dialog.exclusive && this.dialog.toggleSize(e),
|
||||
close: e => ! this.dialog.exclusive && this.dialog.toggleVisible(e),
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ export default {
|
|||
has_value: false,
|
||||
profile: null,
|
||||
|
||||
_unseen: false,
|
||||
source: null,
|
||||
source_value: undefined,
|
||||
}
|
||||
|
@ -23,6 +24,11 @@ export default {
|
|||
this._update_profile();
|
||||
this._uses_changed(ctx.uses(setting));
|
||||
|
||||
if ( this.item.unseen ) {
|
||||
this._unseen = true;
|
||||
this.item.unseen = 0;
|
||||
}
|
||||
|
||||
ctx.on(`uses_changed:${setting}`, this._uses_changed, this);
|
||||
},
|
||||
|
||||
|
@ -63,6 +69,10 @@ export default {
|
|||
return data;
|
||||
},
|
||||
|
||||
unseen() {
|
||||
return this._unseen || this.item.unseen > 0
|
||||
},
|
||||
|
||||
default_value() {
|
||||
if ( typeof this.item.default === 'function' )
|
||||
return this.item.default(this.context.context);
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
</span>
|
||||
</button>
|
||||
</header>
|
||||
<section class="tw-border-t tw-full-height tw-full-width tw-flex tw-flex-nowrap tw-overflow-hidden">
|
||||
<section class="tw-border-t tw-full-height tw-full-width tw-flex tw-overflow-hidden">
|
||||
<div v-for="(key, idx) of phrases" :key="idx" class="tw-block tw-mg-1">
|
||||
{{ key }}
|
||||
</div>
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
</span>
|
||||
<span
|
||||
v-if="userAge"
|
||||
:data-title="t('viewer-card.age-tip', 'Member Since: %{age,datetime}', {age: userAge})"
|
||||
:data-title="t('viewer-card.age-tip', 'Member Since: {age,datetime}', {age: userAge})"
|
||||
class="ffz-tooltip ffz-i-clock"
|
||||
>
|
||||
{{ t('viewer-card.age', '{age,humantime}', {age: userAge}) }}
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
v-if="user.following"
|
||||
:disabled="user.loading"
|
||||
:class="{'tw-button--disabled': user.loading}"
|
||||
:data-title="user.loading ? null : t('featured-follow.button.unfollow', 'Unfollow %{user}', {user: user.displayName})"
|
||||
:data-title="user.loading ? null : t('featured-follow.button.unfollow', 'Unfollow {user}', {user: user.displayName})"
|
||||
data-tooltip-type="html"
|
||||
class="tw-button tw-button--status tw-button--success ffz-tooltip ffz--featured-button-unfollow"
|
||||
@click="clickWithTip($event, unfollowUser, user.id)"
|
||||
|
|
|
@ -19,6 +19,7 @@ export default class MenuButton extends SiteModule {
|
|||
this._pill_content = null;
|
||||
this._has_update = false;
|
||||
this._important_update = false;
|
||||
this._new_settings = 0;
|
||||
|
||||
this.NavBar = this.fine.define(
|
||||
'nav-bar',
|
||||
|
@ -26,6 +27,22 @@ export default class MenuButton extends SiteModule {
|
|||
);
|
||||
}
|
||||
|
||||
get new_settings() {
|
||||
return this._new_settings;
|
||||
}
|
||||
|
||||
set new_settings(val) {
|
||||
if ( val === this._new_settings )
|
||||
return;
|
||||
|
||||
this._new_settings = val;
|
||||
this.update();
|
||||
}
|
||||
|
||||
get has_new() {
|
||||
return this._new_settings > 0
|
||||
}
|
||||
|
||||
get important_update() {
|
||||
return this._important_update;
|
||||
}
|
||||
|
@ -115,6 +132,11 @@ export default class MenuButton extends SiteModule {
|
|||
{this.i18n.t('site.menu_button.dev', '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">
|
||||
|
@ -127,6 +149,9 @@ export default class MenuButton extends SiteModule {
|
|||
{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.dev-desc', 'You are running a developer build of FrankerFaceZ.')}
|
||||
</div>)}
|
||||
|
|
|
@ -27,9 +27,10 @@
|
|||
:class="{'active': selected === idx, 'ffz-unmatched-item': showing && ! shouldShow(i)}"
|
||||
role="tab"
|
||||
class="tab tw-pd-y-05 tw-pd-x-1"
|
||||
@click="selected = idx"
|
||||
@click="select(idx)"
|
||||
>
|
||||
{{ t(i.i18n_key, i.title, i) }}
|
||||
<span v-if="i.unseen > 0" class="tw-pill">{{ i.unseen }}</span>
|
||||
</div>
|
||||
</header>
|
||||
<section
|
||||
|
@ -81,6 +82,10 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.markSeen()
|
||||
},
|
||||
|
||||
methods: {
|
||||
focus() {
|
||||
this.$el.querySelector('header').focus();
|
||||
|
@ -106,22 +111,37 @@ export default {
|
|||
this.lastTab();
|
||||
},
|
||||
|
||||
markSeen() {
|
||||
this.$emit('mark-seen', this.item.tabs[this.selected]);
|
||||
},
|
||||
|
||||
firstTab() {
|
||||
this.selected = 0;
|
||||
this.markSeen();
|
||||
},
|
||||
|
||||
lastTab() {
|
||||
this.selected = this.item.tabs.length - 1;
|
||||
this.markSeen();
|
||||
},
|
||||
|
||||
prevTab() {
|
||||
if ( this.selected > 0 )
|
||||
if ( this.selected > 0 ) {
|
||||
this.selected--;
|
||||
this.markSeen();
|
||||
}
|
||||
},
|
||||
|
||||
select(idx) {
|
||||
this.selected = idx;
|
||||
this.markSeen();
|
||||
},
|
||||
|
||||
nextTab() {
|
||||
if ( this.selected + 1 < this.item.tabs.length )
|
||||
if ( this.selected + 1 < this.item.tabs.length ) {
|
||||
this.selected++;
|
||||
this.markSeen();
|
||||
}
|
||||
},
|
||||
|
||||
shouldShow(item) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import dayjs from 'dayjs';
|
|||
import Parser from '@ffz/icu-msgparser';
|
||||
|
||||
import {get} from 'utilities/object';
|
||||
import {duration_to_string} from 'utilities/time';
|
||||
|
||||
|
||||
// ============================================================================
|
||||
|
@ -58,11 +59,11 @@ export const DEFAULT_TYPES = {
|
|||
return this.formatDateTime(val, node.f);
|
||||
},
|
||||
|
||||
duration(val, node) {
|
||||
|
||||
duration(val) {
|
||||
return duration_to_string(val);
|
||||
},
|
||||
|
||||
localestring(val, node) {
|
||||
localestring(val) {
|
||||
return this.toLocaleString(val);
|
||||
},
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue