mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-10-14 06:51:58 +00:00
4.22.5
* Added: Setting to hide unrelated results from the FFZ Control Center when searching. This is now enabled by default. * Added: Setting to control whether the height of the Emote Menu is expanded. This is now disabled by default. * Changed: When searching in the FFZ Control Center, pills are displayed in the navigation tree showing how many matching results there are. * Changed: Update the method used for searching for channels and games, hopefully resulting in more accurate results. * Changed: When searching for users in an auto-complete field, display a check-mark for verified users. * Changed: When searching for users in an auto-complete field, respect the user's preference for rounded avatars. * Fixed: Lazy load Markdown when possible to save on initial download size.
This commit is contained in:
parent
ff4bb24a9a
commit
f0d68527b8
21 changed files with 435 additions and 164 deletions
22
src/std-components/autocomplete-game.vue
Normal file
22
src/std-components/autocomplete-game.vue
Normal file
|
@ -0,0 +1,22 @@
|
|||
<template functional>
|
||||
<div class="tw-pd-x-1 tw-pd-y-05">
|
||||
<div class="tw-card tw-relative">
|
||||
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row">
|
||||
<div class="ffz-card-img ffz-card-img--size-3 tw-flex-shrink-0 tw-overflow-hidden">
|
||||
<aspect :ratio="1/1.33">
|
||||
<img
|
||||
:alt="props.game.displayName"
|
||||
:src="props.game.boxArtURL"
|
||||
class="tw-image"
|
||||
>
|
||||
</aspect>
|
||||
</div>
|
||||
<div class="tw-card-body tw-overflow-hidden tw-relative">
|
||||
<p class="tw-pd-x-1">
|
||||
{{ props.game.displayName }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
35
src/std-components/autocomplete-user.vue
Normal file
35
src/std-components/autocomplete-user.vue
Normal file
|
@ -0,0 +1,35 @@
|
|||
<template functional>
|
||||
<div class="tw-pd-x-1 tw-pd-y-05">
|
||||
<div class="tw-card tw-relative">
|
||||
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row">
|
||||
<div class="ffz-card-img ffz-card-img--size-3 tw-flex-shrink-0 tw-overflow-hidden tw-avatar">
|
||||
<aspect :ratio="1">
|
||||
<img
|
||||
:alt="props.user.displayName"
|
||||
:src="props.user.profileImageURL"
|
||||
class="tw-image tw-image-avatar tw-border-radius-rounded"
|
||||
>
|
||||
</aspect>
|
||||
</div>
|
||||
<div class="tw-card-body tw-overflow-hidden tw-relative tw-flex tw-align-items-center tw-flex-grow-1">
|
||||
<div
|
||||
v-if="props.user.login && props.user.displayName && props.user.login.trim() !== props.user.displayName.trim().toLowerCase()"
|
||||
class="tw-flex tw-flex-column tw-mg-x-1 tw-flex-grow-1"
|
||||
>
|
||||
<p>{{ props.user.displayName }}</p>
|
||||
<p class="tw-font-size-8 tw-c-text-alt-2">
|
||||
({{ props.user.login }})
|
||||
</p>
|
||||
</div>
|
||||
<p v-else class="tw-mg-x-1 tw-flex-grow-1">
|
||||
{{ props.user.displayName }}
|
||||
</p>
|
||||
<figure
|
||||
v-if="props.user.roles && props.user.roles.isPartner"
|
||||
class="tw-c-text-link ffz-i-verified"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -129,6 +129,11 @@ export default {
|
|||
logger: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
allowFilter: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -172,7 +177,7 @@ export default {
|
|||
if ( this.errored )
|
||||
return null;
|
||||
|
||||
if ( ! this.search || ! this.search.length )
|
||||
if ( ! this.search || ! this.search.length || ! this.allowFilter )
|
||||
return this.cachedItems;
|
||||
|
||||
const needle = this.search.toLowerCase();
|
||||
|
@ -180,6 +185,9 @@ export default {
|
|||
if ( typeof item.displayName === 'string' && item.displayName.toLowerCase().includes(needle) )
|
||||
return true;
|
||||
|
||||
if ( typeof item.login === 'string' && item.login.toLowerCase().includes(needle) )
|
||||
return true;
|
||||
|
||||
if ( typeof item.label === 'string' && item.label.toLowerCase().includes(needle) )
|
||||
return true;
|
||||
|
||||
|
|
|
@ -5,21 +5,31 @@
|
|||
|
||||
<script>
|
||||
|
||||
import getMD from 'utilities/markdown';
|
||||
import awaitMD, {getMD} from 'utilities/markdown';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
source: String
|
||||
},
|
||||
|
||||
computed: {
|
||||
md() {
|
||||
return getMD();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
md: getMD()
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
output() {
|
||||
if ( ! this.md )
|
||||
return '';
|
||||
|
||||
return this.md.render(this.source);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if ( ! this.md )
|
||||
awaitMD().then(md => this.md = md);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
@keyup.down="nextTab"
|
||||
>
|
||||
<div
|
||||
v-for="(i, idx) in item.tabs"
|
||||
v-for="(i, idx) in visibleTabs"
|
||||
:id="'tab-for-' + i.full_key"
|
||||
:key="i.full_key"
|
||||
:aria-selected="selected === idx"
|
||||
|
@ -30,7 +30,8 @@
|
|||
@click="select(idx)"
|
||||
>
|
||||
{{ t(i.i18n_key, i.title) }}
|
||||
<span v-if="i.unseen > 0" class="tw-pill">{{ i.unseen }}</span>
|
||||
<span v-if="filter" class="ffz-pill ffz-pill--overlay">{{ countMatches(i) }}</span>
|
||||
<span v-else-if="i.unseen" class="ffz-pill ffz-pill--overlay">{{ i.unseen }}</span>
|
||||
</div>
|
||||
</header>
|
||||
<section
|
||||
|
@ -45,7 +46,7 @@
|
|||
{{ t(tab.desc_i18n_key, tab.description) }}
|
||||
</section>
|
||||
<div
|
||||
v-for="i in tab.contents"
|
||||
v-for="i in visibleContents"
|
||||
:key="i.full_key"
|
||||
:class="{'ffz-unmatched-item': showing && ! shouldShow(i)}"
|
||||
>
|
||||
|
@ -79,6 +80,26 @@ export default {
|
|||
|
||||
tab() {
|
||||
return this.item.tabs[this.selected];
|
||||
},
|
||||
|
||||
visibleTabs() {
|
||||
if ( ! this.item || ! this.item.tabs )
|
||||
return [];
|
||||
|
||||
if ( ! this.context.matches_only )
|
||||
return this.item.tabs;
|
||||
|
||||
return this.item.tabs.filter(tab => this.shouldShow(tab));
|
||||
},
|
||||
|
||||
visibleContents() {
|
||||
if ( ! this.tab || ! this.tab.contents )
|
||||
return [];
|
||||
|
||||
if ( ! this.context.matches_only )
|
||||
return this.tab.contents;
|
||||
|
||||
return this.tab.contents.filter(item => this.shouldShow(item));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -97,6 +118,31 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
countMatches(item, seen) {
|
||||
if ( ! this.filter || ! this.filter.length || ! item )
|
||||
return 0;
|
||||
|
||||
if ( seen && seen.has(item) )
|
||||
return 0;
|
||||
|
||||
if ( ! seen )
|
||||
seen = new Set;
|
||||
|
||||
seen.add(item);
|
||||
|
||||
let count = 0;
|
||||
|
||||
for(const key of ['tabs', 'contents', 'items'])
|
||||
if ( item[key] )
|
||||
for(const thing of item[key])
|
||||
count += this.countMatches(thing, seen);
|
||||
|
||||
if ( item.setting && item.search_terms && item.search_terms.includes(this.filter) )
|
||||
count++;
|
||||
|
||||
return count;
|
||||
},
|
||||
|
||||
focus() {
|
||||
this.$el.querySelector('header').focus();
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue