aboutsummaryrefslogtreecommitdiff
path: root/vnext
diff options
context:
space:
mode:
Diffstat (limited to 'vnext')
-rw-r--r--vnext/src/App.js27
-rw-r--r--vnext/src/api/index.js59
-rw-r--r--vnext/src/ui/Spinner.js4
-rw-r--r--vnext/src/utils/embed.js24
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 &copy; 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(/^(?:>|&gt;)\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) {