mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.3.0
* Added: Setting to hide the "Not Live" bar beneath videos and clips that appears when the channel is currently live. * Fixed: Handling of `https://www.twitch.tv/<channel>/clip/<slug>` urls for rich chat embeds and rich link tool-tips. * Fixed: Lower the priority of custom highlight terms so they will not break links. * Fixed: Holding multiple modifier keys to display in-line chat actions. * Fixed: Clean up out-dated avatar display setting for the directory. * API Added: Allow add-ons to access the Popper JS library via `FrankerFaceZ.utilities.popper`. * API Added: `<icon-picker />` Vue component for selecting an icon. * API Added: `<react-link />` Vue component for creating links that cause the React app to navigate without a page load. * API Added: `<t-list />` Vue component for translating text including Vue elements. * API Added: `maybeLoad(icon)` function for font awesome to only load the font if the icon is from font awesome. * API Added: `generateUUID()` function to `FrankerFaceZ.utilities.object` * API Added: The `vue-observe-visibility` module is now loaded with Vue and made available in all Vue contexts.
This commit is contained in:
parent
3157aeb390
commit
aa25bff498
22 changed files with 210 additions and 218 deletions
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -9887,6 +9887,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue-observe-visibility": {
|
||||||
|
"version": "0.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-observe-visibility/-/vue-observe-visibility-0.4.4.tgz",
|
||||||
|
"integrity": "sha512-2eDYHgL2MJ2wkkNZnus56D0CG8m80BFLuvEcGnD7rQ9jxFogpXMsM9aM5Md+XT8AmYGYCqVfOVBaWFtVvwbpmw=="
|
||||||
|
},
|
||||||
"vue-style-loader": {
|
"vue-style-loader": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.1.2.tgz",
|
||||||
|
|
|
@ -74,6 +74,7 @@
|
||||||
"vue": "^2.5.16",
|
"vue": "^2.5.16",
|
||||||
"vue-clickaway": "^2.2.2",
|
"vue-clickaway": "^2.2.2",
|
||||||
"vue-color": "^2.4.6",
|
"vue-color": "^2.4.6",
|
||||||
|
"vue-observe-visibility": "^0.4.4",
|
||||||
"vue-template-compiler": "^2.5.16",
|
"vue-template-compiler": "^2.5.16",
|
||||||
"vuedraggable": "^2.16.0"
|
"vuedraggable": "^2.16.0"
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -100,6 +100,12 @@
|
||||||
|
|
||||||
<glyph glyph-name="camera" unicode="" d="M560 600c22 0 39-17 40-38v-424a40 40 0 0 0-40-38h-420a40 40 0 0 0-40 40v421c0 21 18 39 40 39h420z m323-55c11-7 17-18 17-31v-328a35 35 0 0 0-17-30 37 37 0 0 0-35-2l-148 54v284l148 55a37 37 0 0 0 35-2z" horiz-adv-x="1000" />
|
<glyph glyph-name="camera" unicode="" d="M560 600c22 0 39-17 40-38v-424a40 40 0 0 0-40-38h-420a40 40 0 0 0-40 40v421c0 21 18 39 40 39h420z m323-55c11-7 17-18 17-31v-328a35 35 0 0 0-17-30 37 37 0 0 0-35-2l-148 54v284l148 55a37 37 0 0 0 35-2z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="cw" unicode="" d="M857 707v-250q0-14-10-25t-26-11h-250q-23 0-32 23-10 22 7 38l77 77q-82 77-194 77-58 0-111-23t-91-61-61-91-23-111 23-111 61-91 91-61 111-23q66 0 125 29t100 82q4 6 13 7 8 0 14-5l76-77q5-4 6-11t-5-13q-60-74-147-114t-182-41q-87 0-167 34t-136 92-92 137-34 166 34 166 92 137 136 92 167 34q82 0 158-31t137-88l72 72q17 18 39 8 22-9 22-33z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="up-dir" unicode="" d="M571 171q0-14-10-25t-25-10h-500q-15 0-25 10t-11 25 11 26l250 250q10 10 25 10t25-10l250-250q10-11 10-26z" horiz-adv-x="571.4" />
|
||||||
|
|
||||||
|
<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="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: 28 KiB After Width: | Height: | Size: 29 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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: 2, revision: 6,
|
major: 4, minor: 3, revision: 0,
|
||||||
commit: __git_commit__,
|
commit: __git_commit__,
|
||||||
build: __webpack_hash__,
|
build: __webpack_hash__,
|
||||||
toString: () =>
|
toString: () =>
|
||||||
|
@ -174,7 +174,8 @@ FrankerFaceZ.utilities = {
|
||||||
time: require('utilities/time'),
|
time: require('utilities/time'),
|
||||||
tooltip: require('utilities/tooltip'),
|
tooltip: require('utilities/tooltip'),
|
||||||
i18n: require('utilities/translation-core'),
|
i18n: require('utilities/translation-core'),
|
||||||
dayjs: require('dayjs')
|
dayjs: require('dayjs'),
|
||||||
|
popper: require('popper.js').default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,140 +4,15 @@
|
||||||
{{ t('setting.actions.icon', 'Icon') }}
|
{{ t('setting.actions.icon', 'Icon') }}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="tw-full-width">
|
<icon-picker :value="value.icon" @input="change" />
|
||||||
<div class="tw-search-input">
|
|
||||||
<label for="icon-search" class="tw-hide-accessible">
|
|
||||||
{{ t('setting.actions.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"
|
|
||||||
: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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<simplebar classes="tw-c-background-alt-2 tw-border-l tw-border-r tw-border-b ffz-icon-picker tw-mg-b-05">
|
|
||||||
<div v-if="visible.length" role="radiogroup" class="tw-pd-1 tw-flex tw-flex-wrap" >
|
|
||||||
<div
|
|
||||||
v-for="i of visible"
|
|
||||||
:key="i[0]"
|
|
||||||
:aria-checked="value.icon === i[0]"
|
|
||||||
:class="{'tw-interactable--selected': value.icon === i[0]}"
|
|
||||||
class="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>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import {escape_regex, deep_copy} from 'utilities/object';
|
|
||||||
import {load, ICONS as FA_ICONS, ALIASES as FA_ALIASES} from 'utilities/font-awesome';
|
|
||||||
|
|
||||||
const FFZ_ICONS = [
|
|
||||||
'zreknarf',
|
|
||||||
'crown',
|
|
||||||
'verified',
|
|
||||||
'inventory',
|
|
||||||
'ignore',
|
|
||||||
'pin-outline',
|
|
||||||
'pin',
|
|
||||||
'block',
|
|
||||||
'ok',
|
|
||||||
'clock',
|
|
||||||
'eye',
|
|
||||||
'eye-off',
|
|
||||||
'trash',
|
|
||||||
'discord',
|
|
||||||
'star',
|
|
||||||
'star-empty',
|
|
||||||
'twitch',
|
|
||||||
'twitter',
|
|
||||||
'download',
|
|
||||||
'upload',
|
|
||||||
'download-cloud',
|
|
||||||
'upload-cloud',
|
|
||||||
'tag',
|
|
||||||
'tags',
|
|
||||||
'retweet',
|
|
||||||
'thumbs-up',
|
|
||||||
'thumbs-down',
|
|
||||||
'bell',
|
|
||||||
'bell-off',
|
|
||||||
'pencil',
|
|
||||||
'info',
|
|
||||||
'help',
|
|
||||||
'calendar',
|
|
||||||
'lock',
|
|
||||||
'lock-open',
|
|
||||||
'arrows-cw',
|
|
||||||
'gift',
|
|
||||||
'eyedropper',
|
|
||||||
'github',
|
|
||||||
'user-secret'
|
|
||||||
];
|
|
||||||
|
|
||||||
const FFZ_ALIASES = {
|
|
||||||
'block': ['ban', 'block'],
|
|
||||||
'ok': ['ok', 'unban', 'untimeout', 'checkmark'],
|
|
||||||
'clock': ['clock', 'clock-o', 'timeout']
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const ICONS = FFZ_ICONS
|
|
||||||
.map(x => [`ffz-i-${x}`, FFZ_ALIASES[x] ? FFZ_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'],
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
search: '',
|
|
||||||
icons: deep_copy(ICONS)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
visible() {
|
|
||||||
if ( ! this.search || ! this.search.length )
|
|
||||||
return this.icons;
|
|
||||||
|
|
||||||
const search = this.search.toLowerCase().replace(' ', '-'),
|
|
||||||
reg = new RegExp('(?:^|-| )' + escape_regex(search), 'i');
|
|
||||||
|
|
||||||
return this.icons.filter(x => reg.test(x[1]));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
load();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
change(val) {
|
change(val) {
|
||||||
this.value.icon = val;
|
this.value.icon = val;
|
||||||
|
@ -146,15 +21,4 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.ffz-icon-picker {
|
|
||||||
max-height: 15rem;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
|
|
||||||
.ffz-icon {
|
|
||||||
width: auto !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -5,6 +5,7 @@
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
const CLIP_URL = /^(?:https?:\/\/)?clips\.twitch\.tv\/(\w+)(?:\/)?(\w+)?(?:\/edit)?/;
|
const CLIP_URL = /^(?:https?:\/\/)?clips\.twitch\.tv\/(\w+)(?:\/)?(\w+)?(?:\/edit)?/;
|
||||||
|
const NEW_CLIP_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/\w+\/clip\/(\w+)/;
|
||||||
const VIDEO_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/(?:\w+\/v|videos)\/(\w+)/;
|
const VIDEO_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/(?:\w+\/v|videos)\/(\w+)/;
|
||||||
|
|
||||||
import GET_CLIP from './clip_info.gql';
|
import GET_CLIP from './clip_info.gql';
|
||||||
|
@ -74,13 +75,18 @@ export const Clips = {
|
||||||
hide_token: true,
|
hide_token: true,
|
||||||
|
|
||||||
test(token) {
|
test(token) {
|
||||||
return token.type === 'link' && CLIP_URL.test(token.url)
|
if ( token.type !== 'link' )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CLIP_URL.test(token.url) || NEW_CLIP_URL.test(token.url);
|
||||||
},
|
},
|
||||||
|
|
||||||
process(token) {
|
process(token) {
|
||||||
const match = CLIP_URL.exec(token.url),
|
let match = CLIP_URL.exec(token.url);
|
||||||
apollo = this.resolve('site.apollo');
|
if ( ! match )
|
||||||
|
match = NEW_CLIP_URL.exec(token.url);
|
||||||
|
|
||||||
|
const apollo = this.resolve('site.apollo');
|
||||||
if ( ! apollo || ! match || match[1] === 'create' )
|
if ( ! apollo || ! match || match[1] === 'create' )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -415,7 +415,7 @@ export const BlockedBadges = {
|
||||||
|
|
||||||
export const CustomHighlights = {
|
export const CustomHighlights = {
|
||||||
type: 'highlight',
|
type: 'highlight',
|
||||||
priority: 100,
|
priority: 35,
|
||||||
|
|
||||||
component: () => import(/* webpackChunkName: 'vue-chat' */ './components/chat-highlight.vue'),
|
component: () => import(/* webpackChunkName: 'vue-chat' */ './components/chat-highlight.vue'),
|
||||||
|
|
||||||
|
|
|
@ -319,18 +319,8 @@ export default class Scroller extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
inst.ffzHandleKey = inst.ffzHandleKey.bind(inst);
|
inst.ffzHandleKey = inst.ffzHandleKey.bind(inst);
|
||||||
|
window.addEventListener('keydown', inst.ffzHandleKey);
|
||||||
Mousetrap.bindGlobal('alt', inst.ffzHandleKey, 'keydown');
|
window.addEventListener('keyup', inst.ffzHandleKey);
|
||||||
Mousetrap.bindGlobal('alt', inst.ffzHandleKey, 'keyup');
|
|
||||||
|
|
||||||
Mousetrap.bindGlobal('shift', inst.ffzHandleKey, 'keydown');
|
|
||||||
Mousetrap.bindGlobal('shift', inst.ffzHandleKey, 'keyup');
|
|
||||||
|
|
||||||
Mousetrap.bindGlobal('ctrl', inst.ffzHandleKey, 'keydown');
|
|
||||||
Mousetrap.bindGlobal('ctrl', inst.ffzHandleKey, 'keyup');
|
|
||||||
|
|
||||||
Mousetrap.bindGlobal('command', inst.ffzHandleKey, 'keydown');
|
|
||||||
Mousetrap.bindGlobal('command', inst.ffzHandleKey, 'keyup');
|
|
||||||
|
|
||||||
inst.hoverPause = inst.ffzMouseMove.bind(inst);
|
inst.hoverPause = inst.ffzMouseMove.bind(inst);
|
||||||
inst.hoverResume = inst.ffzMouseLeave.bind(inst);
|
inst.hoverResume = inst.ffzMouseLeave.bind(inst);
|
||||||
|
@ -624,18 +614,8 @@ export default class Scroller extends Module {
|
||||||
inst.ffzInstallHandler();
|
inst.ffzInstallHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnmount() { // eslint-disable-line class-methods-use-this
|
onUnmount(inst) { // eslint-disable-line class-methods-use-this
|
||||||
const Mousetrap = this.web_munch.getModule('mousetrap') || window.Mousetrap;
|
window.removeEventListener('keydown', inst.ffzHandleKey);
|
||||||
if ( Mousetrap != null ) {
|
window.removeEventListener('keyup', inst.ffzHandleKey);
|
||||||
Mousetrap.unbind('alt', 'keydown');
|
|
||||||
Mousetrap.unbind('alt', 'keyup');
|
|
||||||
Mousetrap.unbind('shift', 'keydown');
|
|
||||||
Mousetrap.unbind('shift', 'keyup');
|
|
||||||
Mousetrap.unbind('ctrl', 'keydown');
|
|
||||||
Mousetrap.unbind('ctrl', 'keyup');
|
|
||||||
Mousetrap.unbind('command', 'keydown');
|
|
||||||
Mousetrap.unbind('command', 'keyup');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -230,14 +230,15 @@ export default class CSSTweaks extends Module {
|
||||||
changed: val => this.toggle('square-avatars', !val)
|
changed: val => this.toggle('square-avatars', !val)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settings.add('channel.not-live-bar', {
|
this.settings.add('channel.hide-not-live-bar', {
|
||||||
default: true,
|
default: true,
|
||||||
ui: {
|
ui: {
|
||||||
path: 'Channel > Appearance >> General',
|
path: 'Channel > Appearance >> General',
|
||||||
title: 'Show notification below clips and videos if the streamer is live.',
|
title: 'Hide the "Not Live" bar.',
|
||||||
|
description: 'Hide the bar which appears beneath clips and videos when the streamer is live, telling you they are live.',
|
||||||
component: 'setting-check-box'
|
component: 'setting-check-box'
|
||||||
},
|
},
|
||||||
changed: val => this.toggle('not-live-bar', !val)
|
changed: val => this.toggleHide('not-live-bar', val)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +255,7 @@ export default class CSSTweaks extends Module {
|
||||||
this.toggleHide('top-discover', !this.settings.get('layout.discover'));
|
this.toggleHide('top-discover', !this.settings.get('layout.discover'));
|
||||||
|
|
||||||
this.toggle('square-avatars', ! this.settings.get('channel.round-avatars'));
|
this.toggle('square-avatars', ! this.settings.get('channel.round-avatars'));
|
||||||
this.toggleHide('not-live-bar', ! this.settings.get('channel.not-live-bar'));
|
this.toggleHide('not-live-bar', this.settings.get('channel.hide-not-live-bar'));
|
||||||
|
|
||||||
const recs = this.settings.get('layout.side-nav.show-rec-channels');
|
const recs = this.settings.get('layout.side-nav.show-rec-channels');
|
||||||
this.toggleHide('side-rec-channels', recs === 0);
|
this.toggleHide('side-rec-channels', recs === 0);
|
||||||
|
|
|
@ -85,26 +85,15 @@ export default class Directory extends SiteModule {
|
||||||
|
|
||||||
|
|
||||||
this.settings.add('directory.show-channel-avatars', {
|
this.settings.add('directory.show-channel-avatars', {
|
||||||
default: 1,
|
default: true,
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
path: 'Directory > Channels >> Appearance',
|
path: 'Directory > Channels >> Appearance',
|
||||||
title: 'Channel Avatars',
|
title: 'Display channel avatars.',
|
||||||
description: 'Show channel avatars next to stream titles or directly on their thumbnails.',
|
component: 'setting-check-box'
|
||||||
component: 'setting-select-box',
|
|
||||||
|
|
||||||
data: [
|
|
||||||
{value: 0, title: 'Disabled'},
|
|
||||||
{value: 1, title: 'By Title'},
|
|
||||||
{value: 2, title: 'Over Thumbnail (Hidden on Hover)'},
|
|
||||||
{value: 3, title: 'Over Thumbnail'}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
changed: value => {
|
changed: () => this.DirectoryCard.forceUpdate()
|
||||||
this.css_tweaks.toggleHide('profile-hover', value === 2);
|
|
||||||
this.DirectoryCard.forceUpdate();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -225,7 +214,7 @@ export default class Directory extends SiteModule {
|
||||||
|
|
||||||
cls.prototype.renderIconicImage = function() {
|
cls.prototype.renderIconicImage = function() {
|
||||||
if ( this.props.context !== CARD_CONTEXTS.SingleChannelList &&
|
if ( this.props.context !== CARD_CONTEXTS.SingleChannelList &&
|
||||||
t.settings.get('directory.show-channel-avatars') !== 1 )
|
! t.settings.get('directory.show-channel-avatars') )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
return old_render_iconic.call(this);
|
return old_render_iconic.call(this);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="ffz--icon-picker">
|
<div class="ffz--icon-picker">
|
||||||
<div class="tw-full-width">
|
<div class="tw-full-width">
|
||||||
<div class="tw-search-input">
|
<div class="tw-search-input">
|
||||||
<label for="icon-search" class="tw-hide-accessible">
|
<label :for="'icon-search$' + id" class="tw-hide-accessible">
|
||||||
{{ t('setting.icon.search', 'Search for Icon') }}
|
{{ t('setting.icon.search', 'Search for Icon') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="tw-relative tw-mg-t-05">
|
<div class="tw-relative tw-mg-t-05">
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
<figure class="ffz-i-search" />
|
<figure class="ffz-i-search" />
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
id="icon-search"
|
:id="'icon-search$' + id"
|
||||||
:placeholder="t('setting.actions.icon.search', 'Search for Icon')"
|
:placeholder="t('setting.actions.icon.search', 'Search for Icon')"
|
||||||
v-model="search"
|
v-model="search"
|
||||||
type="search"
|
type="search"
|
||||||
|
@ -23,13 +23,14 @@
|
||||||
</div>
|
</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">
|
<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" >
|
<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.icon === i[0]"
|
:aria-checked="value === i[0]"
|
||||||
:class="{'tw-interactable--selected': value.icon === i[0]}"
|
:class="{'tw-interactable--selected': value === i[0]}"
|
||||||
class="ffz-icon tw-interactable tw-interactable--inverted"
|
:data-title="i[1]"
|
||||||
|
class="ffz-tooltip ffz-icon tw-interactable tw-interactable--inverted"
|
||||||
role="radio"
|
role="radio"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@keydown.space.stop.prevent=""
|
@keydown.space.stop.prevent=""
|
||||||
|
@ -50,50 +51,81 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
|
||||||
import {escape_regex, deep_copy} from 'utilities/object';
|
import {escape_regex, deep_copy} 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';
|
||||||
|
|
||||||
const FFZ_ICONS = [
|
const FFZ_ICONS = [
|
||||||
|
'cancel',
|
||||||
'zreknarf',
|
'zreknarf',
|
||||||
'crown',
|
'search',
|
||||||
'verified',
|
|
||||||
'inventory',
|
|
||||||
'ignore',
|
|
||||||
'pin-outline',
|
|
||||||
'pin',
|
|
||||||
'block',
|
|
||||||
'ok',
|
|
||||||
'clock',
|
'clock',
|
||||||
'eye',
|
|
||||||
'eye-off',
|
|
||||||
'trash',
|
|
||||||
'discord',
|
|
||||||
'star',
|
'star',
|
||||||
'star-empty',
|
'star-empty',
|
||||||
'twitch',
|
'down-dir',
|
||||||
'twitter',
|
'right-dir',
|
||||||
|
'attention',
|
||||||
|
'ok',
|
||||||
|
'cog',
|
||||||
|
'plus',
|
||||||
|
'folder-open',
|
||||||
'download',
|
'download',
|
||||||
'upload',
|
'upload',
|
||||||
'download-cloud',
|
'floppy',
|
||||||
'upload-cloud',
|
'crown',
|
||||||
|
'verified',
|
||||||
|
'heart',
|
||||||
|
'heart-empty',
|
||||||
'tag',
|
'tag',
|
||||||
'tags',
|
'tags',
|
||||||
'retweet',
|
'retweet',
|
||||||
'thumbs-up',
|
'thumbs-up',
|
||||||
'thumbs-down',
|
'thumbs-down',
|
||||||
'bell',
|
'bell',
|
||||||
'bell-off',
|
|
||||||
'pencil',
|
'pencil',
|
||||||
'info',
|
'info',
|
||||||
'help',
|
'help',
|
||||||
'calendar',
|
'calendar',
|
||||||
|
'left-dir',
|
||||||
|
'inventory',
|
||||||
'lock',
|
'lock',
|
||||||
'lock-open',
|
'lock-open',
|
||||||
'arrows-cw',
|
'arrows-cw',
|
||||||
|
'ignore',
|
||||||
|
'block',
|
||||||
|
'pin',
|
||||||
|
'pin-outline',
|
||||||
'gift',
|
'gift',
|
||||||
'eyedropper',
|
'discord',
|
||||||
|
'eye',
|
||||||
|
'eye-off',
|
||||||
|
'views',
|
||||||
|
'conversations',
|
||||||
|
'channels',
|
||||||
|
'camera',
|
||||||
|
'cw',
|
||||||
|
'up-dir',
|
||||||
|
'up-big',
|
||||||
|
'link-ext',
|
||||||
|
'twitter',
|
||||||
'github',
|
'github',
|
||||||
'user-secret'
|
'gauge',
|
||||||
|
'download-cloud',
|
||||||
|
'upload-cloud',
|
||||||
|
'smile',
|
||||||
|
'keyboard',
|
||||||
|
'calendar-empty',
|
||||||
|
'ellipsis-vert',
|
||||||
|
'twitch',
|
||||||
|
'bell-off',
|
||||||
|
'trash',
|
||||||
|
'eyedropper',
|
||||||
|
'user-secret',
|
||||||
|
'window-maximize',
|
||||||
|
'window-minimize',
|
||||||
|
'window-restore',
|
||||||
|
'window-close'
|
||||||
];
|
];
|
||||||
|
|
||||||
const FFZ_ALIASES = {
|
const FFZ_ALIASES = {
|
||||||
|
@ -112,6 +144,7 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
id: id++,
|
||||||
search: '',
|
search: '',
|
||||||
icons: deep_copy(ICONS)
|
icons: deep_copy(ICONS)
|
||||||
}
|
}
|
||||||
|
@ -131,11 +164,19 @@ export default {
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
load();
|
load();
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if ( this.value ) {
|
||||||
|
const el = this.$el.querySelector('.tw-interactable--selected');
|
||||||
|
if ( el )
|
||||||
|
el.scrollIntoViewIfNeeded();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
change(val) {
|
change(val) {
|
||||||
this.value.icon = val;
|
this.value = val;
|
||||||
this.$emit('input', this.value);
|
this.$emit('input', this.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<a :href="href" @click="reactNavigate(href, $event)">
|
<a :href="href" @click="onClick($event)">
|
||||||
<slot />
|
<slot />
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
@ -7,7 +7,17 @@
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['href']
|
props: ['href', 'click'],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onClick(event) {
|
||||||
|
this.$emit('click', event);
|
||||||
|
|
||||||
|
if ( ! event.defaultPrevented )
|
||||||
|
this.reactNavigate(this.href, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
|
@ -237,3 +237,9 @@ export const load = () => {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const maybeLoad = icon => {
|
||||||
|
if ( loaded || ! String(icon).startsWith('fa-') && ! String(icon).startsWith('ffz-fa') )
|
||||||
|
return;
|
||||||
|
|
||||||
|
load();
|
||||||
|
}
|
|
@ -2,6 +2,28 @@
|
||||||
|
|
||||||
const HOP = Object.prototype.hasOwnProperty;
|
const HOP = Object.prototype.hasOwnProperty;
|
||||||
|
|
||||||
|
// Source: https://gist.github.com/jed/982883 (WTFPL)
|
||||||
|
export function generateUUID(input) {
|
||||||
|
return input // if the placeholder was passed, return
|
||||||
|
? ( // a random number from 0 to 15
|
||||||
|
input ^ // unless b is 8,
|
||||||
|
Math.random() // in which case
|
||||||
|
* 16 // a random number from
|
||||||
|
>> input/4 // 8 to 11
|
||||||
|
).toString(16) // in hexadecimal
|
||||||
|
: ( // or otherwise a concatenated string:
|
||||||
|
[1e7] + // 10000000 +
|
||||||
|
-1e3 + // -1000 +
|
||||||
|
-4e3 + // -4000 +
|
||||||
|
-8e3 + // -80000000 +
|
||||||
|
-1e11 // -100000000000,
|
||||||
|
).replace( // replacing
|
||||||
|
/[018]/g, // zeroes, ones, and eights with
|
||||||
|
generateUUID // random hex digits
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function has(object, key) {
|
export function has(object, key) {
|
||||||
return object ? HOP.call(object, key) : false;
|
return object ? HOP.call(object, key) : false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
import {has} from 'utilities/object';
|
import {has} from 'utilities/object';
|
||||||
|
import {DEBUG} from 'utilities/constants';
|
||||||
|
|
||||||
|
|
||||||
export class Vue extends Module {
|
export class Vue extends Module {
|
||||||
|
@ -18,12 +19,15 @@ 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'),
|
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);
|
this.component((await import(/* webpackChunkName: "vue" */ 'src/std-components/index.js')).default);
|
||||||
|
|
||||||
if ( this.root.raven )
|
Vue.use(ObserveVisibility);
|
||||||
|
|
||||||
|
if ( ! DEBUG && this.root.raven )
|
||||||
this.root.raven.addPlugin(RavenVue, Vue);
|
this.root.raven.addPlugin(RavenVue, Vue);
|
||||||
|
|
||||||
for(const key in components)
|
for(const key in components)
|
||||||
|
@ -82,6 +86,11 @@ export class Vue extends Module {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.on('i18n:transform', () => {
|
||||||
|
this._vue_i18n.locale = this.i18n.locale;
|
||||||
|
this._vue_i18n.phrases = {};
|
||||||
|
});
|
||||||
|
|
||||||
this.on('i18n:changed', () => {
|
this.on('i18n:changed', () => {
|
||||||
this._vue_i18n.locale = this.i18n.locale;
|
this._vue_i18n.locale = this.i18n.locale;
|
||||||
this._vue_i18n.phrases = {};
|
this._vue_i18n.phrases = {};
|
||||||
|
@ -97,6 +106,41 @@ export class Vue extends Module {
|
||||||
vue.prototype.$i18n = this._vue_i18n;
|
vue.prototype.$i18n = this._vue_i18n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vue.component('t-list', {
|
||||||
|
props: {
|
||||||
|
tag: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
phrase: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render(createElement) {
|
||||||
|
return createElement(
|
||||||
|
this.tag || 'span',
|
||||||
|
this.$i18n.tList_(
|
||||||
|
this.phrase,
|
||||||
|
this.default,
|
||||||
|
Object.assign({}, this.data, this.$scopedSlots)
|
||||||
|
).map(out => {
|
||||||
|
if ( typeof out === 'function' )
|
||||||
|
return out();
|
||||||
|
return out;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
vue.mixin({
|
vue.mixin({
|
||||||
methods: {
|
methods: {
|
||||||
reactNavigate(url, event) {
|
reactNavigate(url, event) {
|
||||||
|
|
|
@ -121,6 +121,9 @@
|
||||||
.ffz-i-conversations:before { content: '\e82c'; } /* '' */
|
.ffz-i-conversations:before { content: '\e82c'; } /* '' */
|
||||||
.ffz-i-channels:before { content: '\e82d'; } /* '' */
|
.ffz-i-channels:before { content: '\e82d'; } /* '' */
|
||||||
.ffz-i-camera:before { content: '\e82e'; } /* '' */
|
.ffz-i-camera:before { content: '\e82e'; } /* '' */
|
||||||
|
.ffz-i-cw:before { content: '\e82f'; } /* '' */
|
||||||
|
.ffz-i-up-dir:before { content: '\e830'; } /* '' */
|
||||||
|
.ffz-i-up-big:before { content: '\e831'; } /* '' */
|
||||||
.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'; } /* '' */
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
@import "./widgets/add-ons.scss";
|
@import "./widgets/add-ons.scss";
|
||||||
|
|
||||||
@import "./widgets/color-picker.scss";
|
@import "./widgets/color-picker.scss";
|
||||||
|
@import "./widgets/icon-picker.scss";
|
||||||
|
|
||||||
|
|
||||||
.tw-display-inline { display: inline !important }
|
.tw-display-inline { display: inline !important }
|
||||||
|
|
12
styles/widgets/icon-picker.scss
Normal file
12
styles/widgets/icon-picker.scss
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
.ffz--icon-picker__list {
|
||||||
|
max-height: 15rem;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
|
||||||
|
.ffz-icon {
|
||||||
|
width: auto !important;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue