mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.20.91
* Added: Notice when FFZ fails to save settings because `localStorage` is full. * Added: Setting to move "Chat Identity" from the chat input box to the chat settings menu. (Closes #1025) * Fixed: Actually hide links when "Hide matching links for rich content." is enabled. (Closes #1019) * Fixed: Open the control center when clicking a markdown settings link without the control center open. * API Added: Toasts displayed by the `site.menu_button` module now support markdown.
This commit is contained in:
parent
a80728a10d
commit
66702103ff
24 changed files with 303 additions and 190 deletions
|
@ -773,6 +773,12 @@
|
|||
"search": [
|
||||
"threads"
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "399ef63b1e23ab1b761dfbb5591fa4da",
|
||||
"css": "right-open",
|
||||
"code": 59462,
|
||||
"src": "fontawesome"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.20.90",
|
||||
"version": "4.20.91",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
|
|
Binary file not shown.
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Copyright (C) 2020 by original authors @ fontello.com</metadata>
|
||||
<metadata>Copyright (C) 2021 by original authors @ fontello.com</metadata>
|
||||
<defs>
|
||||
<font id="ffz-fontello" horiz-adv-x="1000" >
|
||||
<font-face font-family="ffz-fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
||||
|
@ -146,6 +146,8 @@
|
|||
|
||||
<glyph glyph-name="volume-off" unicode="" d="M0 551l305 0 345 282 0-965-345 279-229 0z m713-283q0 4 2 6l76 76-76 76q-2 2-2 6t2 6l53 53q2 2 5 2t6-2l76-76 77 76q4 2 6 2t5-2l55-53q2-2 2-6t-2-6l-76-76 76-76q2-4 2-7t-2-5l-55-54q-2-2-5-2t-6 2l-77 78-76-78q-2-2-5-2t-6 2l-53 54q-2 2-2 6z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="right-open" unicode="" d="M618 361l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
|
||||
|
||||
<glyph glyph-name="move" unicode="" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-215v-215h72q14 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-11 10-11 25t11 25 25 10h72v215h-215v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h215v215h-72q-14 0-25 10t-11 25 11 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26t-11-25-25-10h-72v-215h215v72q0 14 10 25t25 11 26-11l142-143q11-10 11-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" />
|
||||
|
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -558,7 +558,7 @@ export default class Badges extends Module {
|
|||
}
|
||||
|
||||
|
||||
render(msg, createElement, skip_hide = false) { // eslint-disable-line class-methods-use-this
|
||||
render(msg, createElement, skip_hide = false, skip_click = false) { // eslint-disable-line class-methods-use-this
|
||||
if ( ! msg.badges && ! msg.ffz_badges )
|
||||
return null;
|
||||
|
||||
|
@ -769,7 +769,8 @@ export default class Badges extends Module {
|
|||
props['data-tooltip-type'] = 'badge';
|
||||
props['data-badge-data'] = JSON.stringify(data.badges);
|
||||
|
||||
props.onClick = this.handleClick;
|
||||
if ( ! skip_click )
|
||||
props.onClick = this.handleClick;
|
||||
|
||||
if ( data.replaced )
|
||||
props['data-replaced'] = data.replaced;
|
||||
|
|
|
@ -206,7 +206,7 @@ export default class Chat extends Module {
|
|||
});
|
||||
|
||||
this.settings.add('chat.rich.hide-tokens', {
|
||||
default: true,
|
||||
default: false,
|
||||
ui: {
|
||||
path: 'Chat > Appearance >> Rich Content',
|
||||
title: 'Hide matching links for rich content.',
|
||||
|
@ -262,6 +262,13 @@ export default class Chat extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
this.settings.addUI('chat.filtering.pad-bottom', {
|
||||
path: 'Chat > Filtering > Highlight',
|
||||
sort: 1000,
|
||||
component: 'setting-spacer',
|
||||
top: '30rem'
|
||||
});
|
||||
|
||||
this.settings.add('chat.filtering.click-to-reveal', {
|
||||
default: false,
|
||||
ui: {
|
||||
|
@ -1694,7 +1701,7 @@ export default class Chat extends Module {
|
|||
for(const token of tokens) {
|
||||
for(const provider of providers)
|
||||
if ( provider.test.call(this, token, msg) ) {
|
||||
token.hidden = this.context.get('chat.rich.hide-tokens') && provider.hide_token;
|
||||
token.hidden = provider.can_hide_token && (this.context.get('chat.rich.hide-tokens') || provider.hide_token);
|
||||
return provider.process.call(this, token);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import {truncate} from 'utilities/object';
|
|||
|
||||
export const Links = {
|
||||
type: 'link',
|
||||
hide_token: false,
|
||||
can_hide_token: true,
|
||||
priority: -10,
|
||||
|
||||
test(token) {
|
||||
|
@ -77,7 +77,7 @@ export const Links = {
|
|||
|
||||
export const Users = {
|
||||
type: 'user',
|
||||
hide_token: false,
|
||||
can_hide_token: true,
|
||||
|
||||
test(token) {
|
||||
if ( token.type !== 'link' || (! this.context.get('chat.rich.all-links') && ! token.force_rich) )
|
||||
|
@ -190,7 +190,7 @@ export const Users = {
|
|||
|
||||
export const Clips = {
|
||||
type: 'clip',
|
||||
hide_token: false,
|
||||
can_hide_token: true,
|
||||
|
||||
test(token) {
|
||||
if ( token.type !== 'link' )
|
||||
|
@ -278,7 +278,7 @@ export const Clips = {
|
|||
|
||||
export const Videos = {
|
||||
type: 'video',
|
||||
hide_token: false,
|
||||
can_hide_token: true,
|
||||
|
||||
test(token) {
|
||||
return token.type === 'link' && VIDEO_URL.test(token.url)
|
||||
|
|
|
@ -298,6 +298,9 @@ export default class MainMenu extends Module {
|
|||
return;
|
||||
|
||||
this.requestPage(path);
|
||||
|
||||
if ( ! this.showing )
|
||||
this.emit('site.menu_button:clicked');
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -99,6 +99,9 @@ export default class SettingsManager extends Module {
|
|||
this.provider = provider;
|
||||
this.log.info(`Using Provider: ${provider.constructor.name}`);
|
||||
provider.on('changed', this._onProviderChange, this);
|
||||
provider.on('quota-exceeded', err => {
|
||||
this.emit(':quota-exceeded', err);
|
||||
});
|
||||
provider.on('change-provider', () => {
|
||||
this.emit(':change-provider');
|
||||
});
|
||||
|
|
|
@ -262,7 +262,18 @@ export class LocalStorageProvider extends SettingsProvider {
|
|||
}
|
||||
|
||||
this._cached.set(key, value);
|
||||
localStorage.setItem(this.prefix + key, JSON.stringify(value));
|
||||
try {
|
||||
localStorage.setItem(this.prefix + key, JSON.stringify(value));
|
||||
} catch(err) {
|
||||
if ( this.manager )
|
||||
this.manager.log.error(`An error occurred while trying to save a value to localStorage for key "${this.prefix + key}"`);
|
||||
|
||||
if ( /quota/i.test(err.toString()) )
|
||||
this.emit('quota-exceeded', err);
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
this.broadcast({type: 'set', key});
|
||||
this.emit('set', key, value, false);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,16 @@ export default class SettingsMenu extends Module {
|
|||
this.inject('chat.badges');
|
||||
this.inject('site.fine');
|
||||
this.inject('site.web_munch');
|
||||
this.inject('site.css_tweaks');
|
||||
|
||||
this.settings.add('chat.input.hide-identity', {
|
||||
default: false,
|
||||
ui: {
|
||||
path: 'Chat > Input >> Appearance',
|
||||
title: 'Display "Chat Identity" in the chat settings menu rather than the input box.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
});
|
||||
|
||||
this.SettingsMenu = this.fine.define(
|
||||
'chat-settings',
|
||||
|
@ -35,6 +45,12 @@ export default class SettingsMenu extends Module {
|
|||
async onEnable() {
|
||||
this.on('i18n:update', () => this.SettingsMenu.forceUpdate());
|
||||
this.chat.context.on('changed:chat.scroller.freeze', () => this.SettingsMenu.forceUpdate());
|
||||
this.chat.context.on('changed:chat.input.hide-identity', val => {
|
||||
this.css_tweaks.toggle('hide-chat-identity', val);
|
||||
this.SettingsMenu.forceUpdate();
|
||||
});
|
||||
|
||||
this.css_tweaks.toggle('hide-chat-identity', this.chat.context.get('chat.input.hide-identity'));
|
||||
|
||||
const t = this,
|
||||
React = await this.web_munch.findModule('react');
|
||||
|
@ -101,71 +117,87 @@ export default class SettingsMenu extends Module {
|
|||
return val;
|
||||
}
|
||||
|
||||
cls.prototype.render = function() {
|
||||
try {
|
||||
if ( this.state.ffzPauseMenu ) {
|
||||
if ( ! this.ffzSettingsClick )
|
||||
this.ffzSettingsClick = e => t.click(this, e);
|
||||
cls.prototype.ffzRenderIdentity = function() {
|
||||
if ( ! this.state || ! this.props || this.state.moderatorMode || this.state.chatAppearance || this.state.chatPause || this.state.followerMode || this.state.recentRaids || this.state.repliesAppearance || this.state.slowMode || this.props.isShowingChatFilterSettings || this.props.isShowingDeletedMessageDisplaySettings || ! this.props.isLoggedIn || ! this.props.onClickEditAppearance )
|
||||
return null;
|
||||
|
||||
if ( ! this.ffzPauseClick )
|
||||
this.ffzPauseClick = () => this.setState({ffzPauseMenu: ! this.state.ffzPauseMenu});
|
||||
if ( ! t.chat.context.get('chat.input.hide-identity') )
|
||||
return null;
|
||||
|
||||
return (<div class="tw-absolute ffz-balloon ffz-balloon--auto ffz-balloon--right ffz-balloon--up tw-block" data-a-target="chat-settings-balloon" style={{marginRight: '-5.3rem'}}>
|
||||
<div class="tw-border-radius-large tw-c-background-base tw-c-text-inherit tw-elevation-2">
|
||||
<div class="chat-settings__popover">
|
||||
<div class="chat-settings__header tw-align-items-center tw-c-background-base tw-flex tw-pd-x-1 tw-relative">
|
||||
<div class="chat-settings__back-icon-container tw-left-0 tw-mg-r-05">
|
||||
<button
|
||||
class="tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-button-icon ffz-core-button ffz-core-button--border tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative"
|
||||
data-test-selector="chat-settings-back-button"
|
||||
aria-label={t.i18n.t('chat.settings.back', 'Back')}
|
||||
onClick={this.ffzPauseClick}
|
||||
>
|
||||
<div class="tw-align-items-center tw-flex tw-flex-grow-0">
|
||||
<span class="tw-button-icon__icon">
|
||||
<figure class="ffz-i-left-open" />
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="tw-align-center tw-align-items-center tw-flex tw-flex-grow-1 tw-justify-content-center">
|
||||
<p class="tw-c-text-alt tw-font-size-5 tw-semibold">
|
||||
{ t.i18n.t('chat.settings.pause', 'Pause Chat') }
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-settings scrollable-area scrollable-area--suppress-scroll-x" data-simplebar>
|
||||
<div class="chat-settings__content tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-c-background-base tw-c-text-base tw-pd-1">
|
||||
<div class="tw-pd-x-05">
|
||||
<div class="tw-border-b tw-mg-b-1 tw-pd-b-1">
|
||||
<p class="tw-c-text-alt-2">
|
||||
{ t.i18n.t('chat.settings.pause-explain', 'FrankerFaceZ overrides the behavior of Pause Chat entirely. Please use FFZ\'s Scrolling settings within the FFZ Control Center under Chat > Behavior.') }
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="tw-block tw-border-radius-medium tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive"
|
||||
data-page="chat.behavior"
|
||||
onClick={this.ffzSettingsClick}
|
||||
>
|
||||
<div class="tw-align-items-center tw-flex tw-pd-05 tw-relative">
|
||||
<div class="tw-flex-grow-1">
|
||||
{t.i18n.t('chat.settings.open-settings', 'Open Control Center')}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
const user = this.props.data?.currentUser,
|
||||
raw_badges = this.props.data?.user?.self?.displayBadges;
|
||||
|
||||
if ( ! user || ! user.login || ! Array.isArray(raw_badges) )
|
||||
return null;
|
||||
|
||||
const is_intl = user.login && user.displayName && user.displayName.trim().toLowerCase() !== user.login,
|
||||
color = t.parent.colors.process(user.chatColor),
|
||||
badges = {};
|
||||
|
||||
for(const badge of raw_badges) {
|
||||
if ( badge?.setID && badge.version )
|
||||
badges[badge.setID] = badge.version;
|
||||
}
|
||||
|
||||
return (<div class="ffz-identity">
|
||||
<div class="tw-mg-y-05 tw-pd-x-05">
|
||||
<p class="tw-c-text-alt-2 tw-font-size-6 tw-strong tw-upcase">
|
||||
{ t.i18n.t('chat.identity-menu', 'Chat Identity') }
|
||||
</p>
|
||||
</div>
|
||||
<div class="tw-full-width tw-relative">
|
||||
<button
|
||||
class="tw-block tw-border-radius-medium tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive"
|
||||
onClick={this.props.onClickEditAppearance}
|
||||
>
|
||||
<div class="tw-align-items-center tw-flex tw-pd-05 tw-relative">
|
||||
<div class="tw-flex-grow-1">
|
||||
<span class="ffz--editor-name">
|
||||
<span
|
||||
class="ffz--editor-badges"
|
||||
data-room-id={this.props.channelID}
|
||||
data-room-login={this.props.channelLogin}
|
||||
>
|
||||
{t.badges.render({
|
||||
user,
|
||||
badges,
|
||||
ffz_badges: t.badges.getBadges(user.id, user.login, this.props.channelID, this.props.channelLogin),
|
||||
roomID: this.props.channelID,
|
||||
roomLogin: this.props.channelLogin
|
||||
}, createElement, true, true)}
|
||||
</span>
|
||||
|
||||
<span class="tw-strong notranslate" style={{color}}>
|
||||
<span class="name-display__name">{ user.displayName || user.login}</span>
|
||||
{is_intl && <span class="intl-name"> ({user.login}) </span>}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="tw-align-items-center tw-flex tw-flex-shrink-0 tw-mg-l-05">
|
||||
<figure class="ffz-i-right-open" />
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
</button>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
cls.prototype.render = function() {
|
||||
const out = old_render.call(this);
|
||||
|
||||
try {
|
||||
const children = out?.props?.children?.props?.children?.[1]?.props?.children?.props?.children;
|
||||
if ( Array.isArray(children) ) {
|
||||
const extra = this.ffzRenderIdentity();
|
||||
if ( extra )
|
||||
children.unshift(extra);
|
||||
}
|
||||
|
||||
} catch(err) {
|
||||
t.log.error('Error rendering chat settings menu.', err);
|
||||
}
|
||||
|
||||
return old_render.call(this);
|
||||
return out;
|
||||
}
|
||||
|
||||
this.SettingsMenu.forceUpdate();
|
||||
|
@ -228,9 +260,10 @@ export default class SettingsMenu extends Module {
|
|||
}, this.badges.render({
|
||||
user,
|
||||
badges,
|
||||
ffz_badges: this.badges.getBadges(user.id, user.login, inst.props.channelID, inst.props.channelLogin),
|
||||
roomID: inst.props.channelID,
|
||||
roomLogin: inst.props.channelLogin
|
||||
}, createElement, true)),
|
||||
}, createElement, true, true)),
|
||||
|
||||
<span class="tw-strong notranslate" style={{color}}>
|
||||
<span class="name-display__name">{user.displayName || user.login}</span>
|
||||
|
@ -292,17 +325,25 @@ export default class SettingsMenu extends Module {
|
|||
} else if ( ! badge )
|
||||
return;
|
||||
|
||||
cont.appendChild(<div class="ffz--badge-selector">
|
||||
<p class="tw-pd-x-05">
|
||||
{this.i18n.tList('chat.ffz-badge.about', '{title}: This badge appears globally for users with FrankerFaceZ.', {
|
||||
title: <span class="tw-strong">{this.i18n.t('chat.ffz-badge.title', 'FrankerFaceZ Badge')}</span>
|
||||
})}{' '}
|
||||
{this.i18n.tList('chat.ffz-badge.site', 'Please visit the {website} to change this badge.', {
|
||||
website: (<a href="https://www.frankerfacez.com/donate" class="ffz-link" rel="noopener noreferrer" target="_blank">
|
||||
{this.i18n.t('chat.ffz-badge.site-link', 'FrankerFaceZ website')}
|
||||
</a>)
|
||||
})}
|
||||
</p>
|
||||
const out = (<div class="ffz--badge-selector tw-border-b tw-mg-b-1">
|
||||
<div class="tw-mg-y-05 tw-pd-x-05">
|
||||
<p class="tw-c-text-alt-2 tw-font-size-6 tw-strong tw-upcase">
|
||||
{ this.i18n.t('chat.ffz-badge.title', 'FrankerFaceZ Badge') }
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="tw-mg-b-05 tw-pd-x-05">
|
||||
{ this.i18n.tList(
|
||||
'chat.ffz-badge.about',
|
||||
'This badge appears globally for users with FrankerFaceZ. Please visit the {website} to change this badge.',
|
||||
{
|
||||
website: (<a href="https://www.frankerfacez.com/donate" class="ffz-link" rel="noopener noreferrer" target="_blank">
|
||||
{this.i18n.t('chat.ffz-badge.site-link', 'FrankerFaceZ website')}
|
||||
</a>)
|
||||
}
|
||||
) }
|
||||
</p>
|
||||
</div>
|
||||
<div role="radiogroup" class="tw-align-items-center tw-flex tw-flex-wrap tw-mg-b-05 tw-mg-t-05 tw-pd-x-05">
|
||||
<div class="tw-mg-r-1 tw-mg-y-05">
|
||||
<div class="tw-inline-flex">
|
||||
|
@ -321,6 +362,12 @@ export default class SettingsMenu extends Module {
|
|||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
|
||||
const after = cont.querySelector('[data-test-selector="global-badges-test-selector"]')?.nextElementSibling;
|
||||
if ( after )
|
||||
cont.insertBefore(out, after);
|
||||
else
|
||||
cont.appendChild(out);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
.chat-input__badge-carousel {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.chat-input__textarea .tw-textarea {
|
||||
padding-left: 1rem !important;
|
||||
}
|
|
@ -9,6 +9,7 @@ import {SiteModule} from 'utilities/module';
|
|||
import {createElement, ClickOutside, setChildren} from 'utilities/dom';
|
||||
|
||||
import Twilight from 'site';
|
||||
import getMD from 'src/utilities/markdown';
|
||||
|
||||
|
||||
export default class MenuButton extends SiteModule {
|
||||
|
@ -257,6 +258,17 @@ export default class MenuButton extends SiteModule {
|
|||
this.on('i18n:changed-strings', this.update);
|
||||
this.on('i18n:update', this.update);
|
||||
this.on('addons:data-loaded', this.update);
|
||||
|
||||
this.once('settings:quota-exceeded', () => {
|
||||
this.addError(
|
||||
'site.menu_button.quota-exceeded',
|
||||
'Your local storage space for this website is full, and settings cannot be saved. Please backup your settings and switch to a higher capacity provider in [Data Management > Storage >> Provider](~data_management.storage.tabs.provider).',
|
||||
'ffz-i-attention',
|
||||
true
|
||||
);
|
||||
this.update();
|
||||
});
|
||||
|
||||
this.on('settings:change-provider', () => {
|
||||
this.addError('site.menu_button.changed',
|
||||
'The FrankerFaceZ settings provider has changed. Please refresh this tab to avoid strange behavior.'
|
||||
|
@ -266,11 +278,12 @@ export default class MenuButton extends SiteModule {
|
|||
}
|
||||
|
||||
|
||||
addError(i18n, text, icon = 'ffz-i-attention') {
|
||||
addError(i18n, text, icon = 'ffz-i-attention', use_markdown = false) {
|
||||
this.addToast({
|
||||
icon,
|
||||
text_i18n: i18n,
|
||||
text
|
||||
text,
|
||||
markdown: use_markdown
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -425,7 +438,8 @@ export default class MenuButton extends SiteModule {
|
|||
{ data.title_i18n ? this.i18n.tList(data.title_i18n, data.title, data) : data.title}
|
||||
</header>) : null }
|
||||
{ data.text ? (<span class={`${data.lines ? 'ffz--line-clamp' : ''}`} style={{'--ffz-lines': data.lines}}>
|
||||
{ data.text_i18n ? this.i18n.tList(data.text_i18n, data.text, data) : data.text}
|
||||
{ data.markdown ? <span dangerouslySetInnerHTML={{__html: getMD().render(data.text_i18n ? this.i18n.t(data.text_i18n, data.text, data) : data.text) }} /> : null}
|
||||
{ data.markdown ? null : (data.text_i18n ? this.i18n.tList(data.text_i18n, data.text, data) : data.text)}
|
||||
</span>) : null }
|
||||
</div>) : null}
|
||||
{ ! data.unclosable && (<button
|
||||
|
|
|
@ -5,101 +5,7 @@
|
|||
|
||||
<script>
|
||||
|
||||
import MD from 'markdown-it';
|
||||
import MILA from 'markdown-it-link-attributes';
|
||||
import {parse as parse_path} from 'utilities/path-parser';
|
||||
|
||||
let _md;
|
||||
|
||||
function getMD() {
|
||||
if ( ! _md ) {
|
||||
const md = _md = new MD({
|
||||
html: false,
|
||||
linkify: true
|
||||
});
|
||||
|
||||
md.use(SettingsLinks);
|
||||
md.use(MILA, {
|
||||
attrs: {
|
||||
class: 'ffz-tooltip',
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
'data-tooltip-type': 'link'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return _md;
|
||||
}
|
||||
|
||||
|
||||
function SettingsLinks(md) {
|
||||
const default_render = md.renderer.rules.link_open || this.defaultRender;
|
||||
|
||||
md.renderer.rules.link_open = function(tokens, idx, options, env, self) {
|
||||
const token = tokens[idx];
|
||||
if ( token && token.type === 'link_open' && Array.isArray(token.attrs) ) {
|
||||
let href;
|
||||
for(const attr of token.attrs) {
|
||||
if ( attr[0] === 'href' ) {
|
||||
href = attr[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( href.startsWith('~') ) {
|
||||
let path;
|
||||
|
||||
if ( href === '~' ) {
|
||||
// We don't have a path, make one from the bits.
|
||||
let i = idx + 1;
|
||||
let bits = [];
|
||||
|
||||
while(i < tokens.length) {
|
||||
const tok = tokens[i],
|
||||
type = tok?.type;
|
||||
if ( type === 'text' )
|
||||
bits.push(tok);
|
||||
else if ( type === 'link_close' )
|
||||
break;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
bits = bits.map(x => x.content).join('');
|
||||
const toks = parse_path(bits);
|
||||
path = toks.map(x => x.key).join('.');
|
||||
} else
|
||||
path = href.slice(1);
|
||||
|
||||
if ( path && path.length ) {
|
||||
for(const attr of token.attrs) {
|
||||
if ( attr[0] === 'class' ) {
|
||||
attr[1] = attr[1].replace(/ffz-tooltip/g, '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
token.attrs.push([
|
||||
'data-settings-link',
|
||||
path
|
||||
]);
|
||||
token.attrs.push([
|
||||
'onclick',
|
||||
'FrankerFaceZ.get().resolve("main_menu").mdNavigate(this);return false'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return default_render(tokens, idx, options, env, self);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsLinks.defaultRender = function(tokens, idx, options, env, self) {
|
||||
return self.renderToken(tokens, idx, options);
|
||||
}
|
||||
|
||||
import getMD from 'utilities/markdown';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
|
@ -102,5 +102,6 @@ export default [
|
|||
"link",
|
||||
"volume-off",
|
||||
"reply",
|
||||
"threads"
|
||||
"threads",
|
||||
"right-open"
|
||||
];
|
96
src/utilities/markdown.js
Normal file
96
src/utilities/markdown.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
'use strict';
|
||||
|
||||
import MD from 'markdown-it';
|
||||
import MILA from 'markdown-it-link-attributes';
|
||||
import {parse as parse_path} from 'utilities/path-parser';
|
||||
|
||||
let _md;
|
||||
|
||||
function SettingsLinks(md) {
|
||||
const default_render = md.renderer.rules.link_open || this.defaultRender;
|
||||
|
||||
md.renderer.rules.link_open = function(tokens, idx, options, env, self) {
|
||||
const token = tokens[idx];
|
||||
if ( token && token.type === 'link_open' && Array.isArray(token.attrs) ) {
|
||||
let href;
|
||||
for(const attr of token.attrs) {
|
||||
if ( attr[0] === 'href' ) {
|
||||
href = attr[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( href.startsWith('~') ) {
|
||||
let path;
|
||||
|
||||
if ( href === '~' ) {
|
||||
// We don't have a path, make one from the bits.
|
||||
let i = idx + 1;
|
||||
let bits = [];
|
||||
|
||||
while(i < tokens.length) {
|
||||
const tok = tokens[i],
|
||||
type = tok?.type;
|
||||
if ( type === 'text' )
|
||||
bits.push(tok);
|
||||
else if ( type === 'link_close' )
|
||||
break;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
bits = bits.map(x => x.content).join('');
|
||||
const toks = parse_path(bits);
|
||||
path = toks.map(x => x.key).join('.');
|
||||
} else
|
||||
path = href.slice(1);
|
||||
|
||||
if ( path && path.length ) {
|
||||
for(const attr of token.attrs) {
|
||||
if ( attr[0] === 'class' ) {
|
||||
attr[1] = attr[1].replace(/ffz-tooltip/g, '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
token.attrs.push([
|
||||
'data-settings-link',
|
||||
path
|
||||
]);
|
||||
token.attrs.push([
|
||||
'onclick',
|
||||
'FrankerFaceZ.get().resolve("main_menu").mdNavigate(this);return false'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return default_render(tokens, idx, options, env, self);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsLinks.defaultRender = function(tokens, idx, options, env, self) {
|
||||
return self.renderToken(tokens, idx, options);
|
||||
}
|
||||
|
||||
export default function getMD() {
|
||||
if ( ! _md ) {
|
||||
const md = _md = new MD({
|
||||
html: false,
|
||||
linkify: true
|
||||
});
|
||||
|
||||
md.use(SettingsLinks);
|
||||
md.use(MILA, {
|
||||
attrs: {
|
||||
class: 'ffz-tooltip',
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
'data-tooltip-type': 'link'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return _md;
|
||||
}
|
||||
|
|
@ -69,6 +69,7 @@
|
|||
.ffz-i-link:before { content: '\e843'; } /* '' */
|
||||
.ffz-i-threads:before { content: '\e844'; } /* '' */
|
||||
.ffz-i-volume-off:before { content: '\e845'; } /* '' */
|
||||
.ffz-i-right-open:before { content: '\e846'; } /* '' */
|
||||
.ffz-i-move:before { content: '\f047'; } /* '' */
|
||||
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
|
||||
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -69,6 +69,7 @@
|
|||
.ffz-i-link { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-threads { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-volume-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
.ffz-i-link { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-threads { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-volume-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
@font-face {
|
||||
font-family: 'ffz-fontello';
|
||||
src: url('../font/ffz-fontello.eot?89863637');
|
||||
src: url('../font/ffz-fontello.eot?89863637#iefix') format('embedded-opentype'),
|
||||
url('../font/ffz-fontello.woff2?89863637') format('woff2'),
|
||||
url('../font/ffz-fontello.woff?89863637') format('woff'),
|
||||
url('../font/ffz-fontello.ttf?89863637') format('truetype'),
|
||||
url('../font/ffz-fontello.svg?89863637#ffz-fontello') format('svg');
|
||||
src: url('../font/ffz-fontello.eot?52570921');
|
||||
src: url('../font/ffz-fontello.eot?52570921#iefix') format('embedded-opentype'),
|
||||
url('../font/ffz-fontello.woff2?52570921') format('woff2'),
|
||||
url('../font/ffz-fontello.woff?52570921') format('woff'),
|
||||
url('../font/ffz-fontello.ttf?52570921') format('truetype'),
|
||||
url('../font/ffz-fontello.svg?52570921#ffz-fontello') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
|||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||
@font-face {
|
||||
font-family: 'ffz-fontello';
|
||||
src: url('../font/ffz-fontello.svg?89863637#ffz-fontello') format('svg');
|
||||
src: url('../font/ffz-fontello.svg?52570921#ffz-fontello') format('svg');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -125,6 +125,7 @@
|
|||
.ffz-i-link:before { content: '\e843'; } /* '' */
|
||||
.ffz-i-threads:before { content: '\e844'; } /* '' */
|
||||
.ffz-i-volume-off:before { content: '\e845'; } /* '' */
|
||||
.ffz-i-right-open:before { content: '\e846'; } /* '' */
|
||||
.ffz-i-move:before { content: '\f047'; } /* '' */
|
||||
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
|
||||
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue