diff --git a/babel.config.js b/babel.config.js
index dd91e8d..e6243cc 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,5 +1,6 @@
+const pkg = require('./package.json');
+
module.exports = function (api) {
- api.cache(true);
const plugins = [
'@babel/plugin-transform-flow-strip-types',
'@babel/plugin-proposal-throw-expressions',
@@ -13,11 +14,18 @@ module.exports = function (api) {
const presets = [
[
"@babel/preset-env",
- {
- "targets": {
- "node": "current"
+ api.caller(caller => caller && caller.target === "node")
+ ? {
+ targets: {
+ node: pkg.engines.node.replace(/^\D+/g, ''),
+ },
+ modules: false,
+ }
+ : {
+ targets: {
+ browsers: pkg.browserslist,
+ },
}
- }
],
'@babel/react',
];
diff --git a/i18n/template-ssr.pot b/i18n/template-ssr.pot
index f03168e..404fd90 100644
--- a/i18n/template-ssr.pot
+++ b/i18n/template-ssr.pot
@@ -83,6 +83,14 @@ msgid ""
"one (Note: you can use those links just once)"
msgstr ""
+#: src/ssr-components/Main.jsx:70
+msgid "PixelPlanet.fun"
+msgstr ""
+
+#: src/ssr-components/Main.jsx:72
+msgid "Place color pixels on an map styled canvas with other players online"
+msgstr ""
+
#: src/ssr-components/Globe.jsx:44
msgid "Double click on globe to go back."
msgstr ""
@@ -99,14 +107,6 @@ msgstr ""
msgid "A 3D globe of our whole map"
msgstr ""
-#: src/ssr-components/Main.jsx:70
-msgid "PixelPlanet.fun"
-msgstr ""
-
-#: src/ssr-components/Main.jsx:72
-msgid "Place color pixels on an map styled canvas with other players online"
-msgstr ""
-
#: src/core/mail.js:65
#, javascript-format
msgid ""
@@ -305,6 +305,21 @@ msgstr ""
msgid "Password must be shorter than 60 characters."
msgstr ""
+#: src/routes/api/auth/verify.js:25
+#: src/routes/api/auth/verify.js:32
+msgid "Mail verification"
+msgstr ""
+
+#: src/routes/api/auth/verify.js:26
+msgid "You are now verified :)"
+msgstr ""
+
+#: src/routes/api/auth/verify.js:32
+msgid ""
+"Your mail verification code is invalid or already expired :(, please "
+"request a new one."
+msgstr ""
+
#: src/routes/api/auth/register.js:33
msgid "No Captcha given"
msgstr ""
@@ -325,21 +340,6 @@ msgstr ""
msgid "Failed to establish session after register :("
msgstr ""
-#: src/routes/api/auth/verify.js:25
-#: src/routes/api/auth/verify.js:32
-msgid "Mail verification"
-msgstr ""
-
-#: src/routes/api/auth/verify.js:26
-msgid "You are now verified :)"
-msgstr ""
-
-#: src/routes/api/auth/verify.js:32
-msgid ""
-"Your mail verification code is invalid or already expired :(, please "
-"request a new one."
-msgstr ""
-
#: src/routes/api/auth/logout.js:13
msgid "You are not even logged in."
msgstr ""
diff --git a/i18n/template.pot b/i18n/template.pot
index 62c2460..0caee60 100644
--- a/i18n/template.pot
+++ b/i18n/template.pot
@@ -3,48 +3,6 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n"
-#: src/controls/keypress.js:41
-#, javascript-format
-msgid "Switched to ${ canvasName }"
-msgstr ""
-
-#: src/controls/keypress.js:64
-msgid "Grid ON"
-msgstr ""
-
-#: src/controls/keypress.js:65
-msgid "Grid OFF"
-msgstr ""
-
-#: src/controls/keypress.js:75
-msgid "Pixel Notify ON"
-msgstr ""
-
-#: src/controls/keypress.js:76
-msgid "Pixel Notify OFF"
-msgstr ""
-
-#: src/controls/keypress.js:81
-msgid "Muted Sound"
-msgstr ""
-
-#: src/controls/keypress.js:82
-msgid "Unmuted Sound"
-msgstr ""
-
-#: src/components/CoordinatesBox.jsx:29
-#: src/controls/keypress.js:88
-msgid "Copied!"
-msgstr ""
-
-#: src/controls/keypress.js:94
-msgid "Show Hidden Canvases"
-msgstr ""
-
-#: src/controls/keypress.js:95
-msgid "Hide Hidden Canvases"
-msgstr ""
-
#: src/ui/placePixel.js:53
msgid "Error :("
msgstr ""
@@ -146,6 +104,48 @@ msgstr ""
msgid "Error ${ retCode }"
msgstr ""
+#: src/controls/keypress.js:41
+#, javascript-format
+msgid "Switched to ${ canvasName }"
+msgstr ""
+
+#: src/controls/keypress.js:64
+msgid "Grid ON"
+msgstr ""
+
+#: src/controls/keypress.js:65
+msgid "Grid OFF"
+msgstr ""
+
+#: src/controls/keypress.js:75
+msgid "Pixel Notify ON"
+msgstr ""
+
+#: src/controls/keypress.js:76
+msgid "Pixel Notify OFF"
+msgstr ""
+
+#: src/controls/keypress.js:81
+msgid "Muted Sound"
+msgstr ""
+
+#: src/controls/keypress.js:82
+msgid "Unmuted Sound"
+msgstr ""
+
+#: src/components/CoordinatesBox.jsx:29
+#: src/controls/keypress.js:88
+msgid "Copied!"
+msgstr ""
+
+#: src/controls/keypress.js:94
+msgid "Show Hidden Canvases"
+msgstr ""
+
+#: src/controls/keypress.js:95
+msgid "Hide Hidden Canvases"
+msgstr ""
+
#: src/ui/renderer.js:36
msgid "Canvas Error"
msgstr ""
@@ -170,11 +170,6 @@ msgstr ""
msgid "Look at past Canvases"
msgstr ""
-#: src/components/Converter.jsx:559
-#: src/components/CoordinatesBox.jsx:32
-msgid "Copy to Clipboard"
-msgstr ""
-
#: src/components/OnlineBox.jsx:41
msgid "Online Users on Canvas"
msgstr ""
@@ -187,6 +182,11 @@ msgstr ""
msgid "Pixels placed"
msgstr ""
+#: src/components/Converter.jsx:559
+#: src/components/CoordinatesBox.jsx:32
+msgid "Copy to Clipboard"
+msgstr ""
+
#: src/components/ModalRoot.jsx:69
#: src/components/Modtools.jsx:224
#: src/components/Window.jsx:138
@@ -198,6 +198,27 @@ msgstr ""
msgid "Restore"
msgstr ""
+#: src/components/buttons/ChatButton.jsx:92
+msgid "Close Chat"
+msgstr ""
+
+#: src/components/buttons/ChatButton.jsx:92
+msgid "Open Chat"
+msgstr ""
+
+#: src/components/buttons/CanvasSwitchButton.jsx:22
+#: src/components/windows/index.js:19
+msgid "Canvas Selection"
+msgstr ""
+
+#: src/components/buttons/ExpandMenuButton.jsx:23
+msgid "Close Menu"
+msgstr ""
+
+#: src/components/buttons/ExpandMenuButton.jsx:23
+msgid "Open Menu"
+msgstr ""
+
#: src/actions/fetch.js:39
msgid "You made too many requests"
msgstr ""
@@ -227,25 +248,8 @@ msgstr ""
msgid "Server answered with gibberish :("
msgstr ""
-#: src/components/buttons/CanvasSwitchButton.jsx:22
-#: src/components/windows/index.js:22
-msgid "Canvas Selection"
-msgstr ""
-
-#: src/components/buttons/ChatButton.jsx:92
-msgid "Close Chat"
-msgstr ""
-
-#: src/components/buttons/ChatButton.jsx:92
-msgid "Open Chat"
-msgstr ""
-
-#: src/components/buttons/ExpandMenuButton.jsx:23
-msgid "Close Menu"
-msgstr ""
-
-#: src/components/buttons/ExpandMenuButton.jsx:23
-msgid "Open Menu"
+#: src/components/buttons/GlobeButton.jsx:35
+msgid "Globe View"
msgstr ""
#: src/components/HistorySelect.jsx:144
@@ -256,6 +260,33 @@ msgstr ""
msgid "Select Date above"
msgstr ""
+#: src/components/buttons/PalselButton.jsx:31
+msgid "Close Palette"
+msgstr ""
+
+#: src/components/buttons/PalselButton.jsx:31
+msgid "Open Palette"
+msgstr ""
+
+#: src/components/buttons/HelpButton.jsx:23
+#: src/components/windows/index.js:13
+msgid "Help"
+msgstr ""
+
+#: src/components/buttons/SettingsButton.jsx:23
+#: src/components/windows/index.js:14
+msgid "Settings"
+msgstr ""
+
+#: src/components/buttons/DownloadButton.jsx:37
+msgid "Make Screenshot"
+msgstr ""
+
+#: src/components/buttons/LogInButton.jsx:23
+#: src/components/windows/index.js:15
+msgid "User Area"
+msgstr ""
+
#: src/components/Window.jsx:117
msgid "Clone"
msgstr ""
@@ -272,37 +303,6 @@ msgstr ""
msgid "Resize"
msgstr ""
-#: src/components/buttons/GlobeButton.jsx:35
-msgid "Globe View"
-msgstr ""
-
-#: src/components/buttons/PalselButton.jsx:31
-msgid "Close Palette"
-msgstr ""
-
-#: src/components/buttons/PalselButton.jsx:31
-msgid "Open Palette"
-msgstr ""
-
-#: src/components/buttons/HelpButton.jsx:23
-#: src/components/windows/index.js:16
-msgid "Help"
-msgstr ""
-
-#: src/components/buttons/SettingsButton.jsx:23
-#: src/components/windows/index.js:17
-msgid "Settings"
-msgstr ""
-
-#: src/components/buttons/LogInButton.jsx:23
-#: src/components/windows/index.js:18
-msgid "User Area"
-msgstr ""
-
-#: src/components/buttons/DownloadButton.jsx:37
-msgid "Make Screenshot"
-msgstr ""
-
#: src/components/contextmenus/UserContextMenu.jsx:53
msgid "Ping"
msgstr ""
@@ -319,19 +319,19 @@ msgstr ""
msgid "Mute"
msgstr ""
-#: src/components/windows/index.js:19
+#: src/components/windows/index.js:16
msgid "Registration"
msgstr ""
-#: src/components/windows/index.js:20
+#: src/components/windows/index.js:17
msgid "Forgot Password"
msgstr ""
-#: src/components/windows/index.js:21
+#: src/components/windows/index.js:18
msgid "Chat"
msgstr ""
-#: src/components/windows/index.js:23
+#: src/components/windows/index.js:20
msgid "Canvas Archive"
msgstr ""
@@ -538,30 +538,6 @@ msgstr ""
msgid "Credit for the Palette of the Top10 canvas goes to ${ vinikLink }."
msgstr ""
-#: src/components/windows/UserArea.jsx:27
-msgid "Profile"
-msgstr ""
-
-#: src/components/windows/UserArea.jsx:30
-msgid "Ranking"
-msgstr ""
-
-#: src/components/windows/UserArea.jsx:33
-msgid "Converter"
-msgstr ""
-
-#: src/components/windows/UserArea.jsx:39
-msgid "Modtools"
-msgstr ""
-
-#: src/components/windows/UserArea.jsx:40
-msgid "Loading..."
-msgstr ""
-
-#: src/components/windows/UserArea.jsx:47
-msgid "Consider joining us on Guilded:"
-msgstr ""
-
#: src/components/windows/Settings.jsx:133
msgid "Show Grid"
msgstr ""
@@ -655,48 +631,6 @@ msgstr ""
msgid "Select Language"
msgstr ""
-#: src/components/windows/Register.jsx:85
-msgid "Register new account here"
-msgstr ""
-
-#: src/components/windows/Register.jsx:90
-#: src/components/windows/Register.jsx:96
-msgid "Name"
-msgstr ""
-
-#: src/components/windows/ForgotPassword.jsx:82
-#: src/components/windows/Register.jsx:98
-#: src/components/windows/Register.jsx:104
-msgid "Email"
-msgstr ""
-
-#: src/components/ChangeMail.jsx:80
-#: src/components/DeleteAccount.jsx:62
-#: src/components/LogInForm.jsx:83
-#: src/components/windows/Register.jsx:106
-#: src/components/windows/Register.jsx:112
-msgid "Password"
-msgstr ""
-
-#: src/components/windows/Register.jsx:114
-#: src/components/windows/Register.jsx:120
-msgid "Confirm Password"
-msgstr ""
-
-#: src/components/windows/Register.jsx:122
-msgid "Captcha"
-msgstr ""
-
-#: src/components/Modtools.jsx:311
-#: src/components/Modtools.jsx:392
-#: src/components/Modtools.jsx:467
-#: src/components/Modtools.jsx:512
-#: src/components/Modtools.jsx:595
-#: src/components/windows/ForgotPassword.jsx:86
-#: src/components/windows/Register.jsx:125
-msgid "Submit"
-msgstr ""
-
#: src/components/windows/CanvasSelect.jsx:33
msgid ""
"Select the canvas you want to use. Every canvas is unique and has "
@@ -749,6 +683,22 @@ msgstr ""
msgid "Enter your mail address and we will send you a new password:"
msgstr ""
+#: src/components/windows/ForgotPassword.jsx:82
+#: src/components/windows/Register.jsx:98
+#: src/components/windows/Register.jsx:104
+msgid "Email"
+msgstr ""
+
+#: src/components/Modtools.jsx:311
+#: src/components/Modtools.jsx:392
+#: src/components/Modtools.jsx:467
+#: src/components/Modtools.jsx:512
+#: src/components/Modtools.jsx:595
+#: src/components/windows/ForgotPassword.jsx:86
+#: src/components/windows/Register.jsx:125
+msgid "Submit"
+msgstr ""
+
#: src/components/windows/Chat.jsx:146
msgid "Channel settings"
msgstr ""
@@ -765,6 +715,56 @@ msgstr ""
msgid "You must be logged in to chat"
msgstr ""
+#: src/components/windows/UserArea.jsx:27
+msgid "Profile"
+msgstr ""
+
+#: src/components/windows/UserArea.jsx:30
+msgid "Ranking"
+msgstr ""
+
+#: src/components/windows/UserArea.jsx:33
+msgid "Converter"
+msgstr ""
+
+#: src/components/windows/UserArea.jsx:39
+msgid "Modtools"
+msgstr ""
+
+#: src/components/windows/UserArea.jsx:40
+msgid "Loading..."
+msgstr ""
+
+#: src/components/windows/UserArea.jsx:47
+msgid "Consider joining us on Guilded:"
+msgstr ""
+
+#: src/components/windows/Register.jsx:85
+msgid "Register new account here"
+msgstr ""
+
+#: src/components/windows/Register.jsx:90
+#: src/components/windows/Register.jsx:96
+msgid "Name"
+msgstr ""
+
+#: src/components/ChangeMail.jsx:80
+#: src/components/DeleteAccount.jsx:62
+#: src/components/LogInForm.jsx:83
+#: src/components/windows/Register.jsx:106
+#: src/components/windows/Register.jsx:112
+msgid "Password"
+msgstr ""
+
+#: src/components/windows/Register.jsx:114
+#: src/components/windows/Register.jsx:120
+msgid "Confirm Password"
+msgstr ""
+
+#: src/components/windows/Register.jsx:122
+msgid "Captcha"
+msgstr ""
+
#: src/components/Captcha.jsx:50
#: src/components/Captcha.jsx:105
msgid "Could not load captcha"
@@ -846,28 +846,56 @@ msgstr ""
msgid "Password must be shorter than 60 characters."
msgstr ""
-#: src/components/LogInArea.jsx:21
-msgid "Login to access more features and stats."
+#: src/components/ChangeMail.jsx:91
+#: src/components/ChangeName.jsx:68
+#: src/components/ChangePassword.jsx:110
+#: src/components/LanguageSelect.jsx:73
+msgid "Save"
msgstr ""
-#: src/components/LogInArea.jsx:23
-msgid "Login with Name or Mail:"
+#: src/components/CanvasItem.jsx:30
+msgid "Online Users"
msgstr ""
-#: src/components/LogInArea.jsx:30
-msgid "I forgot my Password."
+#: src/components/CanvasItem.jsx:35
+msgid "Cooldown"
msgstr ""
-#: src/components/LogInArea.jsx:31
-msgid "or login with:"
+#: src/components/CanvasItem.jsx:41
+msgid "Stacking till"
msgstr ""
-#: src/components/LogInArea.jsx:72
-msgid "or register here:"
+#: src/components/CanvasItem.jsx:43
+msgid "Ranked"
msgstr ""
-#: src/components/LogInArea.jsx:79
-msgid "Register"
+#: src/components/CanvasItem.jsx:45
+msgid "Yes"
+msgstr ""
+
+#: src/components/CanvasItem.jsx:45
+msgid "No"
+msgstr ""
+
+#: src/components/CanvasItem.jsx:51
+msgid "Requirements"
+msgstr ""
+
+#: src/components/CanvasItem.jsx:54
+msgid "User Account"
+msgstr ""
+
+#: src/components/CanvasItem.jsx:56
+#, javascript-format
+msgid "and ${ canvas.req } Pixels set"
+msgstr ""
+
+#: src/components/CanvasItem.jsx:59
+msgid "Top 10 Daily Ranking"
+msgstr ""
+
+#: src/components/CanvasItem.jsx:65
+msgid "Dimensions"
msgstr ""
#: src/components/UserAreaContent.jsx:63
@@ -1083,64 +1111,28 @@ msgstr ""
msgid "Download Template"
msgstr ""
-#: src/components/ChangeMail.jsx:91
-#: src/components/ChangeName.jsx:68
-#: src/components/ChangePassword.jsx:110
-#: src/components/LanguageSelect.jsx:73
-msgid "Save"
+#: src/components/LogInArea.jsx:21
+msgid "Login to access more features and stats."
msgstr ""
-#: src/components/CanvasItem.jsx:30
-msgid "Online Users"
+#: src/components/LogInArea.jsx:23
+msgid "Login with Name or Mail:"
msgstr ""
-#: src/components/CanvasItem.jsx:35
-msgid "Cooldown"
+#: src/components/LogInArea.jsx:30
+msgid "I forgot my Password."
msgstr ""
-#: src/components/CanvasItem.jsx:41
-msgid "Stacking till"
+#: src/components/LogInArea.jsx:31
+msgid "or login with:"
msgstr ""
-#: src/components/CanvasItem.jsx:43
-msgid "Ranked"
+#: src/components/LogInArea.jsx:72
+msgid "or register here:"
msgstr ""
-#: src/components/CanvasItem.jsx:45
-msgid "Yes"
-msgstr ""
-
-#: src/components/CanvasItem.jsx:45
-msgid "No"
-msgstr ""
-
-#: src/components/CanvasItem.jsx:51
-msgid "Requirements"
-msgstr ""
-
-#: src/components/CanvasItem.jsx:54
-msgid "User Account"
-msgstr ""
-
-#: src/components/CanvasItem.jsx:56
-#, javascript-format
-msgid "and ${ canvas.req } Pixels set"
-msgstr ""
-
-#: src/components/CanvasItem.jsx:59
-msgid "Top 10 Daily Ranking"
-msgstr ""
-
-#: src/components/CanvasItem.jsx:65
-msgid "Dimensions"
-msgstr ""
-
-#: src/components/LogInForm.jsx:76
-msgid "Name or Email"
-msgstr ""
-
-#: src/components/LogInForm.jsx:87
-msgid "LogIn"
+#: src/components/LogInArea.jsx:79
+msgid "Register"
msgstr ""
#: src/components/UserMessages.jsx:28
@@ -1177,6 +1169,10 @@ msgstr ""
msgid "Confirm New Password"
msgstr ""
+#: src/components/ChangeName.jsx:64
+msgid "New Username"
+msgstr ""
+
#: src/components/ChangeMail.jsx:59
msgid ""
"Changed Mail successfully. We sent you a verification mail, "
@@ -1187,14 +1183,6 @@ msgstr ""
msgid "New Mail"
msgstr ""
-#: src/components/ChangeName.jsx:64
-msgid "New Username"
-msgstr ""
-
-#: src/components/DeleteAccount.jsx:66
-msgid "Yes, Delete My Account!"
-msgstr ""
-
#: src/components/SocialSettings.jsx:38
msgid "Block all Private Messages"
msgstr ""
@@ -1207,6 +1195,18 @@ msgstr ""
msgid "You have no users blocked"
msgstr ""
+#: src/components/DeleteAccount.jsx:66
+msgid "Yes, Delete My Account!"
+msgstr ""
+
+#: src/components/LogInForm.jsx:76
+msgid "Name or Email"
+msgstr ""
+
+#: src/components/LogInForm.jsx:87
+msgid "LogIn"
+msgstr ""
+
#: src/components/windows/Help.jsx:14
#: src/components/windows/Settings.jsx:134
msgctxt "keybinds"
diff --git a/public/embico/direct.png b/public/embico/direct.png
new file mode 100644
index 0000000..0ce2d67
Binary files /dev/null and b/public/embico/direct.png differ
diff --git a/public/embico/matrix.png b/public/embico/matrix.png
new file mode 100644
index 0000000..2c18c76
Binary files /dev/null and b/public/embico/matrix.png differ
diff --git a/public/embico/tiktok.png b/public/embico/tiktok.png
new file mode 100644
index 0000000..0f603e5
Binary files /dev/null and b/public/embico/tiktok.png differ
diff --git a/public/embico/youtube.png b/public/embico/youtube.png
new file mode 100644
index 0000000..cf0bd43
Binary files /dev/null and b/public/embico/youtube.png differ
diff --git a/src/actions/index.js b/src/actions/index.js
index 0809682..96712f0 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -760,6 +760,16 @@ export function addToChatInputMessage(windowId, msg) {
};
}
+export function addToChatInputMessageAndFocus(windowId, msg) {
+ return (dispatch) => {
+ dispatch(addToChatInputMessage(windowId, msg));
+ const inputElem = document.getElementById(`chtipt-${windowId}`);
+ if (inputElem) {
+ inputElem.focus();
+ }
+ };
+}
+
export function closeWindow(windowId) {
return {
type: 'CLOSE_WINDOW',
diff --git a/src/components/ChatMessage.jsx b/src/components/ChatMessage.jsx
index eaf5f38..9371c63 100644
--- a/src/components/ChatMessage.jsx
+++ b/src/components/ChatMessage.jsx
@@ -3,25 +3,29 @@
* @flow
*/
import React from 'react';
-import { useDispatch } from 'react-redux';
+import { useSelector, useDispatch } from 'react-redux';
import { showContextMenu } from '../actions';
+import { MarkdownParagraph } from './Markdown';
import { colorFromText, setBrightness } from '../core/utils';
+import { parseParagraph } from '../core/MarkdownParser';
function ChatMessage({
name,
uid,
country,
- dark,
windowId,
- msgArray,
+ msg,
}) {
- if (!name || !msgArray) {
+ if (!name) {
return null;
}
const dispatch = useDispatch();
+ const isDarkMode = useSelector(
+ (state) => state.gui.style.indexOf('dark') !== -1,
+ );
const isInfo = (name === 'info');
const isEvent = (name === 'event');
@@ -30,90 +34,61 @@ function ChatMessage({
className += ' info';
} else if (isEvent) {
className += ' event';
- } else if (msgArray[0][1].charAt(0) === '>') {
+ } else if (msg.charAt(0) === '>') {
className += ' greentext';
+ } else if (msg.charAt(0) === '<') {
+ className += ' redtext';
}
+ const pArray = parseParagraph(msg);
+
return (
-
- {
+
+
+ {
(!isInfo && !isEvent)
&& (
-
-
{
- e.target.onerror = null;
- e.target.src = './cf/xx.gif';
- }}
- />
-
- {
- const {
- clientX,
- clientY,
- } = event;
- dispatch(showContextMenu('USER', clientX, clientY, {
- windowId,
- uid,
- name,
- }));
- }}
- >
- {name}
-
- :
-
+ <>
+
![{country}]({`${window.ssv.assetserver}/cf/${country}.gif`})
{
+ e.target.onerror = null;
+ e.target.src = './cf/xx.gif';
+ }}
+ />
+
+
{
+ const {
+ clientX,
+ clientY,
+ } = event;
+ dispatch(showContextMenu('USER', clientX, clientY, {
+ windowId,
+ uid,
+ name,
+ }));
+ }}
+ >
+ {name}
+
+ :
+ >
)
}
- {
- msgArray.map((msgPart) => {
- const [type, txt] = msgPart;
- if (type === 't') {
- return (
{txt});
- } if (type === 'c') {
- return (
{txt});
- } if (type === 'l') {
- return (
-
{txt}
- );
- } if (type === 'p') {
- return (
-
{txt}
- );
- } if (type === 'm') {
- return (
-
{txt}
- );
- }
- return null;
- })
- }
-
+
+
+
+
+
);
}
diff --git a/src/components/Markdown.jsx b/src/components/Markdown.jsx
new file mode 100644
index 0000000..dbedd12
--- /dev/null
+++ b/src/components/Markdown.jsx
@@ -0,0 +1,148 @@
+/*
+ * Renders Markdown that got parsed by core/MarkdownParser
+ */
+import React from 'react';
+
+import MdLink from './MdLink';
+import MdMention from './MdMention';
+
+// eslint-disable-next-line max-len
+export const MarkdownParagraph = React.memo(({ pArray }) => pArray.map((part) => {
+ if (!Array.isArray(part)) {
+ return part;
+ }
+ const type = part[0];
+ switch (type) {
+ case 'c':
+ return ({part[1]}
);
+ case '*':
+ return (
+
+
+
+ );
+ case '~':
+ return (
+
+
+
+ );
+ case '+':
+ return (
+
+
+
+ );
+ case '_':
+ return (
+
+
+
+ );
+ case 'img':
+ case 'l': {
+ return (
+
+ );
+ }
+ case '@': {
+ return (
+
+ );
+ }
+ default:
+ return type;
+ }
+}));
+
+const Markdown = ({ mdArray }) => mdArray.map((part) => {
+ const type = part[0];
+ switch (type) {
+ /* Heading */
+ case 'a': {
+ const level = Number(part[1]);
+ const heading = part[2];
+ const children = part[3];
+ let headingElem = [];
+ switch (level) {
+ case 1:
+ headingElem = {heading}
;
+ break;
+ case 2:
+ headingElem = {heading}
;
+ break;
+ case 3:
+ headingElem = {heading}
;
+ break;
+ default:
+ headingElem = {heading}
;
+ }
+ return (
+ <>
+ {headingElem}
+
+ >
+ );
+ }
+ /* Paragraph */
+ case 'p': {
+ return (
+
+
+
+ );
+ }
+ /* Code Block */
+ case 'cb': {
+ const content = part[1];
+ return {content}
;
+ }
+ case '>':
+ case '<': {
+ const children = part[1];
+ return (
+ ') ? 'gt' : 'rt'}
+ >
+
+
+ );
+ }
+ case 'ul': {
+ const children = part[1];
+ return (
+
+ );
+ }
+ case 'ol': {
+ const children = part[1];
+ return (
+
+
+
+ );
+ }
+ case '-': {
+ const children = part[1];
+ return (
+
+
+
+ );
+ }
+ default:
+ return part[0];
+ }
+});
+
+const MarkdownArticle = ({ mdArray }) => (
+
+
+
+);
+
+export default React.memo(MarkdownArticle);
diff --git a/src/components/MdLink.jsx b/src/components/MdLink.jsx
new file mode 100644
index 0000000..7a658da
--- /dev/null
+++ b/src/components/MdLink.jsx
@@ -0,0 +1,94 @@
+/*
+ * Renders a markdown link
+ * Also provides previews
+ * Links are assumed to start with protocol (http:// etc.)
+ */
+import React, { useState } from 'react';
+import { HiArrowsExpand, HiStop } from 'react-icons/hi';
+
+import { getLinkDesc } from '../core/utils';
+import EMBEDS from './embeds';
+
+const titleAllowed = [
+ 'odysee',
+ 'twitter',
+ 'matrix.pixelplanet.fun',
+ 'youtube',
+ 'youtu.be',
+];
+
+const MdLink = ({ href, title }) => {
+ const [showEmbed, setShowEmbed] = useState(false);
+
+ const desc = getLinkDesc(href);
+
+ // treat pixelplanet links seperately
+ if (desc === window.location.hostname && href.includes('/#')) {
+ const coords = href.substring(href.indexOf('/#') + 1);
+ return (
+ {title || coords}
+ );
+ }
+
+ const embedObj = EMBEDS[desc];
+ const embedAvailable = embedObj && embedObj[1](href);
+ const Embed = embedObj && embedObj[0];
+
+
+ let parsedTitle;
+ if (title && titleAllowed.includes(desc)) {
+ parsedTitle = title;
+ } else if (embedAvailable && embedObj[2]) {
+ parsedTitle = embedObj[2](href);
+ } else {
+ parsedTitle = href;
+ }
+
+ return (
+ <>
+
+ {parsedTitle}
+
+ {(embedAvailable) && (
+ <>
+
+ {(embedObj[3])
+ && (
+
+ )}
+ setShowEmbed(!showEmbed)}
+ >
+ {(showEmbed)
+ ? (
+
+ )
+ : (
+
+ )}
+
+ >
+ )}
+ {(showEmbed && embedAvailable) && }
+ >
+ );
+};
+
+export default React.memo(MdLink);
diff --git a/src/components/MdMention.jsx b/src/components/MdMention.jsx
new file mode 100644
index 0000000..4bbc6f4
--- /dev/null
+++ b/src/components/MdMention.jsx
@@ -0,0 +1,30 @@
+/*
+ * Parse Mention of Username
+ */
+import React from 'react';
+import { useSelector } from 'react-redux';
+
+import { colorFromText, setBrightness } from '../core/utils';
+
+const MdMention = ({ name, uid }) => {
+ const id = uid && uid.trim();
+
+ const isDarkMode = useSelector(
+ (state) => state.gui.style.indexOf('dark') !== -1,
+ );
+ const ownId = useSelector((state) => state.user.id);
+
+ return (
+ {`@${name}`}
+ );
+};
+
+export default React.memo(MdMention);
diff --git a/src/components/contextmenus/UserContextMenu.jsx b/src/components/contextmenus/UserContextMenu.jsx
index 197285c..0f90d1d 100644
--- a/src/components/contextmenus/UserContextMenu.jsx
+++ b/src/components/contextmenus/UserContextMenu.jsx
@@ -12,7 +12,7 @@ import {
} from '../hooks/clickOutside';
import {
hideContextMenu,
- addToChatInputMessage,
+ addToChatInputMessageAndFocus,
startDm,
setUserBlock,
setChatChannel,
@@ -45,7 +45,9 @@ const UserContextMenu = () => {
role="button"
tabIndex={0}
onClick={() => {
- dispatch(addToChatInputMessage(windowId, `@${name} `));
+ dispatch(
+ addToChatInputMessageAndFocus(windowId, `@[${name}](${uid}) `),
+ );
close();
}}
style={{ borderTop: 'none' }}
diff --git a/src/components/embeds/DirectLinkMedia.jsx b/src/components/embeds/DirectLinkMedia.jsx
new file mode 100644
index 0000000..e632a22
--- /dev/null
+++ b/src/components/embeds/DirectLinkMedia.jsx
@@ -0,0 +1,52 @@
+/* eslint-disable jsx-a11y/media-has-caption */
+
+import React from 'react';
+
+import { getExt } from '../../core/utils';
+
+const videoExts = [
+ 'webm',
+ 'mp4',
+];
+const imageExts = [
+ 'jpg',
+ 'jpeg',
+ 'png',
+ 'webp',
+ 'gif',
+];
+
+const DirectLinkMedia = ({ url }) => {
+ const ext = getExt(url);
+ if (videoExts.includes(ext)) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+ );
+};
+
+export default [
+ React.memo(DirectLinkMedia),
+ (url) => {
+ const ext = getExt(url);
+ return (videoExts.includes(ext) || imageExts.includes(ext));
+ },
+ null,
+ `${window.ssv.assetserver}/embico/direct.png`,
+];
diff --git a/src/components/embeds/Matrix.jsx b/src/components/embeds/Matrix.jsx
new file mode 100644
index 0000000..0ef1a06
--- /dev/null
+++ b/src/components/embeds/Matrix.jsx
@@ -0,0 +1,33 @@
+/* eslint-disable jsx-a11y/media-has-caption */
+
+import React from 'react';
+
+const Matrix = ({ url }) => {
+ const cleanUrl = url.substring(0, url.indexOf('?type='));
+ if (url.includes('?type=video')) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+ );
+};
+
+export default [
+ React.memo(Matrix),
+ (url) => url.includes('?type=video') || url.includes('?type=image'),
+ null,
+ `${window.ssv.assetserver}/embico/matrix.png`,
+];
diff --git a/src/components/embeds/TikTok.jsx b/src/components/embeds/TikTok.jsx
new file mode 100644
index 0000000..e0ea412
--- /dev/null
+++ b/src/components/embeds/TikTok.jsx
@@ -0,0 +1,59 @@
+import React, { useState, useEffect } from 'react';
+
+function getUserFromUrl(url) {
+ let aPos = url.indexOf('/@');
+ if (aPos === -1) {
+ return url;
+ }
+ aPos += 1;
+ let bPos = url.indexOf('/', aPos);
+ if (bPos === -1) {
+ bPos = url.length;
+ }
+ return url.substring(aPos, bPos);
+}
+
+const TikTok = ({ url }) => {
+ const [embedCode, setEmbedCode] = useState(null);
+
+ useEffect(async () => {
+ const prot = window.location.protocol.startsWith('http')
+ ? window.location.protocol : 'https';
+ // eslint-disable-next-line max-len
+ const tkurl = `${prot}//www.tiktok.com/oembed?url=${encodeURIComponent(url)}`;
+ const resp = await fetch(tkurl);
+ const embedData = await resp.json();
+ if (embedData.html) {
+ setEmbedCode(embedData.html);
+ }
+ }, []);
+
+ if (!embedCode) {
+ return LOADING
;
+ }
+
+ return (
+
+ );
+};
+
+export default [
+ React.memo(TikTok),
+ (url) => url.includes('/video/'),
+ (url) => getUserFromUrl(url),
+ `${window.ssv.assetserver}/embico/tiktok.png`,
+];
diff --git a/src/components/embeds/YouTube.jsx b/src/components/embeds/YouTube.jsx
new file mode 100644
index 0000000..81af61f
--- /dev/null
+++ b/src/components/embeds/YouTube.jsx
@@ -0,0 +1,49 @@
+import React from 'react';
+
+function getIdFromURL(url) {
+ let vPos = -1;
+ if (url.includes('youtube')) {
+ vPos = url.indexOf('v=');
+ }
+ if (url.includes('youtu.be')) {
+ vPos = url.indexOf('e/');
+ }
+ if (vPos === -1) {
+ return null;
+ }
+ vPos += 2;
+ let vEnd;
+ for (vEnd = vPos;
+ vEnd < url.length && !['&', '?', '/'].includes(url[vEnd]);
+ vEnd += 1);
+ return url.substring(vPos, vEnd);
+}
+
+const YouTube = ({ url }) => {
+ const id = getIdFromURL(url);
+ if (!id) {
+ return null;
+ }
+ return (
+
+
+
+ );
+};
+
+export default [
+ React.memo(YouTube),
+ getIdFromURL,
+ (url) => getIdFromURL(url),
+ `${window.ssv.assetserver}/embico/youtube.png`,
+];
diff --git a/src/components/embeds/index.js b/src/components/embeds/index.js
new file mode 100644
index 0000000..6783c33
--- /dev/null
+++ b/src/components/embeds/index.js
@@ -0,0 +1,28 @@
+/*
+ * Embeds for external content like youtube, etc.
+ *
+ */
+import TikTok from './TikTok';
+import YouTube from './YouTube';
+import Matrix from './Matrix';
+import DirectLinkMedia from './DirectLinkMedia';
+
+/*
+ * key is the domain (with .com and www. stripped)
+ * value is an Array with
+ * [
+ * ReactElement: takes url as prop,
+ * isEmbedAvailable: function that takes url as argument and returns boolean
+ * whether embed is available for this url of this domain
+ * ]
+ */
+export default {
+ tiktok: TikTok,
+ youtube: YouTube,
+ 'youtu.be': YouTube,
+ 'matrix.pixelplanet.fun': Matrix,
+ 'i.4cdn.org': DirectLinkMedia,
+ 'i.imgur': DirectLinkMedia,
+ 'litter.catbox.moe': DirectLinkMedia,
+ 'files.catbox.moe': DirectLinkMedia,
+};
diff --git a/src/components/windows/Chat.jsx b/src/components/windows/Chat.jsx
index 61ed86e..edf2865 100644
--- a/src/components/windows/Chat.jsx
+++ b/src/components/windows/Chat.jsx
@@ -22,11 +22,7 @@ import {
setWindowTitle,
} from '../../actions';
import SocketClient from '../../socket/SocketClient';
-import splitChatMessage from '../../core/chatMessageFilter';
-function escapeRegExp(string) {
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
-}
const Chat = ({
windowId,
@@ -34,7 +30,6 @@ const Chat = ({
const listRef = useRef();
const targetRef = useRef();
- const [nameRegExp, setNameRegExp] = useState(null);
const [blockedIds, setBlockedIds] = useState([]);
const [btnSize, setBtnSize] = useState(20);
@@ -46,7 +41,6 @@ const Chat = ({
const ownName = useSelector((state) => state.user.name);
// eslint-disable-next-line max-len
- const isDarkMode = useSelector((state) => state.gui.style.indexOf('dark') !== -1);
const fetching = useSelector((state) => state.fetching.fetchingChat);
const { channels, messages, blocked } = useSelector((state) => state.chat);
// eslint-disable-next-line max-len
@@ -73,13 +67,6 @@ const Chat = ({
stayScrolled();
}, [channelMessages.length]);
- useEffect(() => {
- const regExp = (ownName)
- ? new RegExp(`(^|\\s)(@${escapeRegExp(ownName)})(\\s|$)`, 'g')
- : null;
- setNameRegExp(regExp);
- }, [ownName]);
-
useEffect(() => {
setTimeout(() => {
const fontSize = Math.round(targetRef.current.offsetHeight / 10);
@@ -97,10 +84,10 @@ const Chat = ({
function handleSubmit(e) {
e.preventDefault();
- const msg = inputMessage.trim();
- if (!msg) return;
+ const inptMsg = inputMessage.trim();
+ if (!inptMsg) return;
// send message via websocket
- SocketClient.sendChatMessage(msg, chatChannel);
+ SocketClient.sendChatMessage(inptMsg, chatChannel);
dispatch(setChatInputMessage(windowId, ''));
}
@@ -157,11 +144,10 @@ const Chat = ({
(!channelMessages.length)
&& (
)
@@ -170,11 +156,10 @@ const Chat = ({
channelMessages.map((message) => ((blockedIds.includes(message[3]))
? null : (
)))
@@ -188,6 +173,7 @@ const Chat = ({
>
dispatch(
setChatInputMessage(windowId, e.target.value),
diff --git a/src/components/windows/index.js b/src/components/windows/index.js
index 8d997a1..9abc270 100644
--- a/src/components/windows/index.js
+++ b/src/components/windows/index.js
@@ -1,6 +1,3 @@
-/*
- * @flow
- */
import { t } from 'ttag';
import Help from './Help';
diff --git a/src/core/ChatProvider.js b/src/core/ChatProvider.js
index 7662344..693c213 100644
--- a/src/core/ChatProvider.js
+++ b/src/core/ChatProvider.js
@@ -20,6 +20,14 @@ import {
APISOCKET_USER_NAME,
} from './constants';
+function getUserFromMd(mdUserLink) {
+ const mdUser = mdUserLink.trim();
+ if (mdUser[0] === '[' && mdUser[mdUser.length - 1] === ')') {
+ return mdUser.substring(1, mdUser.lastIndexOf(']')).trim();
+ }
+ return mdUser;
+}
+
export class ChatProvider {
constructor() {
this.defaultChannels = {};
@@ -262,21 +270,30 @@ export class ChatProvider {
case 'mute': {
const timeMin = Number(args.slice(-1));
if (Number.isNaN(timeMin)) {
- return this.mute(args.join(' '), channelId);
+ return this.mute(
+ getUserFromMd(args.join(' ')),
+ channelId,
+ );
}
return this.mute(
- args.slice(0, -1).join(' '),
+ getUserFromMd(args.slice(0, -1).join(' ')),
channelId,
timeMin,
);
}
case 'unmute':
- return this.unmute(args.join(' '), channelId);
+ return this.unmute(
+ getUserFromMd(args.join(' ')),
+ channelId,
+ );
case 'mutec': {
if (args[0]) {
const cc = args[0].toLowerCase();
+ if (cc.length > 3) {
+ return 'No legit country defined';
+ }
this.mutedCountries.push(cc);
this.broadcastChatMessage(
'info',
diff --git a/src/core/MString.js b/src/core/MString.js
index eac3dd1..add59a7 100644
--- a/src/core/MString.js
+++ b/src/core/MString.js
@@ -212,4 +212,56 @@ export default class MString {
this.iter = cIter;
return link;
}
+
+ /*
+ * Check if current '#' is part of ppfun coordinates (example: #d,23,11,-10)
+ * @return null if not coords, otherwise the coords string
+ */
+ checkIfCoords() {
+ let cIter = this.iter + 1;
+ while (cIter < this.txt.length) {
+ const chr = this.txt[cIter];
+ if (chr === ',') {
+ break;
+ }
+ if (MString.isWhiteSpace(chr)
+ || !Number.isNaN(Number(chr))
+ ) {
+ return null;
+ }
+ cIter += 1;
+ }
+ if (cIter >= this.txt.length
+ || cIter - this.iter > 12
+ || cIter === this.iter
+ ) {
+ return null;
+ }
+ cIter += 1;
+ const curChr = this.txt[cIter];
+ if (curChr !== '-' && Number.isNaN(curChr)) {
+ return null;
+ }
+ cIter += 1;
+ let sectCount = 1;
+ while (cIter < this.txt.length && !MString.isWhiteSpace(this.txt[cIter])) {
+ const chr = this.txt[cIter];
+ if (chr === ',') {
+ sectCount += 1;
+ } else if (chr !== '-' && Number.isNaN(Number(chr))) {
+ return null;
+ }
+ cIter += 1;
+ }
+ if (sectCount < 2
+ || sectCount > 3
+ || this.txt[cIter - 1] === ','
+ ) {
+ return null;
+ }
+
+ const coords = this.txt.slice(this.iter, cIter);
+ this.iter = cIter;
+ return coords;
+ }
}
diff --git a/src/core/MarkdownParser.js b/src/core/MarkdownParser.js
index 5a70a3b..6e6f0e2 100644
--- a/src/core/MarkdownParser.js
+++ b/src/core/MarkdownParser.js
@@ -5,8 +5,6 @@
* stuff like pixelplanet coords and usernames and bare links.
* This code is written in preparation for a possible imporementation in
* WebAssambly, so it's all in a big loop
- *
- * @flow
*/
import MString from './MString';
@@ -41,6 +39,19 @@ function parseMParagraph(text, opts, breakChar) {
}
pStart = text.iter + 1;
text.moveForward();
+ } else if (chr === '#') {
+ /*
+ * ppfun coords #d,34,23,-10
+ */
+ const oldPos = text.iter;
+ const coords = text.checkIfCoords();
+ if (coords) {
+ if (pStart !== oldPos) {
+ pArray.push(text.slice(pStart, oldPos));
+ }
+ pArray.push(['l', null, `${window.location.origin}/${coords}`]);
+ pStart = text.iter;
+ }
} else if (paraElems.includes(chr)) {
/*
* bold, cursive, underline, etc.
@@ -100,10 +111,14 @@ function parseMParagraph(text, opts, breakChar) {
* defaults to ordinary link
*/
let tag = 'l';
- const zIsLink = true;
+ let zIsLink = true;
if (x === '!') {
tag = 'img';
oldPos -= 1;
+ } else if (x === '@') {
+ zIsLink = false;
+ tag = '@';
+ oldPos -= 1;
}
const encArr = text.checkIfEnclosure(zIsLink);
@@ -174,8 +189,8 @@ function parseQuote(text, opts) {
* or ident is smaller than given
*/
function parseMSection(
- text: string,
- opts: Object,
+ text,
+ opts,
headingLevel,
indent,
) {
@@ -351,13 +366,13 @@ function parseOpts(inOpts) {
return opts;
}
-export function parseParagraph(text: string, inOpts) {
+export function parseParagraph(text, inOpts) {
const opts = parseOpts(inOpts);
const mText = new MString(text);
return parseMParagraph(mText, opts);
}
-export function parse(text: string, inOpts) {
+export function parse(text, inOpts) {
const opts = parseOpts(inOpts);
const mText = new MString(text);
return parseMText(mText, opts, 0);
diff --git a/src/core/chatMessageFilter.js b/src/core/chatMessageFilter.js
deleted file mode 100644
index fb80ca7..0000000
--- a/src/core/chatMessageFilter.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- *
- * @flow
- */
-
-
-/*
- * splits chat message into array of what it represents
- * [[type, text],[type, text], ...]
- * type:
- * 'l': external link
- * 't': text
- * 'p': ping
- * 'c': coordinates or ppfun link
- * 'm': mention of somebody else
- * nameRegExp has to be in the form of:
- new RegExp(`(^|\\s+)(@${ownName})(\\s+|$)`, 'g');
- */
-// eslint-disable-next-line
-const linkRegExp = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi;
-const ppLinkRegExp = /(#[a-z]*,-?[0-9]*,-?[0-9]*(,-?[0-9]+)?)/gi;
-const linkRegExpFilter = (val, ind) => ((ind % 3) !== 2);
-const mentionRegExp = /(^|\s)(@\S+)/g;
-const spaceFilter = (val, ind) => (val !== ' ' && (ind !== 0 | val !== ''));
-
-function splitChatMessageRegexp(
- msgArray,
- regExp,
- ident,
- filter = () => true,
-) {
- return msgArray.map((msgPart) => {
- const [type, part] = msgPart;
- if (type !== 't') {
- return [msgPart];
- }
- return part
- .split(regExp)
- .filter(filter)
- .map((stri, i) => {
- if (i % 2 === 0) {
- return ['t', stri];
- }
- return [ident, stri];
- })
- .filter((el) => !!el[1]);
- }).flat(1);
-}
-
-function splitChatMessage(message, nameRegExp = null) {
- if (!message) {
- return null;
- }
- let arr = [['t', message.trim()]];
- arr = splitChatMessageRegexp(arr, ppLinkRegExp, 'c', linkRegExpFilter);
- if (nameRegExp) {
- arr = splitChatMessageRegexp(arr, nameRegExp, 'p', spaceFilter);
- }
- arr = splitChatMessageRegexp(arr, linkRegExp, 'l', linkRegExpFilter);
- arr = splitChatMessageRegexp(arr, mentionRegExp, 'm', spaceFilter);
- return arr;
-}
-
-export default splitChatMessage;
diff --git a/src/core/utils.js b/src/core/utils.js
index eccc9df..4d58a12 100644
--- a/src/core/utils.js
+++ b/src/core/utils.js
@@ -307,20 +307,10 @@ export function setBrightness(hex, dark: boolean = false) {
* @param string input string
* @return escaped string
*/
-function escapeRegExp(string) {
+export function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
-/*
- * create RegExp to search for ping in chat messages
- * @param name name
- * @return regular expression to search for name in message
- */
-export function createNameRegExp(name) {
- if (!name) return null;
- return new RegExp(`(^|\\s+)(@${escapeRegExp(name)})(\\s+|$)`, 'g');
-}
-
/*
* check if webGL2 is available
* @return boolean true if available
@@ -333,3 +323,54 @@ export function isWebGL2Available() {
return false;
}
}
+
+/*
+ * gets a descriptive text of the domain of the link
+ * Example:
+ * https://www.youtube.com/watch?v=G8APgeFfkAk returns 'youtube'
+ * http://www.google.at returns 'google.at'
+ * (www. and .com are split)
+ */
+export function getLinkDesc(link) {
+ let domainStart = link.indexOf('://') + 3;
+ if (domainStart < 3) {
+ domainStart = 0;
+ }
+ if (link.startsWith('www.', domainStart)) {
+ domainStart += 4;
+ }
+ let domainEnd = link.indexOf('/', domainStart);
+ if (domainEnd === -1) {
+ domainEnd = link.length;
+ }
+ if (link.endsWith('.com', domainEnd)) {
+ domainEnd -= 4;
+ }
+ if (domainEnd <= domainStart) {
+ return link;
+ }
+ return link.slice(domainStart, domainEnd);
+}
+
+/*
+ * try to get extension out of link
+ * @param link url
+ * @return extension or null if not available
+ */
+export function getExt(link) {
+ let paramStart = link.indexOf('&');
+ if (paramStart === -1) {
+ paramStart = link.length;
+ }
+ let posDot = paramStart - 1;
+ for (;posDot >= 0 && link[posDot] !== '.'; posDot -= 1) {
+ if (link[posDot] === '/') {
+ return null;
+ }
+ }
+ posDot += 1;
+ if (paramStart - posDot > 4) {
+ return null;
+ }
+ return link.slice(posDot, paramStart);
+}
diff --git a/src/data/models/User.js b/src/data/models/User.js
index 1d6440a..d161f5f 100644
--- a/src/data/models/User.js
+++ b/src/data/models/User.js
@@ -219,8 +219,21 @@ class User {
}
getUserData(): Object {
+ const {
+ id,
+ userlvl,
+ channels,
+ blocked,
+ } = this;
+ const data = {
+ id,
+ userlvl,
+ channels,
+ blocked,
+ };
if (this.regUser == null) {
return {
+ ...data,
name: null,
mailVerified: false,
blockDm: false,
@@ -229,15 +242,11 @@ class User {
ranking: null,
dailyRanking: null,
mailreg: false,
- userlvl: 0,
- channels: this.channels,
- blocked: this.blocked,
};
}
- const {
- regUser, userlvl, channels, blocked,
- } = this;
+ const { regUser } = this;
return {
+ ...data,
name: regUser.name,
mailVerified: regUser.mailVerified,
blockDm: regUser.blockDm,
@@ -246,9 +255,6 @@ class User {
ranking: regUser.ranking,
dailyRanking: regUser.dailyRanking,
mailreg: !!(regUser.password),
- userlvl,
- channels,
- blocked,
};
}
}
diff --git a/src/reducers/user.js b/src/reducers/user.js
index 215aedc..b1d77f9 100644
--- a/src/reducers/user.js
+++ b/src/reducers/user.js
@@ -1,8 +1,6 @@
-import { createNameRegExp } from '../core/utils';
-
const initialState = {
+ id: null,
name: null,
- center: [0, 0],
wait: null,
coolDown: null, // ms
lastCoolDownEnd: null,
@@ -18,8 +16,6 @@ const initialState = {
notification: null,
// 1: Admin, 2: Mod, 0: ordinary user
userlvl: 0,
- // regExp for detecting ping
- nameRegExp: null,
};
export default function user(
@@ -86,43 +82,41 @@ export default function user(
case 'RECEIVE_ME':
case 'LOGIN': {
const {
+ id,
name,
mailreg,
blockDm,
userlvl,
} = action;
- const nameRegExp = createNameRegExp(name);
const messages = (action.messages) ? action.messages : [];
return {
...state,
+ id,
name,
messages,
mailreg,
blockDm,
userlvl,
- nameRegExp,
};
}
case 'LOGOUT': {
return {
...state,
+ id: null,
name: null,
messages: [],
mailreg: false,
blockDm: false,
userlvl: 0,
- nameRegExp: null,
};
}
case 'SET_NAME': {
const { name } = action;
- const nameRegExp = createNameRegExp(name);
return {
...state,
name,
- nameRegExp,
};
}
diff --git a/src/styles/default.css b/src/styles/default.css
index f68d856..387a903 100644
--- a/src/styles/default.css
+++ b/src/styles/default.css
@@ -665,17 +665,20 @@ tr:nth-child(even) {
white-space: nowrap;
}
.chatname {
- color: #4B0000;
font-size: 13px;
+ white-space: nowrap;
+ display: inline-block;
}
.chatmsg {
color: #030303;
font-size: 13px;
user-select: text;
margin: 0;
+ display: flex;
+ flex-direction: row;
}
-.chatmsg > span {
- display: inline-block;
+.msg {
+ flex: 1;
}
.msg.info {
color: #cc0000;
@@ -686,6 +689,9 @@ tr:nth-child(even) {
.msg.greentext{
color: green;
}
+.msg.redtext{
+ color: red;
+}
.ping {
background-color: #ffff87;
border-style: solid;
@@ -905,3 +911,18 @@ tr:nth-child(even) {
.Modal.show, .Alert.show, .OverlayAlert.show, .OverlayModal.show, .window.show {
opacity: 1;
}
+
+.vemb {
+ overflow: hidden;
+ position: relative;
+ height: 0;
+ padding-bottom: 66%;
+}
+
+.vembc {
+ left: 0;
+ top: 0;
+ height: 100%;
+ width: 100%;
+ position: absolute;
+}
diff --git a/utils/markdown-test/Markdown.jsx b/utils/markdown-test/Markdown.jsx
index 6679b66..b4a1061 100644
--- a/utils/markdown-test/Markdown.jsx
+++ b/utils/markdown-test/Markdown.jsx
@@ -3,7 +3,10 @@
*/
import React from 'react';
-const MarkdownParagraph = ({ pArray }) => pArray.map((part) => {
+import MdLink from '../../src/components/MdLink';
+import MdMention from './MdMention';
+
+export const MarkdownParagraph = React.memo(({ pArray }) => pArray.map((part) => {
if (!Array.isArray(part)) {
return part;
}
@@ -14,51 +17,42 @@ const MarkdownParagraph = ({ pArray }) => pArray.map((part) => {
case '*':
return (
-
+
);
case '~':
return (
-
+
);
case '+':
return (
-
+
);
case '_':
return (
-
+
);
+ case 'img':
case 'l': {
- let title = getLinkDesc(part[2]);
- if (part[1]) {
- title += ` | ${part[1]}`;
- }
return (
-
- {title}
-
+
);
}
- case 'img': {
- let title = getLinkDesc(part[2]);
- if (part[1]) {
- title += ` | ${part[1]}`;
- }
+ case '@': {
return (
-
+
);
}
default:
return type;
}
-});
+}));
const Markdown = ({ mdArray }) => mdArray.map((part) => {
const type = part[0];
@@ -86,7 +80,7 @@ const Markdown = ({ mdArray }) => mdArray.map((part) => {
headingElem,
+ ,
];
}
/* Paragraph */
diff --git a/utils/markdown-test/MdLink.jsx b/utils/markdown-test/MdLink.jsx
deleted file mode 100644
index f9af760..0000000
--- a/utils/markdown-test/MdLink.jsx
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Renders a markdown link
- * Also provides previews
- * Links are assumed to start with protocol (http:// etc.)
- */
-import React from 'react';
-
-/*
- * gets a descriptive text of the domain of the link
- * Example:
- * https://www.youtube.com/watch?v=G8APgeFfkAk returns 'youtube'
- * http://www.google.at returns 'google.at'
- * (www. and .com are split)
- */
-function getLinkDesc(link) {
- let domainStart = link.indexOf('://') + 3;
- if (domainStart < 3) {
- domainStart = 0;
- }
- if (link.startsWith('www.', domainStart)) {
- domainStart += 4;
- }
- let domainEnd = link.indexOf('/', domainStart);
- if (domainEnd === -1) {
- domainEnd = link.length;
- }
- if (link.endsWith('.com', domainEnd)) {
- domainEnd -= 4;
- }
- if (domainEnd <= domainStart) {
- return link;
- }
- return link.slice(domainStart, domainEnd);
-}
-
-/*
- * try to get extension out of link
- */
-function getExt(link) {
- let paramStart = link.indexOf('&');
- if (paramStart === -1) {
- paramStart = link.length;
- }
- let posDot = paramStart - 1;
- for (;posDot >= 0 && link[posDot] !== '.'; posDot -= 1) {
- if (link[posDot] === '/') {
- return null;
- }
- }
- if (paramStart - posDot > 4) {
- return null;
- }
- return link.slice(posDot, paramStart);
-}
-
-const MdLink = ({ href, title, type }) => {
- const desc = getLinkDesc(href);
-
-};
-
-export default Reace.memo(MdLink);
diff --git a/utils/markdown-test/MdMention.jsx b/utils/markdown-test/MdMention.jsx
new file mode 100644
index 0000000..4d55885
--- /dev/null
+++ b/utils/markdown-test/MdMention.jsx
@@ -0,0 +1,24 @@
+/*
+ * Parse Mention of Username
+ */
+import React from 'react';
+
+import { colorFromText, setBrightness } from '../../src/core/utils';
+
+const dark = false;
+const ownid = 1;
+
+const MdMention = ({ name, uid }) => {
+ const id = uid && uid.trim();
+
+ return (
+ {name}
+ );
+}
+
+export default React.memo(MdMention);
diff --git a/utils/markdown-test/index.css b/utils/markdown-test/index.css
index 2f0fe7d..14166a2 100644
--- a/utils/markdown-test/index.css
+++ b/utils/markdown-test/index.css
@@ -23,3 +23,18 @@ pre {
.rt {
color: red;
}
+
+.vemb {
+ overflow: hidden;
+ position: relative;
+ height: 0;
+ padding-bottom: 66%;
+}
+
+.vembc {
+ left: 0;
+ top: 0;
+ height: 100%;
+ width: 100%;
+ position: absolute;
+}
diff --git a/utils/markdown-test/index.html b/utils/markdown-test/index.html
index 29c5dd1..c0e1392 100644
--- a/utils/markdown-test/index.html
+++ b/utils/markdown-test/index.html
@@ -7,6 +7,7 @@
+
diff --git a/utils/markdown-test/webpack.config.js b/utils/markdown-test/webpack.config.js
index 0092ae7..5104847 100644
--- a/utils/markdown-test/webpack.config.js
+++ b/utils/markdown-test/webpack.config.js
@@ -1,24 +1,6 @@
var path = require('path');
var webpack = require('webpack');
-var babelPlugins = [
- '@babel/plugin-transform-flow-strip-types',
- ['@babel/plugin-proposal-decorators', { legacy: true }],
- '@babel/plugin-proposal-function-sent',
- '@babel/plugin-proposal-export-namespace-from',
- '@babel/plugin-proposal-numeric-separator',
- '@babel/plugin-proposal-throw-expressions',
- ['@babel/plugin-proposal-class-properties', { loose: true }],
- ['@babel/plugin-proposal-private-methods', { loose: true }],
- ["@babel/plugin-proposal-private-property-in-object", { "loose": true }],
- '@babel/proposal-object-rest-spread',
- // react-optimize
- '@babel/transform-react-constant-elements',
- '@babel/transform-react-inline-elements',
- 'transform-react-remove-prop-types',
- 'transform-react-pure-class-to-function',
-];
-
module.exports = {
name: 'script',
target: 'web',
@@ -41,16 +23,7 @@ module.exports = {
test: /\.(js|jsx)$/,
loader: 'babel-loader',
options: {
- presets: [
- ['@babel/preset-env', {
- targets: {
- browsers: [ 'defaults' ],
- },
- modules: false,
- }],
- '@babel/react',
- ],
- plugins: babelPlugins,
+ rootMode: 'upward-optional',
},
},
],
diff --git a/webpack.config.client.babel.js b/webpack.config.client.babel.js
index 58a7f1b..67d3d0d 100644
--- a/webpack.config.client.babel.js
+++ b/webpack.config.client.babel.js
@@ -7,8 +7,6 @@ import webpack from 'webpack';
import AssetsPlugin from 'assets-webpack-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
-import pkg from './package.json';
-
/*
* Emit a file with assets paths
*/
@@ -120,13 +118,6 @@ export function buildWebpackClientConfig(
))
],
options: {
- presets: [
- ['@babel/preset-env', {
- targets: {
- browsers: pkg.browserslist,
- },
- }],
- ],
plugins: babelPlugins,
},
},
diff --git a/webpack.config.server.babel.js b/webpack.config.server.babel.js
index c3434a2..3ee18dc 100644
--- a/webpack.config.server.babel.js
+++ b/webpack.config.server.babel.js
@@ -75,14 +75,6 @@ export default ({
],
options: {
cacheDirectory: false,
- presets: [
- ['@babel/preset-env', {
- targets: {
- node: pkg.engines.node.replace(/^\D+/g, ''),
- },
- modules: false,
- }],
- ],
plugins: babelPlugins,
},
},