expand Markdown parsing, add embeds
This commit is contained in:
parent
3eeece6a54
commit
e80f9b4447
|
@ -1,5 +1,6 @@
|
||||||
|
const pkg = require('./package.json');
|
||||||
|
|
||||||
module.exports = function (api) {
|
module.exports = function (api) {
|
||||||
api.cache(true);
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
'@babel/plugin-transform-flow-strip-types',
|
'@babel/plugin-transform-flow-strip-types',
|
||||||
'@babel/plugin-proposal-throw-expressions',
|
'@babel/plugin-proposal-throw-expressions',
|
||||||
|
@ -13,11 +14,18 @@ module.exports = function (api) {
|
||||||
const presets = [
|
const presets = [
|
||||||
[
|
[
|
||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
{
|
api.caller(caller => caller && caller.target === "node")
|
||||||
"targets": {
|
? {
|
||||||
"node": "current"
|
targets: {
|
||||||
|
node: pkg.engines.node.replace(/^\D+/g, ''),
|
||||||
|
},
|
||||||
|
modules: false,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
targets: {
|
||||||
|
browsers: pkg.browserslist,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
],
|
],
|
||||||
'@babel/react',
|
'@babel/react',
|
||||||
];
|
];
|
||||||
|
|
|
@ -83,6 +83,14 @@ msgid ""
|
||||||
"one (Note: you can use those links just once)"
|
"one (Note: you can use those links just once)"
|
||||||
msgstr ""
|
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
|
#: src/ssr-components/Globe.jsx:44
|
||||||
msgid "Double click on globe to go back."
|
msgid "Double click on globe to go back."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -99,14 +107,6 @@ msgstr ""
|
||||||
msgid "A 3D globe of our whole map"
|
msgid "A 3D globe of our whole map"
|
||||||
msgstr ""
|
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
|
#: src/core/mail.js:65
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -305,6 +305,21 @@ msgstr ""
|
||||||
msgid "Password must be shorter than 60 characters."
|
msgid "Password must be shorter than 60 characters."
|
||||||
msgstr ""
|
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
|
#: src/routes/api/auth/register.js:33
|
||||||
msgid "No Captcha given"
|
msgid "No Captcha given"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -325,21 +340,6 @@ msgstr ""
|
||||||
msgid "Failed to establish session after register :("
|
msgid "Failed to establish session after register :("
|
||||||
msgstr ""
|
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
|
#: src/routes/api/auth/logout.js:13
|
||||||
msgid "You are not even logged in."
|
msgid "You are not even logged in."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
|
@ -3,48 +3,6 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n!=1);\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
|
#: src/ui/placePixel.js:53
|
||||||
msgid "Error :("
|
msgid "Error :("
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -146,6 +104,48 @@ msgstr ""
|
||||||
msgid "Error ${ retCode }"
|
msgid "Error ${ retCode }"
|
||||||
msgstr ""
|
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
|
#: src/ui/renderer.js:36
|
||||||
msgid "Canvas Error"
|
msgid "Canvas Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -170,11 +170,6 @@ msgstr ""
|
||||||
msgid "Look at past Canvases"
|
msgid "Look at past Canvases"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Converter.jsx:559
|
|
||||||
#: src/components/CoordinatesBox.jsx:32
|
|
||||||
msgid "Copy to Clipboard"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/components/OnlineBox.jsx:41
|
#: src/components/OnlineBox.jsx:41
|
||||||
msgid "Online Users on Canvas"
|
msgid "Online Users on Canvas"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -187,6 +182,11 @@ msgstr ""
|
||||||
msgid "Pixels placed"
|
msgid "Pixels placed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/Converter.jsx:559
|
||||||
|
#: src/components/CoordinatesBox.jsx:32
|
||||||
|
msgid "Copy to Clipboard"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ModalRoot.jsx:69
|
#: src/components/ModalRoot.jsx:69
|
||||||
#: src/components/Modtools.jsx:224
|
#: src/components/Modtools.jsx:224
|
||||||
#: src/components/Window.jsx:138
|
#: src/components/Window.jsx:138
|
||||||
|
@ -198,6 +198,27 @@ msgstr ""
|
||||||
msgid "Restore"
|
msgid "Restore"
|
||||||
msgstr ""
|
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
|
#: src/actions/fetch.js:39
|
||||||
msgid "You made too many requests"
|
msgid "You made too many requests"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -227,25 +248,8 @@ msgstr ""
|
||||||
msgid "Server answered with gibberish :("
|
msgid "Server answered with gibberish :("
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/buttons/CanvasSwitchButton.jsx:22
|
#: src/components/buttons/GlobeButton.jsx:35
|
||||||
#: src/components/windows/index.js:22
|
msgid "Globe View"
|
||||||
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"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/HistorySelect.jsx:144
|
#: src/components/HistorySelect.jsx:144
|
||||||
|
@ -256,6 +260,33 @@ msgstr ""
|
||||||
msgid "Select Date above"
|
msgid "Select Date above"
|
||||||
msgstr ""
|
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
|
#: src/components/Window.jsx:117
|
||||||
msgid "Clone"
|
msgid "Clone"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -272,37 +303,6 @@ msgstr ""
|
||||||
msgid "Resize"
|
msgid "Resize"
|
||||||
msgstr ""
|
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
|
#: src/components/contextmenus/UserContextMenu.jsx:53
|
||||||
msgid "Ping"
|
msgid "Ping"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -319,19 +319,19 @@ msgstr ""
|
||||||
msgid "Mute"
|
msgid "Mute"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/windows/index.js:19
|
#: src/components/windows/index.js:16
|
||||||
msgid "Registration"
|
msgid "Registration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/windows/index.js:20
|
#: src/components/windows/index.js:17
|
||||||
msgid "Forgot Password"
|
msgid "Forgot Password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/windows/index.js:21
|
#: src/components/windows/index.js:18
|
||||||
msgid "Chat"
|
msgid "Chat"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/windows/index.js:23
|
#: src/components/windows/index.js:20
|
||||||
msgid "Canvas Archive"
|
msgid "Canvas Archive"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -538,30 +538,6 @@ msgstr ""
|
||||||
msgid "Credit for the Palette of the Top10 canvas goes to ${ vinikLink }."
|
msgid "Credit for the Palette of the Top10 canvas goes to ${ vinikLink }."
|
||||||
msgstr ""
|
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
|
#: src/components/windows/Settings.jsx:133
|
||||||
msgid "Show Grid"
|
msgid "Show Grid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -655,48 +631,6 @@ msgstr ""
|
||||||
msgid "Select Language"
|
msgid "Select Language"
|
||||||
msgstr ""
|
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
|
#: src/components/windows/CanvasSelect.jsx:33
|
||||||
msgid ""
|
msgid ""
|
||||||
"Select the canvas you want to use. Every canvas is unique and has "
|
"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:"
|
msgid "Enter your mail address and we will send you a new password:"
|
||||||
msgstr ""
|
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
|
#: src/components/windows/Chat.jsx:146
|
||||||
msgid "Channel settings"
|
msgid "Channel settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -765,6 +715,56 @@ msgstr ""
|
||||||
msgid "You must be logged in to chat"
|
msgid "You must be logged in to chat"
|
||||||
msgstr ""
|
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:50
|
||||||
#: src/components/Captcha.jsx:105
|
#: src/components/Captcha.jsx:105
|
||||||
msgid "Could not load captcha"
|
msgid "Could not load captcha"
|
||||||
|
@ -846,28 +846,56 @@ msgstr ""
|
||||||
msgid "Password must be shorter than 60 characters."
|
msgid "Password must be shorter than 60 characters."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/LogInArea.jsx:21
|
#: src/components/ChangeMail.jsx:91
|
||||||
msgid "Login to access more features and stats."
|
#: src/components/ChangeName.jsx:68
|
||||||
|
#: src/components/ChangePassword.jsx:110
|
||||||
|
#: src/components/LanguageSelect.jsx:73
|
||||||
|
msgid "Save"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/LogInArea.jsx:23
|
#: src/components/CanvasItem.jsx:30
|
||||||
msgid "Login with Name or Mail:"
|
msgid "Online Users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/LogInArea.jsx:30
|
#: src/components/CanvasItem.jsx:35
|
||||||
msgid "I forgot my Password."
|
msgid "Cooldown"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/LogInArea.jsx:31
|
#: src/components/CanvasItem.jsx:41
|
||||||
msgid "or login with:"
|
msgid "Stacking till"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/LogInArea.jsx:72
|
#: src/components/CanvasItem.jsx:43
|
||||||
msgid "or register here:"
|
msgid "Ranked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/LogInArea.jsx:79
|
#: src/components/CanvasItem.jsx:45
|
||||||
msgid "Register"
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/UserAreaContent.jsx:63
|
#: src/components/UserAreaContent.jsx:63
|
||||||
|
@ -1083,64 +1111,28 @@ msgstr ""
|
||||||
msgid "Download Template"
|
msgid "Download Template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ChangeMail.jsx:91
|
#: src/components/LogInArea.jsx:21
|
||||||
#: src/components/ChangeName.jsx:68
|
msgid "Login to access more features and stats."
|
||||||
#: src/components/ChangePassword.jsx:110
|
|
||||||
#: src/components/LanguageSelect.jsx:73
|
|
||||||
msgid "Save"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/CanvasItem.jsx:30
|
#: src/components/LogInArea.jsx:23
|
||||||
msgid "Online Users"
|
msgid "Login with Name or Mail:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/CanvasItem.jsx:35
|
#: src/components/LogInArea.jsx:30
|
||||||
msgid "Cooldown"
|
msgid "I forgot my Password."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/CanvasItem.jsx:41
|
#: src/components/LogInArea.jsx:31
|
||||||
msgid "Stacking till"
|
msgid "or login with:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/CanvasItem.jsx:43
|
#: src/components/LogInArea.jsx:72
|
||||||
msgid "Ranked"
|
msgid "or register here:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/CanvasItem.jsx:45
|
#: src/components/LogInArea.jsx:79
|
||||||
msgid "Yes"
|
msgid "Register"
|
||||||
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"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/UserMessages.jsx:28
|
#: src/components/UserMessages.jsx:28
|
||||||
|
@ -1177,6 +1169,10 @@ msgstr ""
|
||||||
msgid "Confirm New Password"
|
msgid "Confirm New Password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/ChangeName.jsx:64
|
||||||
|
msgid "New Username"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ChangeMail.jsx:59
|
#: src/components/ChangeMail.jsx:59
|
||||||
msgid ""
|
msgid ""
|
||||||
"Changed Mail successfully. We sent you a verification mail, "
|
"Changed Mail successfully. We sent you a verification mail, "
|
||||||
|
@ -1187,14 +1183,6 @@ msgstr ""
|
||||||
msgid "New Mail"
|
msgid "New Mail"
|
||||||
msgstr ""
|
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
|
#: src/components/SocialSettings.jsx:38
|
||||||
msgid "Block all Private Messages"
|
msgid "Block all Private Messages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -1207,6 +1195,18 @@ msgstr ""
|
||||||
msgid "You have no users blocked"
|
msgid "You have no users blocked"
|
||||||
msgstr ""
|
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/Help.jsx:14
|
||||||
#: src/components/windows/Settings.jsx:134
|
#: src/components/windows/Settings.jsx:134
|
||||||
msgctxt "keybinds"
|
msgctxt "keybinds"
|
||||||
|
|
BIN
public/embico/direct.png
Normal file
BIN
public/embico/direct.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 474 B |
BIN
public/embico/matrix.png
Normal file
BIN
public/embico/matrix.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 359 B |
BIN
public/embico/tiktok.png
Normal file
BIN
public/embico/tiktok.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 575 B |
BIN
public/embico/youtube.png
Normal file
BIN
public/embico/youtube.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 240 B |
|
@ -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) {
|
export function closeWindow(windowId) {
|
||||||
return {
|
return {
|
||||||
type: 'CLOSE_WINDOW',
|
type: 'CLOSE_WINDOW',
|
||||||
|
|
|
@ -3,25 +3,29 @@
|
||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import { showContextMenu } from '../actions';
|
import { showContextMenu } from '../actions';
|
||||||
|
import { MarkdownParagraph } from './Markdown';
|
||||||
import { colorFromText, setBrightness } from '../core/utils';
|
import { colorFromText, setBrightness } from '../core/utils';
|
||||||
|
import { parseParagraph } from '../core/MarkdownParser';
|
||||||
|
|
||||||
|
|
||||||
function ChatMessage({
|
function ChatMessage({
|
||||||
name,
|
name,
|
||||||
uid,
|
uid,
|
||||||
country,
|
country,
|
||||||
dark,
|
|
||||||
windowId,
|
windowId,
|
||||||
msgArray,
|
msg,
|
||||||
}) {
|
}) {
|
||||||
if (!name || !msgArray) {
|
if (!name) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const isDarkMode = useSelector(
|
||||||
|
(state) => state.gui.style.indexOf('dark') !== -1,
|
||||||
|
);
|
||||||
|
|
||||||
const isInfo = (name === 'info');
|
const isInfo = (name === 'info');
|
||||||
const isEvent = (name === 'event');
|
const isEvent = (name === 'event');
|
||||||
|
@ -30,90 +34,61 @@ function ChatMessage({
|
||||||
className += ' info';
|
className += ' info';
|
||||||
} else if (isEvent) {
|
} else if (isEvent) {
|
||||||
className += ' event';
|
className += ' event';
|
||||||
} else if (msgArray[0][1].charAt(0) === '>') {
|
} else if (msg.charAt(0) === '>') {
|
||||||
className += ' greentext';
|
className += ' greentext';
|
||||||
|
} else if (msg.charAt(0) === '<') {
|
||||||
|
className += ' redtext';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pArray = parseParagraph(msg);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p className="chatmsg">
|
<div className="chatmsg">
|
||||||
{
|
<div className="chatname">
|
||||||
|
{
|
||||||
(!isInfo && !isEvent)
|
(!isInfo && !isEvent)
|
||||||
&& (
|
&& (
|
||||||
<span>
|
<>
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
title={country}
|
title={country}
|
||||||
src={`${window.ssv.assetserver}/cf/${country}.gif`}
|
src={`${window.ssv.assetserver}/cf/${country}.gif`}
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
e.target.onerror = null;
|
e.target.onerror = null;
|
||||||
e.target.src = './cf/xx.gif';
|
e.target.src = './cf/xx.gif';
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
className="chatname"
|
style={{
|
||||||
style={{
|
color: setBrightness(colorFromText(name), isDarkMode),
|
||||||
color: setBrightness(colorFromText(name), dark),
|
cursor: 'pointer',
|
||||||
cursor: 'pointer',
|
}}
|
||||||
}}
|
role="button"
|
||||||
role="button"
|
tabIndex={-1}
|
||||||
tabIndex={-1}
|
onClick={(event) => {
|
||||||
onClick={(event) => {
|
const {
|
||||||
const {
|
clientX,
|
||||||
clientX,
|
clientY,
|
||||||
clientY,
|
} = event;
|
||||||
} = event;
|
dispatch(showContextMenu('USER', clientX, clientY, {
|
||||||
dispatch(showContextMenu('USER', clientX, clientY, {
|
windowId,
|
||||||
windowId,
|
uid,
|
||||||
uid,
|
name,
|
||||||
name,
|
}));
|
||||||
}));
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{name}
|
||||||
{name}
|
</span>
|
||||||
</span>
|
:
|
||||||
:
|
</>
|
||||||
</span>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
</div>
|
||||||
msgArray.map((msgPart) => {
|
<span className={className}>
|
||||||
const [type, txt] = msgPart;
|
<MarkdownParagraph pArray={pArray} />
|
||||||
if (type === 't') {
|
</span>
|
||||||
return (<span className={className}>{txt}</span>);
|
</div>
|
||||||
} if (type === 'c') {
|
|
||||||
return (<a href={`./${txt}`}>{txt}</a>);
|
|
||||||
} if (type === 'l') {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href={txt}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>{txt}</a>
|
|
||||||
);
|
|
||||||
} if (type === 'p') {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className="ping"
|
|
||||||
style={{
|
|
||||||
color: setBrightness(colorFromText(txt.substring(1)), dark),
|
|
||||||
}}
|
|
||||||
>{txt}</span>
|
|
||||||
);
|
|
||||||
} if (type === 'm') {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className="mention"
|
|
||||||
style={{
|
|
||||||
color: setBrightness(colorFromText(txt.substring(1)), dark),
|
|
||||||
}}
|
|
||||||
>{txt}</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
148
src/components/Markdown.jsx
Normal file
148
src/components/Markdown.jsx
Normal file
|
@ -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 (<code>{part[1]}</code>);
|
||||||
|
case '*':
|
||||||
|
return (
|
||||||
|
<strong>
|
||||||
|
<MarkdownParagraph pArray={part[1]} />
|
||||||
|
</strong>
|
||||||
|
);
|
||||||
|
case '~':
|
||||||
|
return (
|
||||||
|
<s>
|
||||||
|
<MarkdownParagraph pArray={part[1]} />
|
||||||
|
</s>
|
||||||
|
);
|
||||||
|
case '+':
|
||||||
|
return (
|
||||||
|
<em>
|
||||||
|
<MarkdownParagraph pArray={part[1]} />
|
||||||
|
</em>
|
||||||
|
);
|
||||||
|
case '_':
|
||||||
|
return (
|
||||||
|
<u>
|
||||||
|
<MarkdownParagraph pArray={part[1]} />
|
||||||
|
</u>
|
||||||
|
);
|
||||||
|
case 'img':
|
||||||
|
case 'l': {
|
||||||
|
return (
|
||||||
|
<MdLink href={part[2]} title={part[1]} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case '@': {
|
||||||
|
return (
|
||||||
|
<MdMention uid={part[2]} name={part[1]} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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 = <h1>{heading}</h1>;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
headingElem = <h2>{heading}</h2>;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
headingElem = <h3>{heading}</h3>;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
headingElem = <h4>{heading}</h4>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{headingElem}
|
||||||
|
<section>
|
||||||
|
<Markdown mdArray={children} />
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* Paragraph */
|
||||||
|
case 'p': {
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
<MarkdownParagraph pArray={part[1]} />
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* Code Block */
|
||||||
|
case 'cb': {
|
||||||
|
const content = part[1];
|
||||||
|
return <pre>{content}</pre>;
|
||||||
|
}
|
||||||
|
case '>':
|
||||||
|
case '<': {
|
||||||
|
const children = part[1];
|
||||||
|
return (
|
||||||
|
<blockquote
|
||||||
|
className={(type === '>') ? 'gt' : 'rt'}
|
||||||
|
>
|
||||||
|
<Markdown mdArray={children} />
|
||||||
|
</blockquote>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case 'ul': {
|
||||||
|
const children = part[1];
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
<Markdown mdArray={children} />
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case 'ol': {
|
||||||
|
const children = part[1];
|
||||||
|
return (
|
||||||
|
<ol>
|
||||||
|
<Markdown mdArray={children} />
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case '-': {
|
||||||
|
const children = part[1];
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<Markdown mdArray={children} />
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return part[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const MarkdownArticle = ({ mdArray }) => (
|
||||||
|
<article>
|
||||||
|
<Markdown mdArray={mdArray} />
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default React.memo(MarkdownArticle);
|
94
src/components/MdLink.jsx
Normal file
94
src/components/MdLink.jsx
Normal file
|
@ -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 (
|
||||||
|
<a href={`./${coords}`}>{title || coords}</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{parsedTitle}
|
||||||
|
</a>
|
||||||
|
{(embedAvailable) && (
|
||||||
|
<>
|
||||||
|
|
||||||
|
{(embedObj[3])
|
||||||
|
&& (
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
width: '1em',
|
||||||
|
height: '1em',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
}}
|
||||||
|
src={embedObj[3]}
|
||||||
|
alt={`${desc}-icon`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => setShowEmbed(!showEmbed)}
|
||||||
|
>
|
||||||
|
{(showEmbed)
|
||||||
|
? (
|
||||||
|
<HiStop
|
||||||
|
style={{ verticalAlign: 'middle', color: 'red' }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<HiArrowsExpand
|
||||||
|
style={{ verticalAlign: 'middle', color: '#4646ff' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{(showEmbed && embedAvailable) && <Embed url={href} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(MdLink);
|
30
src/components/MdMention.jsx
Normal file
30
src/components/MdMention.jsx
Normal file
|
@ -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 (
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
// eslint-disable-next-line eqeqeq
|
||||||
|
(id == ownId) ? 'ping' : 'mention'
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
color: setBrightness(colorFromText(name), isDarkMode),
|
||||||
|
}}
|
||||||
|
>{`@${name}`}</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(MdMention);
|
|
@ -12,7 +12,7 @@ import {
|
||||||
} from '../hooks/clickOutside';
|
} from '../hooks/clickOutside';
|
||||||
import {
|
import {
|
||||||
hideContextMenu,
|
hideContextMenu,
|
||||||
addToChatInputMessage,
|
addToChatInputMessageAndFocus,
|
||||||
startDm,
|
startDm,
|
||||||
setUserBlock,
|
setUserBlock,
|
||||||
setChatChannel,
|
setChatChannel,
|
||||||
|
@ -45,7 +45,9 @@ const UserContextMenu = () => {
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(addToChatInputMessage(windowId, `@${name} `));
|
dispatch(
|
||||||
|
addToChatInputMessageAndFocus(windowId, `@[${name}](${uid}) `),
|
||||||
|
);
|
||||||
close();
|
close();
|
||||||
}}
|
}}
|
||||||
style={{ borderTop: 'none' }}
|
style={{ borderTop: 'none' }}
|
||||||
|
|
52
src/components/embeds/DirectLinkMedia.jsx
Normal file
52
src/components/embeds/DirectLinkMedia.jsx
Normal file
|
@ -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 (
|
||||||
|
<div className="vemb">
|
||||||
|
<video
|
||||||
|
className="vembc"
|
||||||
|
controls
|
||||||
|
autoPlay
|
||||||
|
src={url}
|
||||||
|
referrerPolicy="no-referrer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
alt={`Matrix ${url}`}
|
||||||
|
src={url}
|
||||||
|
style={{ maxWidth: '100%' }}
|
||||||
|
referrerPolicy="no-referrer"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
React.memo(DirectLinkMedia),
|
||||||
|
(url) => {
|
||||||
|
const ext = getExt(url);
|
||||||
|
return (videoExts.includes(ext) || imageExts.includes(ext));
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
`${window.ssv.assetserver}/embico/direct.png`,
|
||||||
|
];
|
33
src/components/embeds/Matrix.jsx
Normal file
33
src/components/embeds/Matrix.jsx
Normal file
|
@ -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 (
|
||||||
|
<div className="vemb">
|
||||||
|
<video
|
||||||
|
className="vembc"
|
||||||
|
controls
|
||||||
|
autoPlay
|
||||||
|
src={cleanUrl}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
alt={`Matrix ${cleanUrl}`}
|
||||||
|
src={cleanUrl}
|
||||||
|
style={{ maxWidth: '100%' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
React.memo(Matrix),
|
||||||
|
(url) => url.includes('?type=video') || url.includes('?type=image'),
|
||||||
|
null,
|
||||||
|
`${window.ssv.assetserver}/embico/matrix.png`,
|
||||||
|
];
|
59
src/components/embeds/TikTok.jsx
Normal file
59
src/components/embeds/TikTok.jsx
Normal file
|
@ -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 <div>LOADING</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: 756,
|
||||||
|
}}
|
||||||
|
srcDoc={embedCode}
|
||||||
|
frameBorder="0"
|
||||||
|
referrerPolicy="no-referrer"
|
||||||
|
allow="autoplay; picture-in-picture"
|
||||||
|
scrolling="no"
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
sandbox="allow-scripts allow-modals allow-forms allow-popups allow-same-origin allow-presentation"
|
||||||
|
allowFullScreen
|
||||||
|
title="Embedded youtube"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
React.memo(TikTok),
|
||||||
|
(url) => url.includes('/video/'),
|
||||||
|
(url) => getUserFromUrl(url),
|
||||||
|
`${window.ssv.assetserver}/embico/tiktok.png`,
|
||||||
|
];
|
49
src/components/embeds/YouTube.jsx
Normal file
49
src/components/embeds/YouTube.jsx
Normal file
|
@ -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 (
|
||||||
|
<div className="vemb" style={{ paddingBottom: '56.25%' }}>
|
||||||
|
<iframe
|
||||||
|
className="vembc"
|
||||||
|
src={`https://www.youtube.com/embed/${id}`}
|
||||||
|
frameBorder="0"
|
||||||
|
referrerPolicy="no-referrer"
|
||||||
|
allow="autoplay; picture-in-picture"
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
sandbox="allow-scripts allow-modals allow-forms allow-popups allow-same-origin allow-presentation"
|
||||||
|
allowFullScreen
|
||||||
|
title="Embedded youtube"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
React.memo(YouTube),
|
||||||
|
getIdFromURL,
|
||||||
|
(url) => getIdFromURL(url),
|
||||||
|
`${window.ssv.assetserver}/embico/youtube.png`,
|
||||||
|
];
|
28
src/components/embeds/index.js
Normal file
28
src/components/embeds/index.js
Normal file
|
@ -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,
|
||||||
|
};
|
|
@ -22,11 +22,7 @@ import {
|
||||||
setWindowTitle,
|
setWindowTitle,
|
||||||
} from '../../actions';
|
} from '../../actions';
|
||||||
import SocketClient from '../../socket/SocketClient';
|
import SocketClient from '../../socket/SocketClient';
|
||||||
import splitChatMessage from '../../core/chatMessageFilter';
|
|
||||||
|
|
||||||
function escapeRegExp(string) {
|
|
||||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
||||||
}
|
|
||||||
|
|
||||||
const Chat = ({
|
const Chat = ({
|
||||||
windowId,
|
windowId,
|
||||||
|
@ -34,7 +30,6 @@ const Chat = ({
|
||||||
const listRef = useRef();
|
const listRef = useRef();
|
||||||
const targetRef = useRef();
|
const targetRef = useRef();
|
||||||
|
|
||||||
const [nameRegExp, setNameRegExp] = useState(null);
|
|
||||||
const [blockedIds, setBlockedIds] = useState([]);
|
const [blockedIds, setBlockedIds] = useState([]);
|
||||||
const [btnSize, setBtnSize] = useState(20);
|
const [btnSize, setBtnSize] = useState(20);
|
||||||
|
|
||||||
|
@ -46,7 +41,6 @@ const Chat = ({
|
||||||
|
|
||||||
const ownName = useSelector((state) => state.user.name);
|
const ownName = useSelector((state) => state.user.name);
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
const isDarkMode = useSelector((state) => state.gui.style.indexOf('dark') !== -1);
|
|
||||||
const fetching = useSelector((state) => state.fetching.fetchingChat);
|
const fetching = useSelector((state) => state.fetching.fetchingChat);
|
||||||
const { channels, messages, blocked } = useSelector((state) => state.chat);
|
const { channels, messages, blocked } = useSelector((state) => state.chat);
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
|
@ -73,13 +67,6 @@ const Chat = ({
|
||||||
stayScrolled();
|
stayScrolled();
|
||||||
}, [channelMessages.length]);
|
}, [channelMessages.length]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const regExp = (ownName)
|
|
||||||
? new RegExp(`(^|\\s)(@${escapeRegExp(ownName)})(\\s|$)`, 'g')
|
|
||||||
: null;
|
|
||||||
setNameRegExp(regExp);
|
|
||||||
}, [ownName]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const fontSize = Math.round(targetRef.current.offsetHeight / 10);
|
const fontSize = Math.round(targetRef.current.offsetHeight / 10);
|
||||||
|
@ -97,10 +84,10 @@ const Chat = ({
|
||||||
|
|
||||||
function handleSubmit(e) {
|
function handleSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const msg = inputMessage.trim();
|
const inptMsg = inputMessage.trim();
|
||||||
if (!msg) return;
|
if (!inptMsg) return;
|
||||||
// send message via websocket
|
// send message via websocket
|
||||||
SocketClient.sendChatMessage(msg, chatChannel);
|
SocketClient.sendChatMessage(inptMsg, chatChannel);
|
||||||
dispatch(setChatInputMessage(windowId, ''));
|
dispatch(setChatInputMessage(windowId, ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,11 +144,10 @@ const Chat = ({
|
||||||
(!channelMessages.length)
|
(!channelMessages.length)
|
||||||
&& (
|
&& (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
name="info"
|
|
||||||
msgArray={splitChatMessage(t`Start chatting here`, nameRegExp)}
|
|
||||||
country="xx"
|
|
||||||
uid={0}
|
uid={0}
|
||||||
dark={isDarkMode}
|
name="info"
|
||||||
|
country="xx"
|
||||||
|
msg={t`Start chatting here`}
|
||||||
windowId={windowId}
|
windowId={windowId}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -170,11 +156,10 @@ const Chat = ({
|
||||||
channelMessages.map((message) => ((blockedIds.includes(message[3]))
|
channelMessages.map((message) => ((blockedIds.includes(message[3]))
|
||||||
? null : (
|
? null : (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
name={message[0]}
|
|
||||||
msgArray={splitChatMessage(message[1], nameRegExp)}
|
|
||||||
country={message[2]}
|
|
||||||
uid={message[3]}
|
uid={message[3]}
|
||||||
dark={isDarkMode}
|
name={message[0]}
|
||||||
|
country={message[2]}
|
||||||
|
msg={message[1]}
|
||||||
windowId={windowId}
|
windowId={windowId}
|
||||||
/>
|
/>
|
||||||
)))
|
)))
|
||||||
|
@ -188,6 +173,7 @@ const Chat = ({
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
style={{ flexGrow: 1, minWidth: 40 }}
|
style={{ flexGrow: 1, minWidth: 40 }}
|
||||||
|
id={`chtipt-${windowId}`}
|
||||||
value={inputMessage}
|
value={inputMessage}
|
||||||
onChange={(e) => dispatch(
|
onChange={(e) => dispatch(
|
||||||
setChatInputMessage(windowId, e.target.value),
|
setChatInputMessage(windowId, e.target.value),
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
/*
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
import { t } from 'ttag';
|
import { t } from 'ttag';
|
||||||
|
|
||||||
import Help from './Help';
|
import Help from './Help';
|
||||||
|
|
|
@ -20,6 +20,14 @@ import {
|
||||||
APISOCKET_USER_NAME,
|
APISOCKET_USER_NAME,
|
||||||
} from './constants';
|
} 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 {
|
export class ChatProvider {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.defaultChannels = {};
|
this.defaultChannels = {};
|
||||||
|
@ -262,21 +270,30 @@ export class ChatProvider {
|
||||||
case 'mute': {
|
case 'mute': {
|
||||||
const timeMin = Number(args.slice(-1));
|
const timeMin = Number(args.slice(-1));
|
||||||
if (Number.isNaN(timeMin)) {
|
if (Number.isNaN(timeMin)) {
|
||||||
return this.mute(args.join(' '), channelId);
|
return this.mute(
|
||||||
|
getUserFromMd(args.join(' ')),
|
||||||
|
channelId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return this.mute(
|
return this.mute(
|
||||||
args.slice(0, -1).join(' '),
|
getUserFromMd(args.slice(0, -1).join(' ')),
|
||||||
channelId,
|
channelId,
|
||||||
timeMin,
|
timeMin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'unmute':
|
case 'unmute':
|
||||||
return this.unmute(args.join(' '), channelId);
|
return this.unmute(
|
||||||
|
getUserFromMd(args.join(' ')),
|
||||||
|
channelId,
|
||||||
|
);
|
||||||
|
|
||||||
case 'mutec': {
|
case 'mutec': {
|
||||||
if (args[0]) {
|
if (args[0]) {
|
||||||
const cc = args[0].toLowerCase();
|
const cc = args[0].toLowerCase();
|
||||||
|
if (cc.length > 3) {
|
||||||
|
return 'No legit country defined';
|
||||||
|
}
|
||||||
this.mutedCountries.push(cc);
|
this.mutedCountries.push(cc);
|
||||||
this.broadcastChatMessage(
|
this.broadcastChatMessage(
|
||||||
'info',
|
'info',
|
||||||
|
|
|
@ -212,4 +212,56 @@ export default class MString {
|
||||||
this.iter = cIter;
|
this.iter = cIter;
|
||||||
return link;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
* stuff like pixelplanet coords and usernames and bare links.
|
* stuff like pixelplanet coords and usernames and bare links.
|
||||||
* This code is written in preparation for a possible imporementation in
|
* This code is written in preparation for a possible imporementation in
|
||||||
* WebAssambly, so it's all in a big loop
|
* WebAssambly, so it's all in a big loop
|
||||||
*
|
|
||||||
* @flow
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import MString from './MString';
|
import MString from './MString';
|
||||||
|
@ -41,6 +39,19 @@ function parseMParagraph(text, opts, breakChar) {
|
||||||
}
|
}
|
||||||
pStart = text.iter + 1;
|
pStart = text.iter + 1;
|
||||||
text.moveForward();
|
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)) {
|
} else if (paraElems.includes(chr)) {
|
||||||
/*
|
/*
|
||||||
* bold, cursive, underline, etc.
|
* bold, cursive, underline, etc.
|
||||||
|
@ -100,10 +111,14 @@ function parseMParagraph(text, opts, breakChar) {
|
||||||
* defaults to ordinary link
|
* defaults to ordinary link
|
||||||
*/
|
*/
|
||||||
let tag = 'l';
|
let tag = 'l';
|
||||||
const zIsLink = true;
|
let zIsLink = true;
|
||||||
if (x === '!') {
|
if (x === '!') {
|
||||||
tag = 'img';
|
tag = 'img';
|
||||||
oldPos -= 1;
|
oldPos -= 1;
|
||||||
|
} else if (x === '@') {
|
||||||
|
zIsLink = false;
|
||||||
|
tag = '@';
|
||||||
|
oldPos -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const encArr = text.checkIfEnclosure(zIsLink);
|
const encArr = text.checkIfEnclosure(zIsLink);
|
||||||
|
@ -174,8 +189,8 @@ function parseQuote(text, opts) {
|
||||||
* or ident is smaller than given
|
* or ident is smaller than given
|
||||||
*/
|
*/
|
||||||
function parseMSection(
|
function parseMSection(
|
||||||
text: string,
|
text,
|
||||||
opts: Object,
|
opts,
|
||||||
headingLevel,
|
headingLevel,
|
||||||
indent,
|
indent,
|
||||||
) {
|
) {
|
||||||
|
@ -351,13 +366,13 @@ function parseOpts(inOpts) {
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseParagraph(text: string, inOpts) {
|
export function parseParagraph(text, inOpts) {
|
||||||
const opts = parseOpts(inOpts);
|
const opts = parseOpts(inOpts);
|
||||||
const mText = new MString(text);
|
const mText = new MString(text);
|
||||||
return parseMParagraph(mText, opts);
|
return parseMParagraph(mText, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parse(text: string, inOpts) {
|
export function parse(text, inOpts) {
|
||||||
const opts = parseOpts(inOpts);
|
const opts = parseOpts(inOpts);
|
||||||
const mText = new MString(text);
|
const mText = new MString(text);
|
||||||
return parseMText(mText, opts, 0);
|
return parseMText(mText, opts, 0);
|
||||||
|
|
|
@ -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;
|
|
|
@ -307,20 +307,10 @@ export function setBrightness(hex, dark: boolean = false) {
|
||||||
* @param string input string
|
* @param string input string
|
||||||
* @return escaped string
|
* @return escaped string
|
||||||
*/
|
*/
|
||||||
function escapeRegExp(string) {
|
export function escapeRegExp(string) {
|
||||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched 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
|
* check if webGL2 is available
|
||||||
* @return boolean true if available
|
* @return boolean true if available
|
||||||
|
@ -333,3 +323,54 @@ export function isWebGL2Available() {
|
||||||
return false;
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -219,8 +219,21 @@ class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserData(): Object {
|
getUserData(): Object {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
userlvl,
|
||||||
|
channels,
|
||||||
|
blocked,
|
||||||
|
} = this;
|
||||||
|
const data = {
|
||||||
|
id,
|
||||||
|
userlvl,
|
||||||
|
channels,
|
||||||
|
blocked,
|
||||||
|
};
|
||||||
if (this.regUser == null) {
|
if (this.regUser == null) {
|
||||||
return {
|
return {
|
||||||
|
...data,
|
||||||
name: null,
|
name: null,
|
||||||
mailVerified: false,
|
mailVerified: false,
|
||||||
blockDm: false,
|
blockDm: false,
|
||||||
|
@ -229,15 +242,11 @@ class User {
|
||||||
ranking: null,
|
ranking: null,
|
||||||
dailyRanking: null,
|
dailyRanking: null,
|
||||||
mailreg: false,
|
mailreg: false,
|
||||||
userlvl: 0,
|
|
||||||
channels: this.channels,
|
|
||||||
blocked: this.blocked,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const {
|
const { regUser } = this;
|
||||||
regUser, userlvl, channels, blocked,
|
|
||||||
} = this;
|
|
||||||
return {
|
return {
|
||||||
|
...data,
|
||||||
name: regUser.name,
|
name: regUser.name,
|
||||||
mailVerified: regUser.mailVerified,
|
mailVerified: regUser.mailVerified,
|
||||||
blockDm: regUser.blockDm,
|
blockDm: regUser.blockDm,
|
||||||
|
@ -246,9 +255,6 @@ class User {
|
||||||
ranking: regUser.ranking,
|
ranking: regUser.ranking,
|
||||||
dailyRanking: regUser.dailyRanking,
|
dailyRanking: regUser.dailyRanking,
|
||||||
mailreg: !!(regUser.password),
|
mailreg: !!(regUser.password),
|
||||||
userlvl,
|
|
||||||
channels,
|
|
||||||
blocked,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { createNameRegExp } from '../core/utils';
|
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
|
id: null,
|
||||||
name: null,
|
name: null,
|
||||||
center: [0, 0],
|
|
||||||
wait: null,
|
wait: null,
|
||||||
coolDown: null, // ms
|
coolDown: null, // ms
|
||||||
lastCoolDownEnd: null,
|
lastCoolDownEnd: null,
|
||||||
|
@ -18,8 +16,6 @@ const initialState = {
|
||||||
notification: null,
|
notification: null,
|
||||||
// 1: Admin, 2: Mod, 0: ordinary user
|
// 1: Admin, 2: Mod, 0: ordinary user
|
||||||
userlvl: 0,
|
userlvl: 0,
|
||||||
// regExp for detecting ping
|
|
||||||
nameRegExp: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function user(
|
export default function user(
|
||||||
|
@ -86,43 +82,41 @@ export default function user(
|
||||||
case 'RECEIVE_ME':
|
case 'RECEIVE_ME':
|
||||||
case 'LOGIN': {
|
case 'LOGIN': {
|
||||||
const {
|
const {
|
||||||
|
id,
|
||||||
name,
|
name,
|
||||||
mailreg,
|
mailreg,
|
||||||
blockDm,
|
blockDm,
|
||||||
userlvl,
|
userlvl,
|
||||||
} = action;
|
} = action;
|
||||||
const nameRegExp = createNameRegExp(name);
|
|
||||||
const messages = (action.messages) ? action.messages : [];
|
const messages = (action.messages) ? action.messages : [];
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
id,
|
||||||
name,
|
name,
|
||||||
messages,
|
messages,
|
||||||
mailreg,
|
mailreg,
|
||||||
blockDm,
|
blockDm,
|
||||||
userlvl,
|
userlvl,
|
||||||
nameRegExp,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'LOGOUT': {
|
case 'LOGOUT': {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
id: null,
|
||||||
name: null,
|
name: null,
|
||||||
messages: [],
|
messages: [],
|
||||||
mailreg: false,
|
mailreg: false,
|
||||||
blockDm: false,
|
blockDm: false,
|
||||||
userlvl: 0,
|
userlvl: 0,
|
||||||
nameRegExp: null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_NAME': {
|
case 'SET_NAME': {
|
||||||
const { name } = action;
|
const { name } = action;
|
||||||
const nameRegExp = createNameRegExp(name);
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
name,
|
name,
|
||||||
nameRegExp,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -665,17 +665,20 @@ tr:nth-child(even) {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.chatname {
|
.chatname {
|
||||||
color: #4B0000;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.chatmsg {
|
.chatmsg {
|
||||||
color: #030303;
|
color: #030303;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
.chatmsg > span {
|
.msg {
|
||||||
display: inline-block;
|
flex: 1;
|
||||||
}
|
}
|
||||||
.msg.info {
|
.msg.info {
|
||||||
color: #cc0000;
|
color: #cc0000;
|
||||||
|
@ -686,6 +689,9 @@ tr:nth-child(even) {
|
||||||
.msg.greentext{
|
.msg.greentext{
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
|
.msg.redtext{
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
.ping {
|
.ping {
|
||||||
background-color: #ffff87;
|
background-color: #ffff87;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
@ -905,3 +911,18 @@ tr:nth-child(even) {
|
||||||
.Modal.show, .Alert.show, .OverlayAlert.show, .OverlayModal.show, .window.show {
|
.Modal.show, .Alert.show, .OverlayAlert.show, .OverlayModal.show, .window.show {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vemb {
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 66%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vembc {
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
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)) {
|
if (!Array.isArray(part)) {
|
||||||
return part;
|
return part;
|
||||||
}
|
}
|
||||||
|
@ -14,51 +17,42 @@ const MarkdownParagraph = ({ pArray }) => pArray.map((part) => {
|
||||||
case '*':
|
case '*':
|
||||||
return (
|
return (
|
||||||
<strong>
|
<strong>
|
||||||
<MarkdownParagraph pArray={part[1]} />
|
<MarkdownParagraph pArray={part[1]} />
|
||||||
</strong>
|
</strong>
|
||||||
);
|
);
|
||||||
case '~':
|
case '~':
|
||||||
return (
|
return (
|
||||||
<s>
|
<s>
|
||||||
<MarkdownParagraph pArray={part[1]} />
|
<MarkdownParagraph pArray={part[1]} />
|
||||||
</s>
|
</s>
|
||||||
);
|
);
|
||||||
case '+':
|
case '+':
|
||||||
return (
|
return (
|
||||||
<em>
|
<em>
|
||||||
<MarkdownParagraph pArray={part[1]} />
|
<MarkdownParagraph pArray={part[1]} />
|
||||||
</em>
|
</em>
|
||||||
);
|
);
|
||||||
case '_':
|
case '_':
|
||||||
return (
|
return (
|
||||||
<u>
|
<u>
|
||||||
<MarkdownParagraph pArray={part[1]} />
|
<MarkdownParagraph pArray={part[1]} />
|
||||||
</u>
|
</u>
|
||||||
);
|
);
|
||||||
|
case 'img':
|
||||||
case 'l': {
|
case 'l': {
|
||||||
let title = getLinkDesc(part[2]);
|
|
||||||
if (part[1]) {
|
|
||||||
title += ` | ${part[1]}`;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<a href={part[2]}>
|
<MdLink href={part[2]} title={part[1]} />
|
||||||
{title}
|
|
||||||
</a>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case 'img': {
|
case '@': {
|
||||||
let title = getLinkDesc(part[2]);
|
|
||||||
if (part[1]) {
|
|
||||||
title += ` | ${part[1]}`;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<img src={part[2]} title={part[1] || title} alt={title} />
|
<MdMention uid={part[2]} name={part[1]} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
const Markdown = ({ mdArray }) => mdArray.map((part) => {
|
const Markdown = ({ mdArray }) => mdArray.map((part) => {
|
||||||
const type = part[0];
|
const type = part[0];
|
||||||
|
@ -86,7 +80,7 @@ const Markdown = ({ mdArray }) => mdArray.map((part) => {
|
||||||
headingElem,
|
headingElem,
|
||||||
<section>
|
<section>
|
||||||
<Markdown mdArray={children} />
|
<Markdown mdArray={children} />
|
||||||
</section>
|
</section>,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
/* Paragraph */
|
/* Paragraph */
|
||||||
|
|
|
@ -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);
|
|
||||||
<div className="link">
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Reace.memo(MdLink);
|
|
24
utils/markdown-test/MdMention.jsx
Normal file
24
utils/markdown-test/MdMention.jsx
Normal file
|
@ -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 (
|
||||||
|
<span
|
||||||
|
className={(id == ownid) ? "ping" : "mention"}
|
||||||
|
style={{
|
||||||
|
color: setBrightness(colorFromText(name), dark),
|
||||||
|
}}
|
||||||
|
>{name}</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(MdMention);
|
|
@ -23,3 +23,18 @@ pre {
|
||||||
.rt {
|
.rt {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vemb {
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 66%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vembc {
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<meta name="description" content="Testing Markdown Parser">
|
<meta name="description" content="Testing Markdown Parser">
|
||||||
<meta name="author" content="hf">
|
<meta name="author" content="hf">
|
||||||
<link rel="stylesheet" href="index.css">
|
<link rel="stylesheet" href="index.css">
|
||||||
|
<script>window.ssv={assetserver:'http://dev.pixelplanet.fun'}</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -1,24 +1,6 @@
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var webpack = require('webpack');
|
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 = {
|
module.exports = {
|
||||||
name: 'script',
|
name: 'script',
|
||||||
target: 'web',
|
target: 'web',
|
||||||
|
@ -41,16 +23,7 @@ module.exports = {
|
||||||
test: /\.(js|jsx)$/,
|
test: /\.(js|jsx)$/,
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
options: {
|
options: {
|
||||||
presets: [
|
rootMode: 'upward-optional',
|
||||||
['@babel/preset-env', {
|
|
||||||
targets: {
|
|
||||||
browsers: [ 'defaults' ],
|
|
||||||
},
|
|
||||||
modules: false,
|
|
||||||
}],
|
|
||||||
'@babel/react',
|
|
||||||
],
|
|
||||||
plugins: babelPlugins,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -7,8 +7,6 @@ import webpack from 'webpack';
|
||||||
import AssetsPlugin from 'assets-webpack-plugin';
|
import AssetsPlugin from 'assets-webpack-plugin';
|
||||||
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
||||||
|
|
||||||
import pkg from './package.json';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Emit a file with assets paths
|
* Emit a file with assets paths
|
||||||
*/
|
*/
|
||||||
|
@ -120,13 +118,6 @@ export function buildWebpackClientConfig(
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
presets: [
|
|
||||||
['@babel/preset-env', {
|
|
||||||
targets: {
|
|
||||||
browsers: pkg.browserslist,
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
],
|
|
||||||
plugins: babelPlugins,
|
plugins: babelPlugins,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -75,14 +75,6 @@ export default ({
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
cacheDirectory: false,
|
cacheDirectory: false,
|
||||||
presets: [
|
|
||||||
['@babel/preset-env', {
|
|
||||||
targets: {
|
|
||||||
node: pkg.engines.node.replace(/^\D+/g, ''),
|
|
||||||
},
|
|
||||||
modules: false,
|
|
||||||
}],
|
|
||||||
],
|
|
||||||
plugins: babelPlugins,
|
plugins: babelPlugins,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user