mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-07 03:28:31 +00:00
4.3.1
* Fixed: Unable to see in-line chat action context menus in theater mode. * Changed: Add a new socket server to the list to take pressure off the others. * API Added: `debounce` method in `FrankerFaceZ.utilities.object`. * API Added: `<autocomplete>` Vue component for implementing text fields with auto-completion. * API Changed: Update localized Vue strings immediately when the i18n debug transformation changes. * API Changed: `<icon-picker />` now has a closed and open state. It doesn't always show the drawer of icons. * API Changed: Include the `vue-clickaway` mixin in everything.
This commit is contained in:
parent
aa25bff498
commit
21ee6fcfb7
23 changed files with 667 additions and 100 deletions
|
@ -1,51 +1,72 @@
|
|||
<template lang="html">
|
||||
<div class="ffz--icon-picker">
|
||||
<div class="tw-full-width">
|
||||
<div class="tw-search-input">
|
||||
<label :for="'icon-search$' + id" class="tw-hide-accessible">
|
||||
{{ t('setting.icon.search', 'Search for Icon') }}
|
||||
</label>
|
||||
<div class="tw-relative tw-mg-t-05">
|
||||
<div class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height tw-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||
<figure class="ffz-i-search" />
|
||||
</div>
|
||||
<input
|
||||
:id="'icon-search$' + id"
|
||||
:placeholder="t('setting.actions.icon.search', 'Search for Icon')"
|
||||
v-model="search"
|
||||
type="search"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-l-3 tw-pd-r-1 tw-pd-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
>
|
||||
<div v-on-clickaway="close" class="ffz--icon-picker tw-relative">
|
||||
<div class="tw-search-input tw-full-width">
|
||||
<label v-if="open" :for="'icon-search$' + id" class="tw-hide-accessible">{{ t('setting.icon.search', 'Search for Icon') }}</label>
|
||||
<div class="tw-relative">
|
||||
<div class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height tw-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||
<figure :class="[(isOpen || ! val || ! val.length) ? 'ffz-i-search' : val]" />
|
||||
</div>
|
||||
<input
|
||||
ref="input"
|
||||
:id="'icon-search$' + id"
|
||||
:placeholder="('setting.icon.search', 'Search for Icon')"
|
||||
:value="isOpen ? search : val"
|
||||
:class="[clearable ? 'tw-pd-r-5' : 'tw-pd-r-1']"
|
||||
type="text"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-l-3 tw-pd-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
@input="update"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@keydown.escape="open = false"
|
||||
>
|
||||
<button
|
||||
v-if="clearable"
|
||||
class="tw-absolute tw-right-0 tw-top-0 tw-button tw-button--text tw-tooltip-wrapper"
|
||||
@click="change('', false)"
|
||||
@keydown.escape="open = false"
|
||||
@focus="onFocus(false)"
|
||||
@blur="onBlur"
|
||||
>
|
||||
<span class="tw-button__text ffz-i-trash" />
|
||||
<div class="tw-tooltip tw-tooltip--up tw-tooltip--align-right">
|
||||
{{ t('setting.icon.clear', 'Clear') }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<simplebar classes="tw-c-background-alt-2 tw-border-l tw-border-r tw-border-b ffz--icon-picker__list tw-mg-b-05">
|
||||
<div v-if="visible.length" role="radiogroup" class="tw-pd-1 tw-flex tw-flex-wrap tw-justify-content-between" >
|
||||
<div
|
||||
v-for="i of visible"
|
||||
:key="i[0]"
|
||||
:aria-checked="value === i[0]"
|
||||
:class="{'tw-interactable--selected': value === i[0]}"
|
||||
:data-title="i[1]"
|
||||
class="ffz-tooltip ffz-icon tw-interactable tw-interactable--inverted"
|
||||
role="radio"
|
||||
tabindex="0"
|
||||
@keydown.space.stop.prevent=""
|
||||
@keyup.space="change(i[0])"
|
||||
@keyup.enter="change(i[0])"
|
||||
@click="change(i[0])"
|
||||
>
|
||||
<figure :class="`tw-mg-y-05 tw-mg-x-1 ${i[0]}`" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="tw-align-center tw-pd-1 tw-c-text-alt-2">
|
||||
{{ t('setting.actions.empty-search', 'no results') }}
|
||||
</div>
|
||||
</simplebar>
|
||||
</div>
|
||||
<balloon v-if="open" :dir="direction" color="background-base">
|
||||
<div ref="list">
|
||||
<simplebar classes="scrollable-area--suppress-scroll-x ffz--icon-picker__list">
|
||||
<div v-if="visible.length" role="radiogroup" class="tw-pd-1 tw-flex tw-flex-wrap tw-justify-content-between" >
|
||||
<div
|
||||
v-for="i of visible"
|
||||
:key="i[0]"
|
||||
:aria-checked="val === i[0]"
|
||||
:class="{'tw-interactable--selected': val === i[0]}"
|
||||
:data-title="i[1]"
|
||||
class="ffz-tooltip ffz-icon tw-interactable tw-interactable--inverted"
|
||||
role="radio"
|
||||
tabindex="0"
|
||||
@keydown.space.stop.prevent=""
|
||||
@keyup.space="change(i[0])"
|
||||
@keyup.enter="change(i[0])"
|
||||
@click="change(i[0])"
|
||||
@focus="onFocus(false)"
|
||||
@blur="onBlur"
|
||||
>
|
||||
<figure :class="`tw-mg-y-05 tw-mg-x-1 ${i[0]}`" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="tw-align-center tw-pd-1 tw-c-text-alt-2">
|
||||
{{ t('setting.actions.empty-search', 'no results') }}
|
||||
</div>
|
||||
</simplebar>
|
||||
</div>
|
||||
</balloon>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -53,8 +74,9 @@
|
|||
|
||||
let id = 0;
|
||||
|
||||
import {escape_regex, deep_copy} from 'utilities/object';
|
||||
import {escape_regex, deep_copy, debounce} from 'utilities/object';
|
||||
import {load, ICONS as FA_ICONS, ALIASES as FA_ALIASES} from 'utilities/font-awesome';
|
||||
import { maybeLoad } from '../utilities/font-awesome';
|
||||
|
||||
const FFZ_ICONS = [
|
||||
'cancel',
|
||||
|
@ -107,6 +129,7 @@ const FFZ_ICONS = [
|
|||
'cw',
|
||||
'up-dir',
|
||||
'up-big',
|
||||
'play',
|
||||
'link-ext',
|
||||
'twitter',
|
||||
'github',
|
||||
|
@ -140,11 +163,30 @@ const ICONS = FFZ_ICONS
|
|||
.concat(FA_ICONS.filter(x => ! FFZ_ICONS.includes(x)).map(x => [`ffz-fa fa-${x}`, FA_ALIASES[x] ? FA_ALIASES[x].join(' ') : x]));
|
||||
|
||||
export default {
|
||||
props: ['value'],
|
||||
props: {
|
||||
value: String,
|
||||
alwaysOpen: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'down'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
id: id++,
|
||||
open: false,
|
||||
val: this.value,
|
||||
search: '',
|
||||
icons: deep_copy(ICONS)
|
||||
}
|
||||
|
@ -159,25 +201,81 @@ export default {
|
|||
reg = new RegExp('(?:^|-| )' + escape_regex(search), 'i');
|
||||
|
||||
return this.icons.filter(x => reg.test(x[1]));
|
||||
},
|
||||
|
||||
isOpen() {
|
||||
return this.alwaysOpen || this.open
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
load();
|
||||
watch: {
|
||||
value() {
|
||||
this.val = this.value;
|
||||
},
|
||||
|
||||
this.$nextTick(() => {
|
||||
if ( this.value ) {
|
||||
const el = this.$el.querySelector('.tw-interactable--selected');
|
||||
if ( el )
|
||||
el.scrollIntoViewIfNeeded();
|
||||
isOpen() {
|
||||
if ( ! this.isOpen ) {
|
||||
requestAnimationFrame(() => {
|
||||
const ffz = FrankerFaceZ.get();
|
||||
if ( ffz )
|
||||
ffz.emit('tooltips:cleanup');
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
load();
|
||||
|
||||
this.$nextTick(() => {
|
||||
if ( this.val ) {
|
||||
const root = this.$refs.list,
|
||||
el = root && root.querySelector('.tw-interactable--selected');
|
||||
|
||||
if ( el )
|
||||
el.scrollIntoViewIfNeeded();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.maybeClose = debounce(this.maybeClose, 10);
|
||||
},
|
||||
|
||||
mounted() {
|
||||
maybeLoad(this.val);
|
||||
},
|
||||
|
||||
methods: {
|
||||
change(val) {
|
||||
this.value = val;
|
||||
this.$emit('input', this.value);
|
||||
update() {
|
||||
if ( this.open )
|
||||
this.search = this.$refs.input.value;
|
||||
},
|
||||
|
||||
close() {
|
||||
this.open = false;
|
||||
},
|
||||
|
||||
change(val, close = true) {
|
||||
this.val = val;
|
||||
this.$emit('input', this.val);
|
||||
if ( close )
|
||||
this.open = false;
|
||||
},
|
||||
|
||||
onFocus(open = true) {
|
||||
this.focused = true;
|
||||
if ( open )
|
||||
this.open = true;
|
||||
},
|
||||
|
||||
onBlur() {
|
||||
this.focused = false;
|
||||
this.maybeClose();
|
||||
},
|
||||
|
||||
maybeClose() {
|
||||
if ( ! this.focused )
|
||||
this.open = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue