mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-08 23:30:53 +00:00
4.1.2
* Fixed: Mentions not appearing in bold font. * Fixed: Highlighting messages in chat when opening a viewer card by clicking a mention that wasn't all lower-case. * Fixed: Detection of External add-ons when there is a name collision. * Changed: Do not report name collision errors. * Changed: Allow for multi-line add-on descriptions.
This commit is contained in:
parent
cdedf29d04
commit
535192d931
11 changed files with 382 additions and 243 deletions
|
@ -113,6 +113,7 @@ module.exports = {
|
||||||
"warn",
|
"warn",
|
||||||
"tab"
|
"tab"
|
||||||
],
|
],
|
||||||
|
"vue/valid-template-root": "off",
|
||||||
"vue/max-attributes-per-line": "off",
|
"vue/max-attributes-per-line": "off",
|
||||||
"vue/require-prop-types": "off",
|
"vue/require-prop-types": "off",
|
||||||
"vue/require-default-prop": "off",
|
"vue/require-default-prop": "off",
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
import { DEBUG, SERVER } from 'utilities/constants';
|
import { SERVER } from 'utilities/constants';
|
||||||
import { createElement } from 'utilities/dom';
|
import { createElement } from 'utilities/dom';
|
||||||
import { timeout, has } from 'utilities/object';
|
import { timeout, has } from 'utilities/object';
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ export default class AddonManager extends Module {
|
||||||
|
|
||||||
this.settings.addUI('add-ons', {
|
this.settings.addUI('add-ons', {
|
||||||
path: 'Add-Ons @{"description": "Add-Ons are additional modules, often written by other people, that can be loaded automatically by FrankerFaceZ to add new capabilities and behaviors to the extension and Twitch."}',
|
path: 'Add-Ons @{"description": "Add-Ons are additional modules, often written by other people, that can be loaded automatically by FrankerFaceZ to add new capabilities and behaviors to the extension and Twitch."}',
|
||||||
component: 'add-ons',
|
component: 'addon-list',
|
||||||
title: 'Add-Ons',
|
title: 'Add-Ons',
|
||||||
no_filter: true,
|
no_filter: true,
|
||||||
|
|
||||||
|
@ -129,7 +129,6 @@ export default class AddonManager extends Module {
|
||||||
|
|
||||||
addon.name_i18n = addon.name_i18n || `addon.${addon.id}.name`;
|
addon.name_i18n = addon.name_i18n || `addon.${addon.id}.name`;
|
||||||
addon.short_name_i18n = addon.short_name_i18n || `addon.${addon.id}.short_name`;
|
addon.short_name_i18n = addon.short_name_i18n || `addon.${addon.id}.short_name`;
|
||||||
addon.description_i18n = addon.description_i18n || `addon.${addon.id}.description`;
|
|
||||||
addon.author_i18n = addon.author_i18n || `addon.${addon.id}.author`;
|
addon.author_i18n = addon.author_i18n || `addon.${addon.id}.author`;
|
||||||
|
|
||||||
addon.dev = is_dev;
|
addon.dev = is_dev;
|
||||||
|
@ -173,6 +172,9 @@ export default class AddonManager extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
isAddonEnabled(id) {
|
isAddonEnabled(id) {
|
||||||
|
if ( this.isAddonExternal(id) )
|
||||||
|
return true;
|
||||||
|
|
||||||
return this.enabled_addons.includes(id);
|
return this.enabled_addons.includes(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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: 1, revision: 1,
|
major: 4, minor: 1, revision: 2,
|
||||||
commit: __git_commit__,
|
commit: __git_commit__,
|
||||||
build: __webpack_hash__,
|
build: __webpack_hash__,
|
||||||
toString: () =>
|
toString: () =>
|
||||||
|
|
|
@ -209,13 +209,13 @@ export const Mentions = {
|
||||||
},
|
},
|
||||||
|
|
||||||
render(token, createElement) {
|
render(token, createElement) {
|
||||||
return (<span
|
return (<strong
|
||||||
class={`chat-line__message-mention${token.me ? ' ffz--mention-me' : ''}`}
|
class={`chat-line__message-mention${token.me ? ' ffz--mention-me' : ''}`}
|
||||||
data-login={token.recipient}
|
data-login={token.recipient}
|
||||||
onClick={this.handleMentionClick}
|
onClick={this.handleMentionClick}
|
||||||
>
|
>
|
||||||
{token.text}
|
{token.text}
|
||||||
</span>)
|
</strong>)
|
||||||
},
|
},
|
||||||
|
|
||||||
process(tokens, msg, user) {
|
process(tokens, msg, user) {
|
||||||
|
@ -274,7 +274,7 @@ export const Mentions = {
|
||||||
type: 'mention',
|
type: 'mention',
|
||||||
text: `${at}${recipient}`,
|
text: `${at}${recipient}`,
|
||||||
me: mentioned,
|
me: mentioned,
|
||||||
recipient
|
recipient: recipient ? recipient.toLowerCase() : ''
|
||||||
});
|
});
|
||||||
|
|
||||||
if ( mentioned )
|
if ( mentioned )
|
||||||
|
|
|
@ -1,231 +0,0 @@
|
||||||
<template lang="html">
|
|
||||||
<div class="ffz--addons tw-border-t tw-pd-y-1">
|
|
||||||
<div v-if="reload" class="tw-mg-y-1 tw-c-background-accent tw-c-text-overlay tw-pd-1">
|
|
||||||
<h4 class="ffz-i-attention">
|
|
||||||
{{ t('addon.refresh-needed', 'You must refresh your Twitch pages for some changes to take effect.') }}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="tw-button tw-button--hollow tw-mg-t-05"
|
|
||||||
@click="item.refresh()"
|
|
||||||
>
|
|
||||||
<span class="tw-button__icon tw-button__icon--left">
|
|
||||||
<figure class="ffz-i-arrows-cw" />
|
|
||||||
</span>
|
|
||||||
<span class="tw-button__text">
|
|
||||||
{{ t('addon.refresh', 'Refresh') }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="! ready" class="tw-align-center tw-pd-1">
|
|
||||||
<h1 class="tw-mg-5 ffz-i-zreknarf loading" />
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<div
|
|
||||||
v-for="addon in sorted_addons"
|
|
||||||
v-if="shouldShow(addon)"
|
|
||||||
:key="addon.id"
|
|
||||||
class="ffz--addon-info tw-elevation-1 tw-c-background-base tw-border tw-pd-1 tw-mg-b-1 tw-flex tw-flex-nowrap"
|
|
||||||
>
|
|
||||||
<div class="tw-flex tw-flex-column tw-align-center tw-flex-shrink-0 tw-mg-r-1">
|
|
||||||
<div class="tw-card-img--size-6 tw-overflow-hidden tw-mg-b-1">
|
|
||||||
<img :src="addon.icon" class="tw-image">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="enabled[addon.id]" class="tw-mg-b-05 tw-pill ffz--pill-enabled">
|
|
||||||
{{ t('addon.enabled', 'Enabled') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="addon.dev" class="tw-mg-b-05 tw-pill">
|
|
||||||
{{ t('addon.dev', 'Developer') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="item.isAddonExternal(addon.id)" class="tw-mg-b-05 tw-pill">
|
|
||||||
{{ t('addon.external', 'External') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tw-flex-grow-1">
|
|
||||||
<div class="tw-border-b tw-mg-b-05">
|
|
||||||
<h4>{{ t(addon.name_i18n, addon.name) }} <span class="tw-c-text-alt-2 tw-font-size-6">({{ addon.id }})</span></h4>
|
|
||||||
<span class="tw-c-text-alt tw-mg-r-1">
|
|
||||||
{{ t('addon.author', 'By: {author}', {
|
|
||||||
author: t(addon.author_i18n, addon.author)
|
|
||||||
}) }}
|
|
||||||
</span>
|
|
||||||
<span v-if="item.getVersion(addon.id)" class="tw-c-text-alt">
|
|
||||||
{{ t('addon.version', 'Version {version}', {version: item.getVersion(addon.id)}) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<markdown :source="t(addon.description_i18n, addon.description)" />
|
|
||||||
|
|
||||||
<div class="tw-mg-t-1 tw-pd-t-1 tw-border-t">
|
|
||||||
<template v-if="enabled[addon.id]">
|
|
||||||
<button
|
|
||||||
v-if="item.isAddonExternal(addon.id)"
|
|
||||||
disabled
|
|
||||||
class="tw-button tw-button--hollow tw-button--disabled tw-tooltip-wrapper tw-mg-r-1"
|
|
||||||
>
|
|
||||||
<span class="tw-button__icon tw-button__icon--left">
|
|
||||||
<figure class="ffz-i-trash" />
|
|
||||||
</span>
|
|
||||||
<span class="tw-button__text">
|
|
||||||
{{ t('addon.disable', 'Disable') }}
|
|
||||||
</span>
|
|
||||||
<div class="tw-tooltip tw-tooltip--up tw-tooltip--align-left">
|
|
||||||
{{ t('addon.external.description', 'This add-on has been loaded by an external script and cannot be disabled here.') }}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-else
|
|
||||||
class="tw-button tw-button--hollow ffz--button-disable tw-mg-r-1"
|
|
||||||
@click="item.disableAddon(addon.id)"
|
|
||||||
>
|
|
||||||
<span class="tw-button__icon tw-button__icon--left">
|
|
||||||
<figure class="ffz-i-trash" />
|
|
||||||
</span>
|
|
||||||
<span class="tw-button__text">
|
|
||||||
{{ t('addon.disable', 'Disable') }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="addon.settings"
|
|
||||||
class="tw-button tw-button--hollow tw-mg-r-1"
|
|
||||||
@click="openSettings(addon)"
|
|
||||||
>
|
|
||||||
<span class="tw-button__icon tw-button__icon--left">
|
|
||||||
<figure class="ffz-i-cog" />
|
|
||||||
</span>
|
|
||||||
<span class="tw-button__text">
|
|
||||||
{{ t('addon.settings', 'Settings') }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<button
|
|
||||||
class="tw-button tw-button--hollow ffz--button-enable tw-mg-r-1"
|
|
||||||
@click="item.enableAddon(addon.id)"
|
|
||||||
>
|
|
||||||
<span class="tw-button__icon tw-button__icon--left">
|
|
||||||
<figure class="ffz-i-download" />
|
|
||||||
</span>
|
|
||||||
<span class="tw-button__text">
|
|
||||||
{{ t('addon.enable', 'Enable') }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
<a
|
|
||||||
v-if="addon.website"
|
|
||||||
:href="addon.website"
|
|
||||||
:title="addon.website"
|
|
||||||
class="tw-button tw-button--hollow tw-mg-r-1"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
|
||||||
<span class="tw-button__icon tw-button__icon--left">
|
|
||||||
<figure class="ffz-i-link-ext" />
|
|
||||||
</span>
|
|
||||||
<span class="tw-button__text">
|
|
||||||
{{ t('addon.website', 'Website') }}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: ['item', 'context', 'filter'],
|
|
||||||
|
|
||||||
data() {
|
|
||||||
const enabled = {};
|
|
||||||
|
|
||||||
for(const addon of this.item.getAddons())
|
|
||||||
enabled[addon.id] = this.item.isAddonEnabled(addon.id);
|
|
||||||
|
|
||||||
return {
|
|
||||||
ready: this.item.isReady(),
|
|
||||||
reload: this.item.isReloadRequired(),
|
|
||||||
enabled
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
sorted_addons() {
|
|
||||||
const addons = this.item.getAddons();
|
|
||||||
|
|
||||||
addons.sort((a, b) => {
|
|
||||||
if ( a.sort < b.sort ) return -1;
|
|
||||||
if ( b.sort < a.sort ) return 1;
|
|
||||||
|
|
||||||
const a_n = a.name.toLowerCase(),
|
|
||||||
b_n = b.name.toLowerCase();
|
|
||||||
|
|
||||||
if ( a_n < b_n ) return -1;
|
|
||||||
if ( b_n < a_n ) return 1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
return addons;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
this.item.on(':ready', this.onReady, this);
|
|
||||||
this.item.on(':addon-enabled', this.onEnabled, this);
|
|
||||||
this.item.on(':addon-disabled', this.onDisabled, this);
|
|
||||||
this.item.on(':reload-required', this.onReload, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
destroyed() {
|
|
||||||
this.item.off(':ready', this.onReady, this);
|
|
||||||
this.item.off(':addon-enabled', this.onEnabled, this);
|
|
||||||
this.item.off(':addon-disabled', this.onDisabled, this);
|
|
||||||
this.item.off(':reload-required', this.onReload, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
shouldShow(addon) {
|
|
||||||
if ( ! this.filter || ! this.filter.length )
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return addon.search_terms.includes(this.filter)
|
|
||||||
},
|
|
||||||
|
|
||||||
onReady() {
|
|
||||||
this.ready = true;
|
|
||||||
|
|
||||||
// Refresh the enabled cache.
|
|
||||||
for(const addon of this.item.getAddons())
|
|
||||||
this.enabled[addon.id] = this.item.isAddonEnabled(addon.id);
|
|
||||||
},
|
|
||||||
|
|
||||||
onEnabled(id) {
|
|
||||||
this.enabled[id] = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
onDisabled(id) {
|
|
||||||
this.enabled[id] = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
onReload() {
|
|
||||||
this.reload = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
openSettings(addon) {
|
|
||||||
let key;
|
|
||||||
if ( typeof addon.settings === 'string' )
|
|
||||||
key = addon.settings;
|
|
||||||
else
|
|
||||||
key = `add_ons.${addon.id}`;
|
|
||||||
|
|
||||||
this.$emit('navigate', key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
107
src/modules/main_menu/components/addon-list.vue
Normal file
107
src/modules/main_menu/components/addon-list.vue
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
<template lang="html">
|
||||||
|
<div class="ffz--addons tw-border-t tw-pd-y-1">
|
||||||
|
<div v-if="reload" class="tw-mg-y-1 tw-c-background-accent tw-c-text-overlay tw-pd-1">
|
||||||
|
<h4 class="ffz-i-attention">
|
||||||
|
{{ t('addon.refresh-needed', 'You must refresh your Twitch pages for some changes to take effect.') }}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="tw-button tw-button--hollow tw-mg-t-05"
|
||||||
|
@click="item.refresh()"
|
||||||
|
>
|
||||||
|
<span class="tw-button__icon tw-button__icon--left">
|
||||||
|
<figure class="ffz-i-arrows-cw" />
|
||||||
|
</span>
|
||||||
|
<span class="tw-button__text">
|
||||||
|
{{ t('addon.refresh', 'Refresh') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="! ready" class="tw-align-center tw-pd-1">
|
||||||
|
<h1 class="tw-mg-5 ffz-i-zreknarf loading" />
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<addon
|
||||||
|
v-for="addon in sorted_addons"
|
||||||
|
v-if="shouldShow(addon)"
|
||||||
|
:key="addon.id"
|
||||||
|
:id="addon.id"
|
||||||
|
:addon="addon"
|
||||||
|
:item="item"
|
||||||
|
@navigate="navigate"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ['item', 'context', 'filter'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
ready: this.item.isReady(),
|
||||||
|
reload: this.item.isReloadRequired()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
sorted_addons() {
|
||||||
|
const addons = this.item.getAddons();
|
||||||
|
|
||||||
|
addons.sort((a, b) => {
|
||||||
|
if ( a.sort < b.sort ) return -1;
|
||||||
|
if ( b.sort < a.sort ) return 1;
|
||||||
|
|
||||||
|
const a_n = a.name.toLowerCase(),
|
||||||
|
b_n = b.name.toLowerCase();
|
||||||
|
|
||||||
|
if ( a_n < b_n ) return -1;
|
||||||
|
if ( b_n < a_n ) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return addons;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.item.on(':ready', this.onReady, this);
|
||||||
|
this.item.on(':added', this.onAdded, this);
|
||||||
|
this.item.on(':reload-required', this.onReload, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
destroyed() {
|
||||||
|
this.item.off(':ready', this.onReady, this);
|
||||||
|
this.item.off(':added', this.onAdded, this);
|
||||||
|
this.item.off(':reload-required', this.onReload, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
shouldShow(addon) {
|
||||||
|
if ( ! this.filter || ! this.filter.length )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return addon.search_terms.includes(this.filter)
|
||||||
|
},
|
||||||
|
|
||||||
|
onAdded() {
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
|
onReady() {
|
||||||
|
this.ready = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
onReload() {
|
||||||
|
this.reload = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
navigate(...args) {
|
||||||
|
this.$emit('navigate', ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
224
src/modules/main_menu/components/addon.vue
Normal file
224
src/modules/main_menu/components/addon.vue
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
<template lang="html">
|
||||||
|
<div class="ffz--addon-info tw-elevation-1 tw-c-background-base tw-border tw-pd-1 tw-mg-b-1 tw-flex tw-flex-nowrap">
|
||||||
|
<div class="tw-flex tw-flex-column tw-align-center tw-flex-shrink-0 tw-mg-r-1">
|
||||||
|
<div class="tw-card-img--size-6 tw-overflow-hidden tw-mg-b-1">
|
||||||
|
<img :src="icon" class="tw-image">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="external" class="tw-mg-b-05 tw-pill">
|
||||||
|
{{ t('addon.external', 'External') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="enabled" class="tw-mg-b-05 tw-pill ffz--pill-enabled">
|
||||||
|
{{ t('addon.enabled', 'Enabled') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="addon.dev" class="tw-mg-b-05 tw-pill">
|
||||||
|
{{ t('addon.dev', 'Developer') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-flex-grow-1">
|
||||||
|
<div class="tw-border-b tw-mg-b-05">
|
||||||
|
<h4>{{ t(addon.name_i18n, addon.name) }} <span class="tw-c-text-alt-2 tw-font-size-6">({{ addon.id }})</span></h4>
|
||||||
|
<span class="tw-c-text-alt tw-mg-r-1">
|
||||||
|
{{ t('addon.author', 'By: {author}', {
|
||||||
|
author: t(addon.author_i18n, addon.author)
|
||||||
|
}) }}
|
||||||
|
</span>
|
||||||
|
<span v-if="version" class="tw-c-text-alt">
|
||||||
|
{{ t('addon.version', 'Version {version}', {version}) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<markdown :source="show_description" />
|
||||||
|
<a
|
||||||
|
v-if="multi_line"
|
||||||
|
href="#"
|
||||||
|
class="tw-c-text-alt-2"
|
||||||
|
@click.prevent="toggle"
|
||||||
|
>
|
||||||
|
<template v-if="expanded">{{ t('addon.show-less', '(Show Less)') }}</template>
|
||||||
|
<template v-else>{{ t('addon.show-more', '(Show More)') }}</template>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="tw-mg-t-1 tw-pd-t-1 tw-border-t">
|
||||||
|
<template v-if="enabled">
|
||||||
|
<button
|
||||||
|
v-if="external"
|
||||||
|
disabled
|
||||||
|
class="tw-button tw-button--hollow tw-button--disabled tw-tooltip-wrapper tw-mg-r-1"
|
||||||
|
>
|
||||||
|
<span class="tw-button__icon tw-button__icon--left">
|
||||||
|
<figure class="ffz-i-trash" />
|
||||||
|
</span>
|
||||||
|
<span class="tw-button__text">
|
||||||
|
{{ t('addon.disable', 'Disable') }}
|
||||||
|
</span>
|
||||||
|
<div class="tw-tooltip tw-tooltip--up tw-tooltip--align-left">
|
||||||
|
{{ t('addon.external.description', 'This add-on has been loaded by an external script and cannot be disabled here.') }}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="tw-button tw-button--hollow ffz--button-disable tw-mg-r-1"
|
||||||
|
@click="item.disableAddon(id)"
|
||||||
|
>
|
||||||
|
<span class="tw-button__icon tw-button__icon--left">
|
||||||
|
<figure class="ffz-i-trash" />
|
||||||
|
</span>
|
||||||
|
<span class="tw-button__text">
|
||||||
|
{{ t('addon.disable', 'Disable') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="addon.settings"
|
||||||
|
class="tw-button tw-button--hollow tw-mg-r-1"
|
||||||
|
@click="openSettings()"
|
||||||
|
>
|
||||||
|
<span class="tw-button__icon tw-button__icon--left">
|
||||||
|
<figure class="ffz-i-cog" />
|
||||||
|
</span>
|
||||||
|
<span class="tw-button__text">
|
||||||
|
{{ t('addon.settings', 'Settings') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<button
|
||||||
|
class="tw-button tw-button--hollow ffz--button-enable tw-mg-r-1"
|
||||||
|
@click="item.enableAddon(id)"
|
||||||
|
>
|
||||||
|
<span class="tw-button__icon tw-button__icon--left">
|
||||||
|
<figure class="ffz-i-download" />
|
||||||
|
</span>
|
||||||
|
<span class="tw-button__text">
|
||||||
|
{{ t('addon.enable', 'Enable') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<a
|
||||||
|
v-if="addon.website"
|
||||||
|
:href="addon.website"
|
||||||
|
:title="addon.website"
|
||||||
|
class="tw-button tw-button--hollow tw-mg-r-1"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
<span class="tw-button__icon tw-button__icon--left">
|
||||||
|
<figure class="ffz-i-link-ext" />
|
||||||
|
</span>
|
||||||
|
<span class="tw-button__text">
|
||||||
|
{{ t('addon.website', 'Website') }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['id', 'addon', 'item'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
let description;
|
||||||
|
if ( this.addon.description_i18n )
|
||||||
|
description = this.t(this.addon.description_i18n, this.addon.description);
|
||||||
|
else
|
||||||
|
description = this.addon.description;
|
||||||
|
|
||||||
|
const lines = description.split(/\n/);
|
||||||
|
|
||||||
|
return {
|
||||||
|
description,
|
||||||
|
multi_line: lines.length > 1,
|
||||||
|
first_line: lines[0],
|
||||||
|
enabled: this.item.isAddonEnabled(this.id),
|
||||||
|
external: this.item.isAddonExternal(this.id),
|
||||||
|
version: this.item.getVersion(this.id),
|
||||||
|
expanded: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
icon() {
|
||||||
|
return this.addon.icon || 'https://cdn.frankerfacez.com/badge/2/4/solid'
|
||||||
|
},
|
||||||
|
|
||||||
|
show_description() {
|
||||||
|
if ( this.expanded )
|
||||||
|
return this.description;
|
||||||
|
return this.first_line;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.item.on('i18n:update', this.updateDescription, this);
|
||||||
|
this.item.on(':added', this.refreshExternal, this);
|
||||||
|
this.item.on(':ready', this.refreshExternal, this);
|
||||||
|
this.item.on(':addon-enabled', this.onEnabled, this);
|
||||||
|
this.item.on(':addon-disabled', this.onDisabled, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
destroyed() {
|
||||||
|
this.item.off('i18n:update', this.updateDescription, this);
|
||||||
|
this.item.off(':added', this.refreshExternal, this);
|
||||||
|
this.item.off(':ready', this.refreshExternal, this);
|
||||||
|
this.item.off(':addon-enabled', this.onEnabled, this);
|
||||||
|
this.item.off(':addon-disabled', this.onDisabled, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
refreshExternal() {
|
||||||
|
this.external = this.item.isAddonExternal(this.id);
|
||||||
|
this.version = this.item.getVersion(this.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDescription() {
|
||||||
|
if ( this.addon.description_i18n )
|
||||||
|
this.description = this.t(this.addon.description_i18n, this.addon.description);
|
||||||
|
else
|
||||||
|
this.description = this.addon.description;
|
||||||
|
|
||||||
|
const lines = this.description.split(/\n/);
|
||||||
|
|
||||||
|
this.multi_line = lines.length > 1;
|
||||||
|
this.first_line = lines[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
onEnabled(id) {
|
||||||
|
if ( id === this.id )
|
||||||
|
this.enabled = true;
|
||||||
|
|
||||||
|
this.version = this.item.getVersion(this.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
onDisabled(id) {
|
||||||
|
if ( id === this.id )
|
||||||
|
this.enabled = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.expanded = ! this.expanded
|
||||||
|
},
|
||||||
|
|
||||||
|
openSettings() {
|
||||||
|
if ( typeof this.addon.settings === 'string' ) {
|
||||||
|
this.$emit('navigate', this.addon.settings);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = [`add_ons.${this.id}`];
|
||||||
|
if ( this.addon.short_name )
|
||||||
|
list.push(`add_ons.${this.addon.short_name.toSnakeCase()}`);
|
||||||
|
if ( this.addon.name )
|
||||||
|
list.push(`add_ons.${this.addon.name.toSnakeCase()}`);
|
||||||
|
|
||||||
|
this.$emit('navigate', ...list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -227,8 +227,14 @@ export default {
|
||||||
this.displace.reinit();
|
this.displace.reinit();
|
||||||
},
|
},
|
||||||
|
|
||||||
navigate(key) {
|
navigate(...keys) {
|
||||||
let item = this.nav_keys[key];
|
let item;
|
||||||
|
for(const key of keys) {
|
||||||
|
item = this.nav_keys[key];
|
||||||
|
if ( item )
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
while(item && item.page)
|
while(item && item.page)
|
||||||
item = item.parent;
|
item = item.parent;
|
||||||
|
|
||||||
|
|
|
@ -141,7 +141,8 @@ export default class RavenLogger extends Module {
|
||||||
'Zugriff verweigert',
|
'Zugriff verweigert',
|
||||||
'freed script',
|
'freed script',
|
||||||
'ffzenhancing',
|
'ffzenhancing',
|
||||||
'dead object'
|
'dead object',
|
||||||
|
'Name Collision for Module'
|
||||||
],
|
],
|
||||||
sanitizeKeys: [
|
sanitizeKeys: [
|
||||||
/Token$/
|
/Token$/
|
||||||
|
|
|
@ -64,6 +64,10 @@ export default class ViewerCards extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStyle(login) {
|
updateStyle(login) {
|
||||||
|
// Make sure we're dealing with lower-case logins.
|
||||||
|
if ( typeof login === 'string' )
|
||||||
|
login = login.toLowerCase();
|
||||||
|
|
||||||
this.last_login = login;
|
this.last_login = login;
|
||||||
if ( login && this.chat.context.get('chat.viewer-cards.highlight-chat') ) {
|
if ( login && this.chat.context.get('chat.viewer-cards.highlight-chat') ) {
|
||||||
let color = this.chat.context.get('chat.viewer-cards.color');
|
let color = this.chat.context.get('chat.viewer-cards.color');
|
||||||
|
|
|
@ -9,12 +9,37 @@ export class Addon extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
static register(id, info) {
|
static register(id, info) {
|
||||||
const ffz = FrankerFaceZ.get();
|
if ( typeof id === 'object' ) {
|
||||||
ffz.register(`addon.${id}`, this);
|
info = id;
|
||||||
|
id = info.id || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! id ) {
|
||||||
|
if ( this.name )
|
||||||
|
id = this.name.toSnakeCase();
|
||||||
|
else
|
||||||
|
throw new Error(`Unable to register module without ID.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! info && this.info )
|
||||||
|
info = this.info;
|
||||||
|
|
||||||
|
const ffz = FrankerFaceZ.get();
|
||||||
if ( info ) {
|
if ( info ) {
|
||||||
info.id = id;
|
info.id = id;
|
||||||
ffz.addons.addAddon(info);
|
ffz.addons.addAddon(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ffz.register(`addon.${id}`, this);
|
||||||
|
} catch(err) {
|
||||||
|
if ( err.message && err.message.includes('Name Collision for Module') ) {
|
||||||
|
const module = ffz.resolve(`addon.${id}`);
|
||||||
|
if ( module )
|
||||||
|
module.external = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue