mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-28 15:27:43 +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": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.1.2.tgz",
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
"vue": "^2.5.16",
|
||||
"vue-clickaway": "^2.2.2",
|
||||
"vue-color": "^2.4.6",
|
||||
"vue-observe-visibility": "^0.4.4",
|
||||
"vue-template-compiler": "^2.5.16",
|
||||
"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="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="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;
|
||||
|
||||
const VER = FrankerFaceZ.version_info = {
|
||||
major: 4, minor: 2, revision: 6,
|
||||
major: 4, minor: 3, revision: 0,
|
||||
commit: __git_commit__,
|
||||
build: __webpack_hash__,
|
||||
toString: () =>
|
||||
|
@ -174,7 +174,8 @@ FrankerFaceZ.utilities = {
|
|||
time: require('utilities/time'),
|
||||
tooltip: require('utilities/tooltip'),
|
||||
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') }}
|
||||
</label>
|
||||
|
||||
<div class="tw-full-width">
|
||||
<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>
|
||||
<icon-picker :value="value.icon" @input="change" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
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: {
|
||||
change(val) {
|
||||
this.value.icon = val;
|
||||
|
@ -146,15 +21,4 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ffz-icon-picker {
|
||||
max-height: 15rem;
|
||||
font-size: 1.6rem;
|
||||
|
||||
.ffz-icon {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</script>
|
|
@ -5,6 +5,7 @@
|
|||
// ============================================================================
|
||||
|
||||
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+)/;
|
||||
|
||||
import GET_CLIP from './clip_info.gql';
|
||||
|
@ -74,13 +75,18 @@ export const Clips = {
|
|||
hide_token: true,
|
||||
|
||||
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) {
|
||||
const match = CLIP_URL.exec(token.url),
|
||||
apollo = this.resolve('site.apollo');
|
||||
let match = CLIP_URL.exec(token.url);
|
||||
if ( ! match )
|
||||
match = NEW_CLIP_URL.exec(token.url);
|
||||
|
||||
const apollo = this.resolve('site.apollo');
|
||||
if ( ! apollo || ! match || match[1] === 'create' )
|
||||
return;
|
||||
|
||||
|
|
|
@ -415,7 +415,7 @@ export const BlockedBadges = {
|
|||
|
||||
export const CustomHighlights = {
|
||||
type: 'highlight',
|
||||
priority: 100,
|
||||
priority: 35,
|
||||
|
||||
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);
|
||||
|
||||
Mousetrap.bindGlobal('alt', inst.ffzHandleKey, 'keydown');
|
||||
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');
|
||||
window.addEventListener('keydown', inst.ffzHandleKey);
|
||||
window.addEventListener('keyup', inst.ffzHandleKey);
|
||||
|
||||
inst.hoverPause = inst.ffzMouseMove.bind(inst);
|
||||
inst.hoverResume = inst.ffzMouseLeave.bind(inst);
|
||||
|
@ -624,18 +614,8 @@ export default class Scroller extends Module {
|
|||
inst.ffzInstallHandler();
|
||||
}
|
||||
|
||||
onUnmount() { // eslint-disable-line class-methods-use-this
|
||||
const Mousetrap = this.web_munch.getModule('mousetrap') || window.Mousetrap;
|
||||
if ( Mousetrap != null ) {
|
||||
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');
|
||||
}
|
||||
|
||||
onUnmount(inst) { // eslint-disable-line class-methods-use-this
|
||||
window.removeEventListener('keydown', inst.ffzHandleKey);
|
||||
window.removeEventListener('keyup', inst.ffzHandleKey);
|
||||
}
|
||||
}
|
|
@ -230,14 +230,15 @@ export default class CSSTweaks extends Module {
|
|||
changed: val => this.toggle('square-avatars', !val)
|
||||
});
|
||||
|
||||
this.settings.add('channel.not-live-bar', {
|
||||
this.settings.add('channel.hide-not-live-bar', {
|
||||
default: true,
|
||||
ui: {
|
||||
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'
|
||||
},
|
||||
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.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');
|
||||
this.toggleHide('side-rec-channels', recs === 0);
|
||||
|
|
|
@ -85,26 +85,15 @@ export default class Directory extends SiteModule {
|
|||
|
||||
|
||||
this.settings.add('directory.show-channel-avatars', {
|
||||
default: 1,
|
||||
default: true,
|
||||
|
||||
ui: {
|
||||
path: 'Directory > Channels >> Appearance',
|
||||
title: 'Channel Avatars',
|
||||
description: 'Show channel avatars next to stream titles or directly on their thumbnails.',
|
||||
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'}
|
||||
]
|
||||
title: 'Display channel avatars.',
|
||||
component: 'setting-check-box'
|
||||
},
|
||||
|
||||
changed: value => {
|
||||
this.css_tweaks.toggleHide('profile-hover', value === 2);
|
||||
this.DirectoryCard.forceUpdate();
|
||||
}
|
||||
changed: () => this.DirectoryCard.forceUpdate()
|
||||
});
|
||||
|
||||
|
||||
|
@ -225,7 +214,7 @@ export default class Directory extends SiteModule {
|
|||
|
||||
cls.prototype.renderIconicImage = function() {
|
||||
if ( this.props.context !== CARD_CONTEXTS.SingleChannelList &&
|
||||
t.settings.get('directory.show-channel-avatars') !== 1 )
|
||||
! t.settings.get('directory.show-channel-avatars') )
|
||||
return;
|
||||
|
||||
return old_render_iconic.call(this);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="ffz--icon-picker">
|
||||
<div class="tw-full-width">
|
||||
<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') }}
|
||||
</label>
|
||||
<div class="tw-relative tw-mg-t-05">
|
||||
|
@ -10,7 +10,7 @@
|
|||
<figure class="ffz-i-search" />
|
||||
</div>
|
||||
<input
|
||||
id="icon-search"
|
||||
:id="'icon-search$' + id"
|
||||
:placeholder="t('setting.actions.icon.search', 'Search for Icon')"
|
||||
v-model="search"
|
||||
type="search"
|
||||
|
@ -23,13 +23,14 @@
|
|||
</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" >
|
||||
<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.icon === i[0]"
|
||||
:class="{'tw-interactable--selected': value.icon === i[0]}"
|
||||
class="ffz-icon tw-interactable tw-interactable--inverted"
|
||||
: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=""
|
||||
|
@ -50,50 +51,81 @@
|
|||
|
||||
<script>
|
||||
|
||||
let id = 0;
|
||||
|
||||
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 = [
|
||||
'cancel',
|
||||
'zreknarf',
|
||||
'crown',
|
||||
'verified',
|
||||
'inventory',
|
||||
'ignore',
|
||||
'pin-outline',
|
||||
'pin',
|
||||
'block',
|
||||
'ok',
|
||||
'search',
|
||||
'clock',
|
||||
'eye',
|
||||
'eye-off',
|
||||
'trash',
|
||||
'discord',
|
||||
'star',
|
||||
'star-empty',
|
||||
'twitch',
|
||||
'twitter',
|
||||
'down-dir',
|
||||
'right-dir',
|
||||
'attention',
|
||||
'ok',
|
||||
'cog',
|
||||
'plus',
|
||||
'folder-open',
|
||||
'download',
|
||||
'upload',
|
||||
'download-cloud',
|
||||
'upload-cloud',
|
||||
'floppy',
|
||||
'crown',
|
||||
'verified',
|
||||
'heart',
|
||||
'heart-empty',
|
||||
'tag',
|
||||
'tags',
|
||||
'retweet',
|
||||
'thumbs-up',
|
||||
'thumbs-down',
|
||||
'bell',
|
||||
'bell-off',
|
||||
'pencil',
|
||||
'info',
|
||||
'help',
|
||||
'calendar',
|
||||
'left-dir',
|
||||
'inventory',
|
||||
'lock',
|
||||
'lock-open',
|
||||
'arrows-cw',
|
||||
'ignore',
|
||||
'block',
|
||||
'pin',
|
||||
'pin-outline',
|
||||
'gift',
|
||||
'eyedropper',
|
||||
'discord',
|
||||
'eye',
|
||||
'eye-off',
|
||||
'views',
|
||||
'conversations',
|
||||
'channels',
|
||||
'camera',
|
||||
'cw',
|
||||
'up-dir',
|
||||
'up-big',
|
||||
'link-ext',
|
||||
'twitter',
|
||||
'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 = {
|
||||
|
@ -112,6 +144,7 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
id: id++,
|
||||
search: '',
|
||||
icons: deep_copy(ICONS)
|
||||
}
|
||||
|
@ -131,11 +164,19 @@ export default {
|
|||
|
||||
mounted() {
|
||||
load();
|
||||
|
||||
this.$nextTick(() => {
|
||||
if ( this.value ) {
|
||||
const el = this.$el.querySelector('.tw-interactable--selected');
|
||||
if ( el )
|
||||
el.scrollIntoViewIfNeeded();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
change(val) {
|
||||
this.value.icon = val;
|
||||
this.value = val;
|
||||
this.$emit('input', this.value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<a :href="href" @click="reactNavigate(href, $event)">
|
||||
<a :href="href" @click="onClick($event)">
|
||||
<slot />
|
||||
</a>
|
||||
</template>
|
||||
|
@ -7,7 +7,17 @@
|
|||
<script>
|
||||
|
||||
export default {
|
||||
props: ['href']
|
||||
props: ['href', 'click'],
|
||||
|
||||
methods: {
|
||||
onClick(event) {
|
||||
this.$emit('click', event);
|
||||
|
||||
if ( ! event.defaultPrevented )
|
||||
this.reactNavigate(this.href, event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</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;
|
||||
|
||||
// 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) {
|
||||
return object ? HOP.call(object, key) : false;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Module from 'utilities/module';
|
||||
import {has} from 'utilities/object';
|
||||
import {DEBUG} from 'utilities/constants';
|
||||
|
||||
|
||||
export class Vue extends Module {
|
||||
|
@ -18,12 +19,15 @@ export class Vue extends Module {
|
|||
|
||||
async onLoad() {
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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._vue_i18n.locale = this.i18n.locale;
|
||||
this._vue_i18n.phrases = {};
|
||||
|
@ -97,6 +106,41 @@ export class Vue extends Module {
|
|||
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({
|
||||
methods: {
|
||||
reactNavigate(url, event) {
|
||||
|
|
|
@ -121,6 +121,9 @@
|
|||
.ffz-i-conversations:before { content: '\e82c'; } /* '' */
|
||||
.ffz-i-channels:before { content: '\e82d'; } /* '' */
|
||||
.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-twitter:before { content: '\f099'; } /* '' */
|
||||
.ffz-i-github:before { content: '\f09b'; } /* '' */
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
@import "./widgets/add-ons.scss";
|
||||
|
||||
@import "./widgets/color-picker.scss";
|
||||
@import "./widgets/icon-picker.scss";
|
||||
|
||||
|
||||
.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