Implemented #170

master
WolverinDEV 2021-02-15 19:03:22 +01:00
parent eb1884c307
commit 281748ac7e
7 changed files with 87 additions and 41 deletions

View File

@ -1,4 +1,11 @@
# Changelog:
* **15.02.21**
- Fixed critical bug within the event registry class
- Added a dropdown for the microphone control button to quickly change microphones
- Fixed the microphone settings microphone selection (The default device wasn't selected)
- Adding a hint whatever the device is the default device or not
- Fixed issue [#169](https://github.com/TeaSpeak/TeaWeb/issues/169) (Adding permissions dosn't work for TS3 server)
- Fixed issue [#166](https://github.com/TeaSpeak/TeaWeb/issues/166) (Private conversations are not accessible when IndexDB could not be opened)
* **22.01.21**
- Allowing the user to easily change the channel name mode
- Fixed channel name mode parsing

View File

@ -495,6 +495,12 @@ export class Settings {
valueType: "string",
};
static readonly KEY_CHAT_LAST_USED_EMOJI: ValuedRegistryKey<string> = {
key: "chat_last_used_emoji",
defaultValue: ":joy:",
valueType: "string",
};
static readonly KEY_SWITCH_INSTANT_CHAT: ValuedRegistryKey<boolean> = {
key: "switch_instant_chat",
defaultValue: true,

View File

@ -0,0 +1,28 @@
function toCodePoint(unicodeSurrogates) {
let r = [],
c = 0,
p = 0,
i = 0;
while (i < unicodeSurrogates.length) {
c = unicodeSurrogates.charCodeAt(i++);
if (p) {
r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
p = 0;
} else if (0xD800 <= c && c <= 0xDBFF) {
p = c;
} else {
r.push(c.toString(16));
}
}
return r.join("-");
}
const U200D = String.fromCharCode(0x200D);
const UFE0Fg = /\uFE0F/g;
export function getTwenmojiHashFromNativeEmoji(emoji: string) : string {
// if variant is present as \uFE0F
return toCodePoint(emoji.indexOf(U200D) < 0 ?
emoji.replace(UFE0Fg, '') :
emoji
);
}

View File

@ -7,6 +7,7 @@ import ReactRenderer from "vendor/xbbcode/renderer/react";
import {Settings, settings} from "tc-shared/settings";
import * as emojiRegex from "emoji-regex";
import {getTwenmojiHashFromNativeEmoji} from "tc-shared/text/bbcode/EmojiUtil";
const emojiRegexInstance = (emojiRegex as any)() as RegExp;
@ -15,39 +16,11 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
function: async () => {
let reactId = 0;
function toCodePoint(unicodeSurrogates) {
let r = [],
c = 0,
p = 0,
i = 0;
while (i < unicodeSurrogates.length) {
c = unicodeSurrogates.charCodeAt(i++);
if (p) {
r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
p = 0;
} else if (0xD800 <= c && c <= 0xDBFF) {
p = c;
} else {
r.push(c.toString(16));
}
}
return r.join("-");
}
const U200D = String.fromCharCode(0x200D);
const UFE0Fg = /\uFE0F/g;
function grabTheRightIcon(rawText) {
// if variant is present as \uFE0F
return toCodePoint(rawText.indexOf(U200D) < 0 ?
rawText.replace(UFE0Fg, '') :
rawText
);
}
rendererReact.setTextRenderer(new class extends ElementRenderer<TextElement, React.ReactNode> {
render(element: TextElement, renderer: ReactRenderer): React.ReactNode {
if(!settings.getValue(Settings.KEY_CHAT_COLORED_EMOJIES))
if(!settings.getValue(Settings.KEY_CHAT_COLORED_EMOJIES)) {
return element.text();
}
let text = element.text();
emojiRegexInstance.lastIndex = 0;
@ -59,13 +32,15 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
let match = emojiRegexInstance.exec(text);
const rawText = text.substring(lastIndex, match?.index);
if(rawText)
if(rawText) {
result.push(renderer.renderAsText(rawText, false));
}
if(!match)
if(!match) {
break;
}
let hash = grabTheRightIcon(match[0]);
let hash = getTwenmojiHashFromNativeEmoji(match[0]);
result.push(<img key={"er-" + ++reactId} draggable={false} src={"https://twemoji.maxcdn.com/v/12.1.2/72x72/" + hash + ".png"} alt={match[0]} className={"chat-emoji"} />);
lastIndex = match.index + match[0].length;
}

View File

@ -75,6 +75,13 @@ html:root {
> img {
height: 100%;
width: 100%;
align-self: center;
&.emoji {
width: 1.25em;
height: 1.25em;
}
}
}

View File

@ -3,9 +3,12 @@ import {useEffect, useRef, useState} from "react";
import {Registry} from "tc-shared/events";
import '!style-loader!css-loader!emoji-mart/css/emoji-mart.css'
import {Picker} from 'emoji-mart'
import {Picker, emojiIndex} from 'emoji-mart'
import {settings, Settings} from "tc-shared/settings";
import {Translatable} from "tc-shared/ui/react-elements/i18n";
import {getTwenmojiHashFromNativeEmoji} from "tc-shared/text/bbcode/EmojiUtil";
import {BaseEmoji} from "emoji-mart";
import {useGlobalSetting} from "tc-shared/ui/react-elements/Helper";
const cssStyle = require("./ChatBox.scss");
@ -24,6 +27,18 @@ interface ChatBoxEvents {
notify_typing: {}
}
const LastUsedEmoji = () => {
const settingValue = useGlobalSetting(Settings.KEY_CHAT_LAST_USED_EMOJI);
const lastEmoji: BaseEmoji = (emojiIndex.emojis[settingValue] || emojiIndex.emojis["joy"]) as any;
if(!lastEmoji?.native) {
return <img key={"fallback"} alt={""} src={"img/smiley-smile.svg"} />;
}
return (
<img draggable={false} src={"https://twemoji.maxcdn.com/v/12.1.2/72x72/" + getTwenmojiHashFromNativeEmoji(lastEmoji.native) + ".png"} alt={lastEmoji.native} className={cssStyle.emoji} />
)
}
const EmojiButton = (props: { events: Registry<ChatBoxEvents> }) => {
const [ shown, setShown ] = useState(false);
const [ enabled, setEnabled ] = useState(false);
@ -56,7 +71,7 @@ const EmojiButton = (props: { events: Registry<ChatBoxEvents> }) => {
return (
<div className={cssStyle.containerEmojis} ref={refContainer}>
<div className={cssStyle.button} onClick={() => enabled && setShown(true)}>
<img alt={""} src={"img/smiley-smile.svg"} />
<LastUsedEmoji />
</div>
<div className={cssStyle.picker} style={{ display: shown ? undefined : "none" }}>
{!shown ? undefined :
@ -72,6 +87,7 @@ const EmojiButton = (props: { events: Registry<ChatBoxEvents> }) => {
onSelect={(emoji: any) => {
if(enabled) {
settings.setValue(Settings.KEY_CHAT_LAST_USED_EMOJI, emoji.id as string);
props.events.fire("action_insert_text", { text: emoji.native, focus: true });
}
}}
@ -352,13 +368,15 @@ export class ChatBox extends React.Component<ChatBoxProperties, ChatBoxState> {
}
render() {
return <div className={cssStyle.container + " " + this.props.className}>
<div className={cssStyle.chatbox}>
<EmojiButton events={this.events} />
<TextInput events={this.events} placeholder={tr("Type your message here...")} />
return (
<div className={cssStyle.container + " " + this.props.className}>
<div className={cssStyle.chatbox}>
<EmojiButton events={this.events} />
<TextInput events={this.events} placeholder={tr("Type your message here...")} />
</div>
<MarkdownFormatHelper />
</div>
<MarkdownFormatHelper />
</div>
)
}
componentDidUpdate(prevProps: Readonly<ChatBoxProperties>, prevState: Readonly<ChatBoxState>, snapshot?: any): void {

View File

@ -536,4 +536,9 @@ export class ServerConnection extends AbstractServerConnection {
getControlStatistics(): ConnectionStatistics {
return this.socket?.getControlStatistics() || { bytesSend: 0, bytesReceived: 0 };
}
getServerType(): "teaspeak" | "teamspeak" | "unknown" {
/* It's simple. Only TeaSpeak support web clients */
return "teaspeak";
}
}