mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-09 07:40:53 +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
Binary file not shown.
|
@ -106,6 +106,8 @@
|
||||||
|
|
||||||
<glyph glyph-name="up-big" unicode="" d="M899 308q0-28-21-50l-41-42q-22-21-51-21-30 0-50 21l-165 164v-393q0-29-20-47t-51-19h-71q-30 0-51 19t-21 47v393l-164-164q-20-21-50-21t-50 21l-42 42q-21 21-21 50 0 30 21 51l363 363q20 21 50 21 30 0 51-21l363-363q21-22 21-51z" horiz-adv-x="928.6" />
|
<glyph glyph-name="up-big" unicode="" d="M899 308q0-28-21-50l-41-42q-22-21-51-21-30 0-50 21l-165 164v-393q0-29-20-47t-51-19h-71q-30 0-51 19t-21 47v393l-164-164q-20-21-50-21t-50 21l-42 42q-21 21-21 50 0 30 21 51l363 363q20 21 50 21 30 0 51-21l363-363q21-22 21-51z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="play" unicode="" d="M772 333l-741-412q-13-7-22-2t-9 20v822q0 14 9 20t22-2l741-412q13-7 13-17t-13-17z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
<glyph glyph-name="link-ext" unicode="" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
<glyph glyph-name="link-ext" unicode="" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
<glyph glyph-name="twitter" unicode="" d="M904 622q-37-54-90-93 0-8 0-23 0-73-21-145t-64-139-103-117-144-82-181-30q-151 0-276 81 19-2 43-2 126 0 224 77-59 1-105 36t-64 89q19-3 34-3 24 0 48 6-63 13-104 62t-41 115v2q38-21 82-23-37 25-59 64t-22 86q0 49 25 91 68-83 164-133t208-55q-5 21-5 41 0 75 53 127t127 53q79 0 132-57 61 12 115 44-21-64-80-100 52 6 104 28z" horiz-adv-x="928.6" />
|
<glyph glyph-name="twitter" unicode="" d="M904 622q-37-54-90-93 0-8 0-23 0-73-21-145t-64-139-103-117-144-82-181-30q-151 0-276 81 19-2 43-2 126 0 224 77-59 1-105 36t-64 89q19-3 34-3 24 0 48 6-63 13-104 62t-41 115v2q38-21 82-23-37 25-59 64t-22 86q0 49 25 91 68-83 164-133t208-55q-5 21-5 41 0 75 53 127t127 53q79 0 132-57 61 12 115 44-21-64-80-100 52 6 104 28z" horiz-adv-x="928.6" />
|
||||||
|
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -92,7 +92,8 @@ export class TranslationManager extends Module {
|
||||||
|
|
||||||
changed: val => {
|
changed: val => {
|
||||||
this._.transformation = TRANSFORMATIONS[val];
|
this._.transformation = TRANSFORMATIONS[val];
|
||||||
this.emit(':update')
|
this.emit(':transform');
|
||||||
|
this.emit(':update');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -387,6 +388,10 @@ export class TranslationManager extends Module {
|
||||||
return this._.has(key);
|
return this._.has(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatNode(...args) {
|
||||||
|
return this._.formatNode(...args);
|
||||||
|
}
|
||||||
|
|
||||||
toLocaleString(...args) {
|
toLocaleString(...args) {
|
||||||
return this._.toLocaleString(...args);
|
return this._.toLocaleString(...args);
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,7 +151,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: 3, revision: 0,
|
major: 4, minor: 3, revision: 1,
|
||||||
commit: __git_commit__,
|
commit: __git_commit__,
|
||||||
build: __webpack_hash__,
|
build: __webpack_hash__,
|
||||||
toString: () =>
|
toString: () =>
|
||||||
|
|
|
@ -4,7 +4,11 @@
|
||||||
{{ t('setting.actions.icon', 'Icon') }}
|
{{ t('setting.actions.icon', 'Icon') }}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<icon-picker :value="value.icon" @input="change" />
|
<icon-picker
|
||||||
|
:value="value.icon"
|
||||||
|
class="tw-full-width"
|
||||||
|
@input="change"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -207,13 +207,12 @@
|
||||||
import SettingMixin from '../setting-mixin';
|
import SettingMixin from '../setting-mixin';
|
||||||
import Sortable from 'sortablejs';
|
import Sortable from 'sortablejs';
|
||||||
import {deep_copy} from 'utilities/object';
|
import {deep_copy} from 'utilities/object';
|
||||||
import {mixin as clickaway} from 'vue-clickaway';
|
|
||||||
|
|
||||||
let last_id = 0;
|
let last_id = 0;
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [clickaway, SettingMixin],
|
mixins: [SettingMixin],
|
||||||
props: ['item', 'context'],
|
props: ['item', 'context'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -55,15 +55,12 @@
|
||||||
import {Color} from 'utilities/color';
|
import {Color} from 'utilities/color';
|
||||||
|
|
||||||
import {Sketch} from 'vue-color';
|
import {Sketch} from 'vue-color';
|
||||||
import {mixin as clickaway} from 'vue-clickaway';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
'chrome-picker': Sketch
|
'chrome-picker': Sketch
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [clickaway],
|
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
value: String,
|
value: String,
|
||||||
default: {
|
default: {
|
||||||
|
|
|
@ -83,12 +83,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import { mixin as clickaway} from 'vue-clickaway';
|
|
||||||
|
|
||||||
const indexOf = Array.prototype.indexOf;
|
const indexOf = Array.prototype.indexOf;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [clickaway],
|
|
||||||
props: ['context'],
|
props: ['context'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -162,7 +162,8 @@ Twilight.KNOWN_MODULES = {
|
||||||
'extension-service': n => n.extensionService,
|
'extension-service': n => n.extensionService,
|
||||||
'chat-types': n => n.b && has(n.b, 'Message') && has(n.b, 'RoomMods'),
|
'chat-types': n => n.b && has(n.b, 'Message') && has(n.b, 'RoomMods'),
|
||||||
'gql-printer': n => n !== window && n.print,
|
'gql-printer': n => n !== window && n.print,
|
||||||
mousetrap: n => n.bindGlobal && n.unbind && n.handleKey
|
mousetrap: n => n.bindGlobal && n.unbind && n.handleKey,
|
||||||
|
'algolia-search': n => n.a && n.a.prototype && n.a.prototype.queryTopResults && n.a.prototype.queryForType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ffz-action-balloon {
|
||||||
|
z-index: 10000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.ffz-tooltip.chat-card__link > * {
|
.ffz-tooltip.chat-card__link > * {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
381
src/std-components/autocomplete.vue
Normal file
381
src/std-components/autocomplete.vue
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
<template>
|
||||||
|
<div class="ffz--autocomplete tw-relative">
|
||||||
|
<div class="tw-search-input" data-a-target="dropdown-search-input">
|
||||||
|
<label v-if="placeholder" :for="'ffz-autocomplete$' + id" class="tw-hide-accessible">{{ placeholder }}</label>
|
||||||
|
<div class="tw-relative">
|
||||||
|
<div v-if="hasIcon" 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="icon" />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
:id="'ffz-autocomplete$' + id"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:class="[hasIcon ? 'tw-pd-l-3' : 'tw-pd-l-1']"
|
||||||
|
v-model="search"
|
||||||
|
type="search"
|
||||||
|
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-r-1 tw-pd-y-05"
|
||||||
|
autocapitalize="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
@focus="onFocus"
|
||||||
|
@blur="onBlur"
|
||||||
|
@input="onChange"
|
||||||
|
@keydown.escape="onEscape"
|
||||||
|
@keydown.down="onDown"
|
||||||
|
@keydown.up="onUp"
|
||||||
|
@keydown.enter="onEnter"
|
||||||
|
@keydown.home="onHome"
|
||||||
|
@keydown.end="onEnd"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<balloon v-if="open" :dir="direction" color="background-base">
|
||||||
|
<div ref="list" tabindex="-1">
|
||||||
|
<simplebar classes="scrollable-area--suppress-scroll-x">
|
||||||
|
<div
|
||||||
|
v-if="! hasItems && ! loading"
|
||||||
|
class="tw-align-center tw-c-text-alt-2 tw-pd-y-05 tw-pd-x-1"
|
||||||
|
>
|
||||||
|
{{ t('autocomplete.empty', 'There are no results.') }}
|
||||||
|
</div>
|
||||||
|
<div v-else-if="! hasItems && loading" class="tw-align-center tw-c-text-alt-2 tw-pd-05">
|
||||||
|
<h3 class="ffz-i-zreknarf loading" />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-for="(item, idx) of filteredItems"
|
||||||
|
:key="has(item, 'id') ? item.id : idx"
|
||||||
|
:id="'ffz-autocomplete-item-' + id + '-' + idx"
|
||||||
|
:class="{'tw-interactable--hover' : idx === index}"
|
||||||
|
class="tw-block tw-full-width tw-interactable tw-interactable--inverted tw-interactive"
|
||||||
|
tabindex="-1"
|
||||||
|
data-selectable="true"
|
||||||
|
@mouseenter="index = idx"
|
||||||
|
@click="selectItem(item)"
|
||||||
|
>
|
||||||
|
<slot :item="item">
|
||||||
|
<div class="tw-pd-x-1 tw-pd-y-05">
|
||||||
|
<span :title="item.title">{{ item.displayName || item.label || item.name }}</span>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
</button>
|
||||||
|
</simplebar>
|
||||||
|
</div>
|
||||||
|
</balloon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import {has as objectHas, debounce} from 'utilities/object';
|
||||||
|
|
||||||
|
let last_id = 0;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
items: {
|
||||||
|
type: [Array, Function],
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
suggestOnFocus: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
suggestWhenEmpty: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
escapeToClear: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
clearOnSelect: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
cacheDuration: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 5000
|
||||||
|
},
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'down'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
const is_fn = typeof this.items === 'function';
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: last_id++,
|
||||||
|
|
||||||
|
search: this.value,
|
||||||
|
focused: false,
|
||||||
|
open: false,
|
||||||
|
index: 0,
|
||||||
|
|
||||||
|
cachedFor: null,
|
||||||
|
cachedAt: 0,
|
||||||
|
cachedItems: is_fn ? [] : this.items,
|
||||||
|
async: is_fn,
|
||||||
|
loading: false,
|
||||||
|
errored: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
hasIcon() {
|
||||||
|
return this.icon && this.icon.length > 0
|
||||||
|
},
|
||||||
|
|
||||||
|
hasItems() {
|
||||||
|
return this.filteredItems && this.filteredItems.length > 0
|
||||||
|
},
|
||||||
|
|
||||||
|
filteredItems() {
|
||||||
|
if ( this.errored )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if ( ! this.search || ! this.search.length )
|
||||||
|
return this.cachedItems;
|
||||||
|
|
||||||
|
const needle = this.search.toLowerCase();
|
||||||
|
return this.cachedItems.filter(item => {
|
||||||
|
if ( typeof item.displayName === 'string' && item.displayName.toLowerCase().includes(needle) )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if ( typeof item.label === 'string' && item.label.toLowerCase().includes(needle) )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if ( typeof item.name === 'string' && item.name.toLowerCase().includes(needle) )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return typeof item.value === 'string' && item.value.toLowerCase().includes(needle);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
items() {
|
||||||
|
const is_fn = typeof this.items === 'function';
|
||||||
|
this.cachedItems = is_fn ? [] : this.items;
|
||||||
|
this.async = is_fn;
|
||||||
|
this.loading = false;
|
||||||
|
this.errored = false;
|
||||||
|
|
||||||
|
if ( this.open )
|
||||||
|
this.updateCache();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.maybeClose = debounce(this.maybeClose, 25);
|
||||||
|
this.updateCache = debounce(this.updateCache, 250, 2);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
has(thing, key) {
|
||||||
|
return objectHas(thing, key)
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCache() {
|
||||||
|
if ( ! this.async )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( this.search === this.cachedFor && (Date.now() - this.cachedAt) < this.cacheDuration )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
this.errored = false;
|
||||||
|
|
||||||
|
this.cachedFor = this.search;
|
||||||
|
this.cachedAt = Date.now();
|
||||||
|
|
||||||
|
let result = null;
|
||||||
|
try {
|
||||||
|
result = this.items(this.search);
|
||||||
|
} catch(err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( result instanceof Promise ) {
|
||||||
|
this.loading = true;
|
||||||
|
result.then(items => {
|
||||||
|
this.loading = false;
|
||||||
|
this.cachedItems = items;
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
this.loading = false;
|
||||||
|
this.errored = true;
|
||||||
|
this.cachedItems = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if ( Array.isArray(result) )
|
||||||
|
this.cachedItems = result;
|
||||||
|
|
||||||
|
else {
|
||||||
|
this.errored = true;
|
||||||
|
this.cachedItems = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onFocus() {
|
||||||
|
this.focused = true;
|
||||||
|
this.$emit('focus');
|
||||||
|
|
||||||
|
if ( this.open || ! this.suggestOnFocus )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( ! this.suggestWhenEmpty && (! this.search || ! this.search.length) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.updateCache();
|
||||||
|
|
||||||
|
if ( ! this.open ) {
|
||||||
|
this.open = true;
|
||||||
|
this.index = -1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onBlur() {
|
||||||
|
this.focused = false;
|
||||||
|
this.$emit('blur');
|
||||||
|
this.maybeClose();
|
||||||
|
},
|
||||||
|
|
||||||
|
maybeClose() {
|
||||||
|
if ( ! this.focused )
|
||||||
|
this.open = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange() {
|
||||||
|
this.$emit('input', this.search);
|
||||||
|
|
||||||
|
if ( (! this.search || ! this.search.length) && ! this.suggestWhenEmpty ) {
|
||||||
|
this.loading = false;
|
||||||
|
this.open = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateCache();
|
||||||
|
|
||||||
|
if ( ! this.open ) {
|
||||||
|
this.open = true;
|
||||||
|
this.index = -1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onEscape(event) {
|
||||||
|
if ( this.open || ! this.escapeToClear )
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.open = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
onHome(event) {
|
||||||
|
if ( ! this.open )
|
||||||
|
return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
if ( this.filteredItems )
|
||||||
|
this.index = 0;
|
||||||
|
|
||||||
|
this.scrollToItem();
|
||||||
|
},
|
||||||
|
|
||||||
|
onEnd(event) {
|
||||||
|
if ( ! this.open )
|
||||||
|
return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
if ( this.filteredItems )
|
||||||
|
this.index = this.filteredItems.length - 1;
|
||||||
|
|
||||||
|
this.scrollToItem();
|
||||||
|
},
|
||||||
|
|
||||||
|
onUp(event) {
|
||||||
|
if ( ! this.open )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.index--;
|
||||||
|
if ( ! this.filteredItems )
|
||||||
|
this.index = -1;
|
||||||
|
else if ( this.index < 0 )
|
||||||
|
this.index = 0;
|
||||||
|
|
||||||
|
this.scrollToItem();
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
|
||||||
|
onDown(event) {
|
||||||
|
if ( ! this.open )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.index++;
|
||||||
|
if ( ! this.filteredItems )
|
||||||
|
this.index = -1;
|
||||||
|
else if ( this.index >= this.filteredItems.length )
|
||||||
|
this.index = this.filteredItems.length - 1;
|
||||||
|
|
||||||
|
this.scrollToItem();
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollToItem() {
|
||||||
|
const root = this.$refs.list,
|
||||||
|
element = root && root.querySelector(`#ffz-autocomplete-item-${this.id}-${this.index}`);
|
||||||
|
|
||||||
|
if ( element )
|
||||||
|
element.scrollIntoViewIfNeeded();
|
||||||
|
},
|
||||||
|
|
||||||
|
onEnter(event) {
|
||||||
|
if ( ! this.open )
|
||||||
|
return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
const item = this.filteredItems[this.index];
|
||||||
|
if ( item )
|
||||||
|
this.selectItem(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
selectItem(item) {
|
||||||
|
if ( this.clearOnSelect ) {
|
||||||
|
this.search = '';
|
||||||
|
if ( ! this.suggestWhenEmpty )
|
||||||
|
this.open = false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.search = item.label || item.name || item.value;
|
||||||
|
this.open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('input', this.search);
|
||||||
|
this.$emit('selected', objectHas(item, 'value') ? item.value : objectHas(item, 'name') ? item.name : (item.label || item.displayName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -21,7 +21,7 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'background'
|
default: 'background-base'
|
||||||
},
|
},
|
||||||
|
|
||||||
size: String,
|
size: String,
|
||||||
|
|
|
@ -1,34 +1,52 @@
|
||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div class="ffz--icon-picker">
|
<div v-on-clickaway="close" class="ffz--icon-picker tw-relative">
|
||||||
<div class="tw-full-width">
|
<div class="tw-search-input tw-full-width">
|
||||||
<div class="tw-search-input">
|
<label v-if="open" :for="'icon-search$' + id" class="tw-hide-accessible">{{ t('setting.icon.search', 'Search for Icon') }}</label>
|
||||||
<label :for="'icon-search$' + id" class="tw-hide-accessible">
|
<div class="tw-relative">
|
||||||
{{ 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">
|
<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" />
|
<figure :class="[(isOpen || ! val || ! val.length) ? 'ffz-i-search' : val]" />
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
ref="input"
|
||||||
:id="'icon-search$' + id"
|
:id="'icon-search$' + id"
|
||||||
:placeholder="t('setting.actions.icon.search', 'Search for Icon')"
|
:placeholder="('setting.icon.search', 'Search for Icon')"
|
||||||
v-model="search"
|
:value="isOpen ? search : val"
|
||||||
type="search"
|
:class="[clearable ? 'tw-pd-r-5' : 'tw-pd-r-1']"
|
||||||
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"
|
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"
|
autocapitalize="off"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocomplete="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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<balloon v-if="open" :dir="direction" color="background-base">
|
||||||
<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 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-if="visible.length" role="radiogroup" class="tw-pd-1 tw-flex tw-flex-wrap tw-justify-content-between" >
|
||||||
<div
|
<div
|
||||||
v-for="i of visible"
|
v-for="i of visible"
|
||||||
:key="i[0]"
|
:key="i[0]"
|
||||||
:aria-checked="value === i[0]"
|
:aria-checked="val === i[0]"
|
||||||
:class="{'tw-interactable--selected': value === i[0]}"
|
:class="{'tw-interactable--selected': val === i[0]}"
|
||||||
:data-title="i[1]"
|
:data-title="i[1]"
|
||||||
class="ffz-tooltip ffz-icon tw-interactable tw-interactable--inverted"
|
class="ffz-tooltip ffz-icon tw-interactable tw-interactable--inverted"
|
||||||
role="radio"
|
role="radio"
|
||||||
|
@ -37,6 +55,8 @@
|
||||||
@keyup.space="change(i[0])"
|
@keyup.space="change(i[0])"
|
||||||
@keyup.enter="change(i[0])"
|
@keyup.enter="change(i[0])"
|
||||||
@click="change(i[0])"
|
@click="change(i[0])"
|
||||||
|
@focus="onFocus(false)"
|
||||||
|
@blur="onBlur"
|
||||||
>
|
>
|
||||||
<figure :class="`tw-mg-y-05 tw-mg-x-1 ${i[0]}`" />
|
<figure :class="`tw-mg-y-05 tw-mg-x-1 ${i[0]}`" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,6 +66,7 @@
|
||||||
</div>
|
</div>
|
||||||
</simplebar>
|
</simplebar>
|
||||||
</div>
|
</div>
|
||||||
|
</balloon>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -53,8 +74,9 @@
|
||||||
|
|
||||||
let id = 0;
|
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 {load, ICONS as FA_ICONS, ALIASES as FA_ALIASES} from 'utilities/font-awesome';
|
||||||
|
import { maybeLoad } from '../utilities/font-awesome';
|
||||||
|
|
||||||
const FFZ_ICONS = [
|
const FFZ_ICONS = [
|
||||||
'cancel',
|
'cancel',
|
||||||
|
@ -107,6 +129,7 @@ const FFZ_ICONS = [
|
||||||
'cw',
|
'cw',
|
||||||
'up-dir',
|
'up-dir',
|
||||||
'up-big',
|
'up-big',
|
||||||
|
'play',
|
||||||
'link-ext',
|
'link-ext',
|
||||||
'twitter',
|
'twitter',
|
||||||
'github',
|
'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]));
|
.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 {
|
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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
id: id++,
|
id: id++,
|
||||||
|
open: false,
|
||||||
|
val: this.value,
|
||||||
search: '',
|
search: '',
|
||||||
icons: deep_copy(ICONS)
|
icons: deep_copy(ICONS)
|
||||||
}
|
}
|
||||||
|
@ -159,25 +201,81 @@ export default {
|
||||||
reg = new RegExp('(?:^|-| )' + escape_regex(search), 'i');
|
reg = new RegExp('(?:^|-| )' + escape_regex(search), 'i');
|
||||||
|
|
||||||
return this.icons.filter(x => reg.test(x[1]));
|
return this.icons.filter(x => reg.test(x[1]));
|
||||||
|
},
|
||||||
|
|
||||||
|
isOpen() {
|
||||||
|
return this.alwaysOpen || this.open
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
watch: {
|
||||||
|
value() {
|
||||||
|
this.val = this.value;
|
||||||
|
},
|
||||||
|
|
||||||
|
isOpen() {
|
||||||
|
if ( ! this.isOpen ) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const ffz = FrankerFaceZ.get();
|
||||||
|
if ( ffz )
|
||||||
|
ffz.emit('tooltips:cleanup');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
load();
|
load();
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if ( this.value ) {
|
if ( this.val ) {
|
||||||
const el = this.$el.querySelector('.tw-interactable--selected');
|
const root = this.$refs.list,
|
||||||
|
el = root && root.querySelector('.tw-interactable--selected');
|
||||||
|
|
||||||
if ( el )
|
if ( el )
|
||||||
el.scrollIntoViewIfNeeded();
|
el.scrollIntoViewIfNeeded();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.maybeClose = debounce(this.maybeClose, 10);
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
maybeLoad(this.val);
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
change(val) {
|
update() {
|
||||||
this.value = val;
|
if ( this.open )
|
||||||
this.$emit('input', this.value);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,8 @@ export const WS_CLUSTERS = {
|
||||||
['wss://andknuckles.frankerfacez.com/', 0.8],
|
['wss://andknuckles.frankerfacez.com/', 0.8],
|
||||||
['wss://tuturu.frankerfacez.com/', 1],
|
['wss://tuturu.frankerfacez.com/', 1],
|
||||||
['wss://lilz.frankerfacez.com/', 1],
|
['wss://lilz.frankerfacez.com/', 1],
|
||||||
['wss://yoohoo.frankerfacez.com/', 1]
|
['wss://yoohoo.frankerfacez.com/', 1],
|
||||||
|
['wss://pog.frankerfacez.com/', 1]
|
||||||
],
|
],
|
||||||
|
|
||||||
Development: [
|
Development: [
|
||||||
|
|
|
@ -73,6 +73,52 @@ export function timeout(promise, delay) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a wrapper for a function that will only execute the function
|
||||||
|
* a period of time after it has stopped being called.
|
||||||
|
* @param {Function} fn The function to wrap.
|
||||||
|
* @param {Integer} delay The time to wait, in milliseconds
|
||||||
|
* @param {Boolean} immediate If immediate is true, trigger the function immediately rather than eventually.
|
||||||
|
* @returns {Function} wrapped function
|
||||||
|
*/
|
||||||
|
export function debounce(fn, delay, immediate) {
|
||||||
|
let timer;
|
||||||
|
if ( immediate ) {
|
||||||
|
const later = () => timer = null;
|
||||||
|
if ( immediate === 2 )
|
||||||
|
// Special Mode! Run immediately OR later.
|
||||||
|
return function(...args) {
|
||||||
|
if ( timer ) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
timer = null;
|
||||||
|
fn.apply(this, args); // eslint-disable-line no-invalid-this
|
||||||
|
}, delay);
|
||||||
|
} else {
|
||||||
|
fn.apply(this, args); // eslint-disable-line no-invalid-this
|
||||||
|
timer = setTimeout(later, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(...args) {
|
||||||
|
if ( ! timer )
|
||||||
|
fn.apply(this, args); // eslint-disable-line no-invalid-this
|
||||||
|
else
|
||||||
|
clearTimeout(timer);
|
||||||
|
|
||||||
|
timer = setTimeout(later, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(...args) {
|
||||||
|
if ( timer )
|
||||||
|
clearTimeout(timer);
|
||||||
|
|
||||||
|
timer = setTimeout(fn.bind(this, ...args), delay); // eslint-disable-line no-invalid-this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make sure that a given asynchronous function is only called once
|
* Make sure that a given asynchronous function is only called once
|
||||||
* at a time.
|
* at a time.
|
||||||
|
|
|
@ -72,7 +72,7 @@ export const DEFAULT_TYPES = {
|
||||||
},
|
},
|
||||||
|
|
||||||
humantime(val, node) {
|
humantime(val, node) {
|
||||||
return this.formatHumanTime(val, node.f);
|
return this.formatHumanTime(val, 1, node.f);
|
||||||
},
|
},
|
||||||
|
|
||||||
en_plural: v => v !== 1 ? 's' : ''
|
en_plural: v => v !== 1 ? 's' : ''
|
||||||
|
@ -226,26 +226,28 @@ export default class TranslationCore {
|
||||||
return thing;
|
return thing;
|
||||||
}
|
}
|
||||||
|
|
||||||
formatHumanTime(value, factor) {
|
formatHumanTime(value, factor, round = false) {
|
||||||
if ( value instanceof Date )
|
if ( value instanceof Date )
|
||||||
value = (Date.now() - value.getTime()) / 1000;
|
value = (Date.now() - value.getTime()) / 1000;
|
||||||
|
|
||||||
value = Math.floor(value);
|
value = Math.floor(value);
|
||||||
factor = Number(factor) || 1;
|
factor = Number(factor) || 1;
|
||||||
|
|
||||||
const years = Math.floor((value * factor) / 31536000) / factor;
|
const fn = round ? Math.round : Math.floor;
|
||||||
|
|
||||||
|
const years = fn((value * factor) / 31536000) / factor;
|
||||||
if ( years >= 1 )
|
if ( years >= 1 )
|
||||||
return this.t('human-time.years', '{count,number} year{count,en_plural}', years);
|
return this.t('human-time.years', '{count,number} year{count,en_plural}', years);
|
||||||
|
|
||||||
const days = Math.floor((value %= 31536000) / 86400);
|
const days = fn((value %= 31536000) / 86400);
|
||||||
if ( days >= 1 )
|
if ( days >= 1 )
|
||||||
return this.t('human-time.days', '{count,number} day{count,en_plural}', days);
|
return this.t('human-time.days', '{count,number} day{count,en_plural}', days);
|
||||||
|
|
||||||
const hours = Math.floor((value %= 86400) / 3600);
|
const hours = fn((value %= 86400) / 3600);
|
||||||
if ( hours >= 1 )
|
if ( hours >= 1 )
|
||||||
return this.t('human-time.hours', '{count,number} hour{count,en_plural}', hours);
|
return this.t('human-time.hours', '{count,number} hour{count,en_plural}', hours);
|
||||||
|
|
||||||
const minutes = Math.floor((value %= 3600) / 60);
|
const minutes = fn((value %= 3600) / 60);
|
||||||
if ( minutes >= 1 )
|
if ( minutes >= 1 )
|
||||||
return this.t('human-time.minutes', '{count,number} minute{count,en_plural}', minutes);
|
return this.t('human-time.minutes', '{count,number} minute{count,en_plural}', minutes);
|
||||||
|
|
||||||
|
@ -436,29 +438,33 @@ export default class TranslationCore {
|
||||||
return this._processAST(...this._preTransform(key, phrase, options, use_default));
|
return this._processAST(...this._preTransform(key, phrase, options, use_default));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatNode(node, data, locale = null, out = null, ast = null) {
|
||||||
|
if ( ! node || typeof node !== 'object' )
|
||||||
|
return node;
|
||||||
|
|
||||||
|
if ( locale == null )
|
||||||
|
locale = this.locale;
|
||||||
|
|
||||||
|
let val = get(node.v, data);
|
||||||
|
if ( val == null )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if ( node.t ) {
|
||||||
|
if ( this.types[node.t] )
|
||||||
|
return this.types[node.t].call(this, val, node, locale, out, ast, data);
|
||||||
|
else if ( this.warn )
|
||||||
|
this.warn(`Encountered unknown type "${node.t}" when formatting node.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
_processAST(ast, data, locale) {
|
_processAST(ast, data, locale) {
|
||||||
const out = [];
|
const out = [];
|
||||||
|
|
||||||
for(const node of ast) {
|
for(const node of ast) {
|
||||||
if ( typeof node === 'string' ) {
|
const val = this.formatNode(node, data, locale, out, ast);
|
||||||
out.push(node);
|
if( val != null )
|
||||||
continue;
|
|
||||||
|
|
||||||
} else if ( ! node || typeof node !== 'object' )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
let val = get(node.v, data);
|
|
||||||
if ( val == null )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if ( node.t ) {
|
|
||||||
if ( this.types[node.t] )
|
|
||||||
val = this.types[node.t].call(this, val, node, locale, out, ast, data);
|
|
||||||
else if ( this.warn )
|
|
||||||
this.warn(`Encountered unknown type "${node.t}" when processing AST.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( val )
|
|
||||||
out.push(val);
|
out.push(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,25 @@ export class Vue extends Module {
|
||||||
|
|
||||||
async onLoad() {
|
async onLoad() {
|
||||||
const Vue = window.ffzVue = this.Vue = (await import(/* webpackChunkName: "vue" */ 'vue')).default,
|
const Vue = window.ffzVue = this.Vue = (await import(/* webpackChunkName: "vue" */ 'vue')).default,
|
||||||
ObserveVisibility = await import(/* webpackChunkName: "vue" */ 'vue-observe-visibility'),
|
|
||||||
RavenVue = await import(/* webpackChunkName: "vue" */ 'raven-js/plugins/vue'),
|
|
||||||
components = this._components;
|
components = this._components;
|
||||||
|
|
||||||
this.component((await import(/* webpackChunkName: "vue" */ 'src/std-components/index.js')).default);
|
const [
|
||||||
|
ObserveVisibility,
|
||||||
|
Clickaway,
|
||||||
|
RavenVue,
|
||||||
|
Components
|
||||||
|
|
||||||
|
] = await Promise.all([
|
||||||
|
import(/* webpackChunkName: "vue" */ 'vue-observe-visibility'),
|
||||||
|
import(/* webpackChunkName: "vue" */ 'vue-clickaway'),
|
||||||
|
import(/* webpackChunkName: "vue" */ 'raven-js/plugins/vue'),
|
||||||
|
import(/* webpackChunkName: "vue" */ 'src/std-components/index.js')
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.component(Components.default);
|
||||||
|
|
||||||
Vue.use(ObserveVisibility);
|
Vue.use(ObserveVisibility);
|
||||||
|
Vue.mixin(Clickaway.mixin);
|
||||||
|
|
||||||
if ( ! DEBUG && this.root.raven )
|
if ( ! DEBUG && this.root.raven )
|
||||||
this.root.raven.addPlugin(RavenVue, Vue);
|
this.root.raven.addPlugin(RavenVue, Vue);
|
||||||
|
@ -80,6 +92,11 @@ export class Vue extends Module {
|
||||||
return t.i18n.tList(key, phrase, options);
|
return t.i18n.tList(key, phrase, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
tNode_(node, data) {
|
||||||
|
this.locale;
|
||||||
|
return t.i18n.formatNode(node, data);
|
||||||
|
},
|
||||||
|
|
||||||
setLocale(locale) {
|
setLocale(locale) {
|
||||||
t.i18n.locale = locale;
|
t.i18n.locale = locale;
|
||||||
}
|
}
|
||||||
|
@ -158,6 +175,9 @@ export class Vue extends Module {
|
||||||
},
|
},
|
||||||
tList(key, phrase, options) {
|
tList(key, phrase, options) {
|
||||||
return this.$i18n.tList_(key, phrase, options);
|
return this.$i18n.tList_(key, phrase, options);
|
||||||
|
},
|
||||||
|
tNode(node, data) {
|
||||||
|
return this.$i18n.tNode_(node, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -124,6 +124,7 @@
|
||||||
.ffz-i-cw:before { content: '\e82f'; } /* '' */
|
.ffz-i-cw:before { content: '\e82f'; } /* '' */
|
||||||
.ffz-i-up-dir:before { content: '\e830'; } /* '' */
|
.ffz-i-up-dir:before { content: '\e830'; } /* '' */
|
||||||
.ffz-i-up-big:before { content: '\e831'; } /* '' */
|
.ffz-i-up-big:before { content: '\e831'; } /* '' */
|
||||||
|
.ffz-i-play:before { content: '\e832'; } /* '' */
|
||||||
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
|
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
|
||||||
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
||||||
.ffz-i-github:before { content: '\f09b'; } /* '' */
|
.ffz-i-github:before { content: '\f09b'; } /* '' */
|
||||||
|
|
|
@ -20,6 +20,11 @@
|
||||||
.ffz-monospace { font-family: monospace }
|
.ffz-monospace { font-family: monospace }
|
||||||
.ffz-bottom-100 { bottom: 100% }
|
.ffz-bottom-100 { bottom: 100% }
|
||||||
|
|
||||||
|
.ffz--autocomplete {
|
||||||
|
.scrollable-area {
|
||||||
|
max-height: 20rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ffz--widget {
|
.ffz--widget {
|
||||||
input, select {
|
input, select {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
.ffz-icon {
|
.ffz-icon {
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
|
|
||||||
> * {
|
figure {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue