diff options
Diffstat (limited to 'vnext')
-rw-r--r-- | vnext/src/App.js | 27 | ||||
-rw-r--r-- | vnext/src/api/index.js | 59 | ||||
-rw-r--r-- | vnext/src/ui/Spinner.js | 4 | ||||
-rw-r--r-- | vnext/src/utils/embed.js | 24 |
4 files changed, 91 insertions, 23 deletions
diff --git a/vnext/src/App.js b/vnext/src/App.js index e6798b31..4e235c46 100644 --- a/vnext/src/App.js +++ b/vnext/src/App.js @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef, Fragment } from 'react'; import { Route, Link, Routes } from 'react-router-dom'; import qs from 'qs'; @@ -17,16 +17,21 @@ import Login from './ui/Login'; import { useCookies } from 'react-cookie'; -import { me } from './api'; +import { me, trends } from './api'; /** - * + * + * @param {import('react').PropsWithChildren<{}> & { + * footer: string + * }} props props */ export default function App({ footer }) { let contentRef = useRef(null); const [cookie, setCookie] = useCookies(['hash']); + const [allTrends, setAllTrends] = useState([]); + useEffect(() => { svg4everybody(); let params = qs.parse(window.location.search.substring(1)); @@ -91,6 +96,13 @@ export default function App({ footer }) { }); }, [hash]); + useEffect(() => { + const getTrends = async () => { + setAllTrends(await trends()); + }; + getTrends(); + }, []); + /** * @param {import("./api").SecureUser} visitor */ @@ -127,6 +139,15 @@ export default function App({ footer }) { <span className="desktop">Settings</span> </Link> </>)} + <div className="tags desktop"> + <h4>Trends</h4> + { allTrends.map((it, index) => ( + <Fragment key={it.tag}> + {index > 0 && ' '} + <Link to={`/tag/${it.tag}`}>#{it.tag}</Link> + </Fragment> + )) } + </div> <div id="footer" className="desktop"> <div id="footer-left">juick.com © 2008-2022 {footer && (<><br />Sponsors: <span dangerouslySetInnerHTML={{ __html: footer }}></span></>)}</div> diff --git a/vnext/src/api/index.js b/vnext/src/api/index.js index a6a6208c..fac845e8 100644 --- a/vnext/src/api/index.js +++ b/vnext/src/api/index.js @@ -4,13 +4,17 @@ import Cookies from 'universal-cookie'; const apiBaseUrl = 'https://juick.com'; /** - * @typedef {Object} Token + * @typedef {object} Token * @property {string} type * @property {string} token */ +/** + * @typedef { object } TagStats + * @property { string } tag + */ /** - * @typedef {Object} User + * @typedef {object} User * @property {string=} uname * @property {number} uid * @property {number=} unreadCount @@ -22,7 +26,7 @@ const apiBaseUrl = 'https://juick.com'; */ /** - * @typedef {Object} SecureUserProperties + * @typedef {object} SecureUserProperties * @property {string=} hash * @property {Token[]=} tokens * @property {string=} telegramName @@ -38,7 +42,7 @@ const apiBaseUrl = 'https://juick.com'; */ /** - * @typedef {Object} ChatProperties + * @typedef {object} ChatProperties * @property {number=} unreadCount * @property {string=} lastMessageText */ @@ -48,7 +52,7 @@ const apiBaseUrl = 'https://juick.com'; */ /** - * @typedef {Object} Message + * @typedef {object} Message * @property {string} body * @property {number=} mid * @property {number=} rid @@ -83,9 +87,10 @@ client.interceptors.request.use(config => { /** * fetch my info + * * @param {string} username * @param {string} password - * @return {Promise<SecureUser, Error>} me object + * @returns {Promise<SecureUser, Error>} me object */ export function me(username = '', password = '') { let cookies = new Cookies(); @@ -113,6 +118,9 @@ export function info(username) { } +/** + * + */ export function getChats() { return client.get('/api/groups_pms'); } @@ -174,7 +182,13 @@ export function comment(mid, rid, body, attach) { form.append('attach', attach); return client.post('/api/comment', form); } - +/** + * Edit message + * + * @param {number} mid + * @param {number} rid + * @param {string?} body + */ export function update(mid, rid, body) { let form = new FormData(); form.append('mid', mid); @@ -182,7 +196,11 @@ export function update(mid, rid, body) { form.append('body', body); return client.post('/api/update', form); } - +/** + * Update user avatar + * + * @param {string} newAvatar + */ export function updateAvatar(newAvatar) { let form = new FormData(); form.append('avatar', newAvatar); @@ -196,14 +214,23 @@ function socialLink(network) { return `${apiBaseUrl}/api/_${network}login?state=${window.location.protocol}//${window.location.host}${window.location.pathname}`; } +/** + * + */ export function facebookLink() { return socialLink('fb'); } +/** + * + */ export function vkLink() { return socialLink('vk'); } +/** + * + */ export function appleLink() { return socialLink('apple'); } @@ -239,3 +266,19 @@ export function fetchUserUri(profileUrl) { } }); } + +/** + * + * @returns { Promise<TagStats[]> } tags + */ + export const trends = async () => { + try { + const response = await client.get('/api/tags'); + return response.data; + } catch (e) { + console.error(e); + return []; + } +}; + + diff --git a/vnext/src/ui/Spinner.js b/vnext/src/ui/Spinner.js index b5027c08..d408f37f 100644 --- a/vnext/src/ui/Spinner.js +++ b/vnext/src/ui/Spinner.js @@ -25,6 +25,10 @@ function Spinner(props) { export default memo(Spinner); +/** + * + * @param { import('react').PropsWithChildren<{}> } props + */ export function ChatSpinner(props) { return ( <ContentLoader diff --git a/vnext/src/utils/embed.js b/vnext/src/utils/embed.js index 22dacd8b..2e2f0aca 100644 --- a/vnext/src/utils/embed.js +++ b/vnext/src/utils/embed.js @@ -16,7 +16,7 @@ function setContent(containerNode, ...newNodes) { } function removeAllFrom(fromNode) { - for (let c; c = fromNode.lastChild; ) { fromNode.removeChild(c); } + fromNode.innerHTML = ''; } // rules :: [{pr: number, re: RegExp, with: string}] @@ -88,7 +88,7 @@ function makeResizable(element, calcHeight) { } function extractDomain(url) { - const domainRe = /^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/\n]+)/i; + const domainRe = /^(?:https?:\/\/)?(?:[^@/\n]+@)?(?:www\.)?([^:/\n]+)/i; let result = domainRe.exec(url) || []; if (result.length > 0) { return result[1]; @@ -126,7 +126,7 @@ function messageReplyReplace(messageId) { * @returns {string} formatted message */ function juickFormat(txt, messageId, isCode) { - const urlRe = /(?:\[([^\]\[]+)\](?:\[([^\]]+)\]|\(((?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[-\w+*&@#/%=~|$?!:;,.])*(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[\w+*&@#/%=~|$]))\))|\b(?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[-\w+*&@#/%=~|$?!:;,.])*(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[\w+*&@#/%=~|$]))/gi; + const urlRe = /(?:\[([^\][]+)\](?:\[([^\]]+)\]|\(((?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[-\w+*&@#/%=~|$?!:;,.])*(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[\w+*&@#/%=~|$]))\))|\b(?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[-\w+*&@#/%=~|$?!:;,.])*(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[\w+*&@#/%=~|$]))/gi; const bqReplace = m => m.replace(/^(?:>|>)\s?/gmi, ''); return (isCode) ? formatText(txt, [ @@ -139,9 +139,9 @@ function juickFormat(txt, messageId, isCode) { { pr: 1, re: urlRe, with: urlReplace }, { pr: 1, re: /\B(?:#(\d+))?(?:\/(\d+))?\b/g, with: messageReplyReplace(messageId) }, { pr: 1, re: /\B@([\w-]+)\b/gi, with: '<a href="/$1">@$1</a>' }, - { pr: 2, re: /\B\*([^\n]+?)\*((?=\s)|(?=$)|(?=[!\"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<b>', '</b>'] }, - { pr: 2, re: /\B\/([^\n]+?)\/((?=\s)|(?=$)|(?=[!\"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<i>', '</i>'] }, - { pr: 2, re: /\b\_([^\n]+?)\_((?=\s)|(?=$)|(?=[!\"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<span class="u">', '</span>'] }, + { pr: 2, re: /\B\*([^\n]+?)\*((?=\s)|(?=$)|(?=[!"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<b>', '</b>'] }, + { pr: 2, re: /\B\/([^\n]+?)\/((?=\s)|(?=$)|(?=[!"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<i>', '</i>'] }, + { pr: 2, re: /\b_([^\n]+?)_((?=\s)|(?=$)|(?=[!"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<span class="u">', '</span>'] }, { pr: 3, re: /\n/g, with: '<br/>' }, ]); } @@ -153,7 +153,7 @@ function getEmbeddableLinkTypes() { id: 'embed_jpeg_and_png_images', className: 'picture compact', ctsDefault: false, - re: /\.(jpe?g|png|svg)(:[a-zA-Z]+)?(?:\?[\w&;\?=]*)?$/i, + re: /\.(jpe?g|png|svg)(:[a-zA-Z]+)?(?:\?[\w&;?=]*)?$/i, makeNode: function(aNode, reResult, div) { div.innerHTML = `<a href="${aNode.href}"><img src="${aNode.href}"></a>`; return div; @@ -164,7 +164,7 @@ function getEmbeddableLinkTypes() { id: 'embed_gif_images', className: 'picture compact', ctsDefault: true, - re: /\.gif(:[a-zA-Z]+)?(?:\?[\w&;\?=]*)?$/i, + re: /\.gif(:[a-zA-Z]+)?(?:\?[\w&;?=]*)?$/i, makeNode: function(aNode, reResult, div) { div.innerHTML = `<a href="${aNode.href}"><img src="${aNode.href}"></a>`; return div; @@ -175,7 +175,7 @@ function getEmbeddableLinkTypes() { id: 'embed_webm_and_mp4_videos', className: 'video compact', ctsDefault: false, - re: /\.(webm|mp4|m4v|ogv)(?:\?[\w&;\?=]*)?$/i, + re: /\.(webm|mp4|m4v|ogv)(?:\?[\w&;?=]*)?$/i, makeNode: function(aNode, reResult, div) { div.innerHTML = `<video src="${aNode.href}" title="${aNode.href}" controls></video>`; return div; @@ -186,7 +186,7 @@ function getEmbeddableLinkTypes() { id: 'embed_sound_files', className: 'audio singleColumn', ctsDefault: false, - re: /\.(mp3|ogg|weba|opus|m4a|oga|wav)(?:\?[\w&;\?=]*)?$/i, + re: /\.(mp3|ogg|weba|opus|m4a|oga|wav)(?:\?[\w&;?=]*)?$/i, makeNode: function(aNode, reResult, div) { div.innerHTML = `<audio src="${aNode.href}" title="${aNode.href}" controls></audio>`; return div; @@ -197,7 +197,7 @@ function getEmbeddableLinkTypes() { id: 'embed_youtube_videos', className: 'youtube resizableV singleColumn', ctsDefault: false, - re: /^(?:https?:)?\/\/(?:www\.|m\.|gaming\.)?(?:youtu(?:(?:\.be\/|be\.com\/(?:v|embed)\/)([-\w]+)|be\.com\/watch)((?:(?:\?|&(?:amp;)?)(?:\w+=[-\.\w]*[-\w]))*)|youtube\.com\/playlist\?list=([-\w]*)(&(amp;)?[-\w\?=]*)?)/i, + re: /^(?:https?:)?\/\/(?:www\.|m\.|gaming\.)?(?:youtu(?:(?:\.be\/|be\.com\/(?:v|embed)\/)([-\w]+)|be\.com\/watch)((?:(?:\?|&(?:amp;)?)(?:\w+=[-.\w]*[-\w]))*)|youtube\.com\/playlist\?list=([-\w]*)(&(amp;)?[-\w?=]*)?)/i, makeNode: function(aNode, reResult, div) { let [v, args, plist] = reResult; let iframeUrl; @@ -293,7 +293,7 @@ function getEmbeddableLinkTypes() { function embedLink(aNode, linkTypes, container, afterNode) { let anyEmbed = false; - let linkId = (aNode.href.replace(/^https?:/i, '').replace(/\'/gi,'')); + let linkId = (aNode.href.replace(/^https?:/i, '').replace(/'/gi,'')); let sameEmbed = container.querySelector(`*[data-linkid='${linkId}']`); // do not embed the same thing twice if (sameEmbed === null) { anyEmbed = [].some.call(linkTypes, function(linkType) { |