diff --git a/babel.config.js b/babel.config.js index dd91e8d3..e6243ccb 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 f03168e9..404fd90c 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 62c2460e..0caee60d 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 00000000..0ce2d67d 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 00000000..2c18c762 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 00000000..0f603e53 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 00000000..cf0bd432 Binary files /dev/null and b/public/embico/youtube.png differ diff --git a/src/actions/index.js b/src/actions/index.js index 08096820..96712f0d 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 eaf5f38c..9371c634 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} - - :  - + <> + { + 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 00000000..dbedd123 --- /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 00000000..7a658daa --- /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]) + && ( + {`${desc}-icon`} + )} + 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 00000000..4bbc6f44 --- /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 197285c1..0f90d1d4 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 00000000..e632a225 --- /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 ( + {`Matrix + ); +}; + +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 00000000..0ef1a067 --- /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 ( + {`Matrix + ); +}; + +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 00000000..e0ea4121 --- /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 ( +