diff --git a/src/i18n.js b/src/i18n.js
index c9681ae4..f9a6452c 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -7,7 +7,7 @@
import Parser from '@ffz/icu-msgparser';
import {SERVER} from 'utilities/constants';
-import {get, pick_random, has, timeout} from 'utilities/object';
+import {get, pick_random, timeout} from 'utilities/object';
import Module from 'utilities/module';
import NewTransCore from 'utilities/translation-core';
@@ -45,7 +45,7 @@ const FACES = ['(・`ω´・)', ';;w;;', 'owo', 'ono', 'oAo', 'oxo', 'ovo;', 'Uw
upper: (key, ast) => transformText(ast, n => n.toUpperCase()),
lower: (key, ast) => transformText(ast, n => n.toLowerCase()),
append_key: (key, ast) => [...ast, ` (${key})`],
- set_key: (key, ast) => [key],
+ set_key: key => [key],
owo: (key, ast) => transformText(ast, owo)
};
diff --git a/src/main.js b/src/main.js
index 68c1ef4c..8944dba7 100644
--- a/src/main.js
+++ b/src/main.js
@@ -149,7 +149,7 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`
FrankerFaceZ.Logger = Logger;
const VER = FrankerFaceZ.version_info = {
- major: 4, minor: 0, revision: 0, extra: '-rc21.6',
+ major: 4, minor: 0, revision: 0, extra: '-rc21.7',
commit: __git_commit__,
build: __webpack_hash__,
toString: () =>
diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js
index 3686bd8e..910a5fc1 100644
--- a/src/modules/chat/index.js
+++ b/src/modules/chat/index.js
@@ -45,6 +45,7 @@ export default class Chat extends Module {
// Bind for JSX stuff
this.clickToReveal = this.clickToReveal.bind(this);
+ this.handleMentionClick = this.handleMentionClick.bind(this);
this.style = new ManagedStyle;
@@ -582,6 +583,16 @@ export default class Chat extends Module {
});
+ this.settings.add('chat.filtering.clickable-mentions', {
+ default: false,
+ ui: {
+ component: 'setting-check-box',
+ path: 'Chat > Filtering >> Appearance',
+ title: 'Enable opening viewer cards by clicking mentions in chat.'
+ }
+ });
+
+
this.settings.add('chat.filtering.highlight-mentions', {
default: false,
ui: {
@@ -950,6 +961,32 @@ export default class Chat extends Module {
}
+ handleMentionClick(event) {
+ if ( ! this.context.get('chat.filtering.clickable-mentions') )
+ return;
+
+ const target = event.target,
+ ds = target && target.dataset;
+
+ if ( ! ds || ! ds.login )
+ return;
+
+ const fine = this.resolve('site.fine');
+ if ( ! fine )
+ return;
+
+ const chat = fine.searchParent(event.target, n => n.props && n.props.onUsernameClick);
+ if ( ! chat )
+ return;
+
+ chat.props.onUsernameClick(
+ ds.login,
+ undefined, undefined,
+ event.currentTarget.getBoundingClientRect().bottom
+ );
+ }
+
+
clickToReveal(event) {
const target = event.target;
if ( target ) {
diff --git a/src/modules/chat/tokenizers.jsx b/src/modules/chat/tokenizers.jsx
index 0dcc510e..9da39fcf 100644
--- a/src/modules/chat/tokenizers.jsx
+++ b/src/modules/chat/tokenizers.jsx
@@ -12,7 +12,8 @@ import {TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/const
const EMOTE_CLASS = 'chat-image chat-line__message--emote',
LINK_REGEX = /([^\w@#%\-+=:~])?((?:(https?:\/\/)?(?:[\w@#%\-+=:~]+\.)+[a-z]{2,6}(?:\/[\w./@#%&()\-+=:?~]*)?))([^\w./@#%&()\-+=:?~]|\s|$)/g,
- MENTION_REGEX = /([^\w@#%\-+=:~])?(@([^\u0000-\u007F]+|\w+)+)([^\w./@#%&()\-+=:?~]|\s|$)/g; // eslint-disable-line no-control-regex
+ //MENTION_REGEX = /([^\w@#%\-+=:~])?(@([^\u0000-\u007F]+|\w+)+)([^\w./@#%&()\-+=:?~]|\s|$)/g; // eslint-disable-line no-control-regex
+ MENTION_REGEX = /^(['"*([{<\\/]*)(@?)((?:[^\u0000-\u007F]|[\w-])+)/; // eslint-disable-line no-control-regex
// ============================================================================
@@ -201,16 +202,29 @@ export const Mentions = {
component: () => import(/* webpackChunkName: 'vue-chat' */ './components/chat-mention.vue'),
- render(token, createElement) {
+ oldRender(token, createElement) {
return (
{token.text}
);
},
+ render(token, createElement) {
+ return (
+ {token.text}
+ )
+ },
+
process(tokens, msg, user) {
if ( ! tokens || ! tokens.length )
return tokens;
+ if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
+ return tokens;
+
let regex, login, display;
if ( user && user.login ) {
login = user.login.toLowerCase();
@@ -218,7 +232,8 @@ export const Mentions = {
if ( display === login )
display = null;
- regex = new RegExp(`([^\\w@#%\\-+=:~]|\\b)?(@?(${user.login.toLowerCase()}${display ? `|${display}` : ''})|@([^\\u0000-\\u007F]+|\\w+)+)([^\\w.\\/@#%&()\\-+=:?~]|\\s|\\b|$)`, 'gi');
+ regex = new RegExp(`^(['"*([{<\\/]*)(?:(@?)(${user.login.toLowerCase()}${display ? `|${display}` : ''})|@((?:[^\u0000-\u007F]|[\\w-])+))`, 'i');
+ //regex = new RegExp(`([^\\w@#%\\-+=:~]|\\b)?(@?(${user.login.toLowerCase()}${display ? `|${display}` : ''})|@([^\\u0000-\\u007F]+|\\w+)+)([^\\w.\\/@#%&()\\-+=:?~]|\\s|\\b|$)`, 'gi');
} else
regex = MENTION_REGEX;
@@ -229,34 +244,52 @@ export const Mentions = {
continue;
}
- regex.lastIndex = 0;
- const text = token.text;
- let idx = 0, match;
+ let text = [];
- while((match = regex.exec(text))) {
- const nix = match.index + (match[1] ? match[1].length : 0),
- m = match[3] || match[4],
- ml = m.toLowerCase(),
- me = ml === login || ml === display;
+ for(const segment of token.text.split(/ +/)) {
+ const match = regex.exec(segment);
+ if ( match ) {
+ // If we have pending text, join it together.
+ if ( text.length || match[1]) {
+ out.push({
+ type: 'text',
+ text: `${text.join(' ')} ${match[1] || ''}`
+ });
+ text = [];
+ }
- if ( idx !== nix )
- out.push({type: 'text', text: text.slice(idx, nix)});
+ let recipient,
+ mentioned = false,
+ at = match[2];
- if ( me )
- msg.mentioned = true;
+ if ( match[4] ) {
+ recipient = match[4];
+ at = '@';
- out.push({
- type: 'mention',
- text: match[2],
- me,
- recipient: m
- });
+ } else {
+ recipient = match[3];
+ mentioned = true;
+ }
- idx = nix + match[2].length;
+ out.push({
+ type: 'mention',
+ text: `${at}${recipient}`,
+ me: mentioned,
+ recipient
+ });
+
+ if ( mentioned )
+ msg.mentioned = true;
+
+ // Push the remaining text from the token.
+ text.push(segment.substr(match[0].length));
+
+ } else
+ text.push(segment);
}
- if ( idx < text.length )
- out.push({type: 'text', text: text.slice(idx)});
+ if ( text.length > 1 || (text.length === 1 && text[0] !== '') )
+ out.push({type: 'text', text: text.join(' ')})
}
return out;
diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js
index 99c69e8e..92a52173 100644
--- a/src/sites/twitch-twilight/modules/chat/index.js
+++ b/src/sites/twitch-twilight/modules/chat/index.js
@@ -452,6 +452,7 @@ export default class ChatHook extends Module {
this.chat.context.on('changed:chat.fix-bad-emotes', this.updateChatLines, this);
this.chat.context.on('changed:chat.filtering.display-deleted', this.updateChatLines, this);
this.chat.context.on('changed:chat.filtering.display-mod-action', this.updateChatLines, this);
+ this.chat.context.on('changed:chat.filtering.clickable-mentions', val => this.css_tweaks.toggle('clickable-mentions', val));
this.chat.context.on('changed:chat.lines.alternate', val => {
this.css_tweaks.toggle('chat-rows', val);
@@ -475,6 +476,8 @@ export default class ChatHook extends Module {
this.css_tweaks.toggle('chat-deleted-strike', val === 1 || val === 2);
this.css_tweaks.toggle('chat-deleted-fade', val < 2);
+ this.css_tweaks.toggle('clickable-mentions', this.chat.context.get('chat.filtering.clickable-mentions'));
+
this.css_tweaks.toggleHide('pinned-cheer', !this.chat.context.get('chat.bits.show-pinned'));
this.css_tweaks.toggle('hide-bits', !this.chat.context.get('chat.bits.show'));
this.css_tweaks.toggle('chat-rows', this.chat.context.get('chat.lines.alternate'));
diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-token.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-token.scss
index ab41a174..619e6dcd 100644
--- a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-token.scss
+++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-token.scss
@@ -1,4 +1,5 @@
.ffz--highlight,
+.mention-fragment--recipient,
.ffz--mention-me {
border-radius: .5rem;
padding: .3rem;