Implemented #170
parent
eb1884c307
commit
281748ac7e
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -75,6 +75,13 @@ html:root {
|
|||
> img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
align-self: center;
|
||||
|
||||
&.emoji {
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}>
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<ChatBoxProperties>, prevState: Readonly<ChatBoxState>, snapshot?: any): void {
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue